Merge "Add ISIM application support for LTE devices."

This commit is contained in:
Jake Hamby
2011-08-26 16:47:45 -07:00
committed by Android (Google) Code Review
21 changed files with 727 additions and 278 deletions

View File

@@ -582,6 +582,12 @@
android:label="@string/permlab_readPhoneState"
android:description="@string/permdesc_readPhoneState" />
<!-- Allows read access to privileged phone state.
@hide Used internally. -->
<permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
android:permissionGroup="android.permission-group.PHONE_CALLS"
android:protectionLevel="signatureOrSystem" />
<!-- ================================== -->
<!-- Permissions for sdcard interaction -->
<!-- ================================== -->

View File

@@ -832,6 +832,55 @@ public class TelephonyManager {
}
}
/**
* Returns the IMS private user identity (IMPI) that was loaded from the ISIM.
* @return the IMPI, or null if not present or not loaded
* @hide
*/
public String getIsimImpi() {
try {
return getSubscriberInfo().getIsimImpi();
} catch (RemoteException ex) {
return null;
} catch (NullPointerException ex) {
// This could happen before phone restarts due to crashing
return null;
}
}
/**
* Returns the IMS home network domain name that was loaded from the ISIM.
* @return the IMS domain name, or null if not present or not loaded
* @hide
*/
public String getIsimDomain() {
try {
return getSubscriberInfo().getIsimDomain();
} catch (RemoteException ex) {
return null;
} catch (NullPointerException ex) {
// This could happen before phone restarts due to crashing
return null;
}
}
/**
* Returns the IMS public user identities (IMPU) that were loaded from the ISIM.
* @return an array of IMPU strings, with one IMPU per string, or null if
* not present or not loaded
* @hide
*/
public String[] getIsimImpu() {
try {
return getSubscriberInfo().getIsimImpu();
} catch (RemoteException ex) {
return null;
} catch (NullPointerException ex) {
// This could happen before phone restarts due to crashing
return null;
}
}
private IPhoneSubInfo getSubscriberInfo() {
// get it each time because that process crashes a lot
return IPhoneSubInfo.Stub.asInterface(ServiceManager.getService("iphonesubinfo"));

View File

@@ -1489,11 +1489,8 @@ public interface CommandsInterface {
/**
* Setup a packet data connection On successful completion, the result
* message will return the following: [0] indicating PDP CID, which is
* generated by RIL. This Connection ID is used in both GSM/UMTS and CDMA
* modes [1] indicating the network interface name for GSM/UMTS or CDMA [2]
* indicating the IP address for this interface for GSM/UMTS and NULL in the
* case of CDMA
* message will return a {@link DataCallState} object containing the connection
* information.
*
* @param radioTechnology
* indicates whether to setup connection on radio technology CDMA
@@ -1569,7 +1566,7 @@ public interface CommandsInterface {
/**
* Request the status of the ICC and UICC cards.
*
* @param response
* @param result
* Callback message containing {@link IccCardStatus} structure for the card.
*/
public void getIccCardStatus(Message result);
@@ -1583,4 +1580,14 @@ public interface CommandsInterface {
* or {@link Phone#LTE_ON_CDMA_TRUE}
*/
public int getLteOnCdmaMode();
/**
* Request the ISIM application on the UICC to perform the AKA
* challenge/response algorithm for IMS authentication. The nonce string
* and challenge response are Base64 encoded Strings.
*
* @param nonce the nonce string to pass with the ISIM authentication request
* @param response a callback message with the String response in the obj field
*/
public void requestIsimAuthentication(String nonce, Message response);
}

View File

@@ -67,4 +67,23 @@ interface IPhoneSubInfo {
* Retrieves the alpha identifier associated with the voice mail number.
*/
String getVoiceMailAlphaTag();
/**
* Returns the IMS private user identity (IMPI) that was loaded from the ISIM.
* @return the IMPI, or null if not present or not loaded
*/
String getIsimImpi();
/**
* Returns the IMS home network domain name that was loaded from the ISIM.
* @return the IMS domain name, or null if not present or not loaded
*/
String getIsimDomain();
/**
* Returns the IMS public user identities (IMPU) that were loaded from the ISIM.
* @return an array of IMPU strings, with one IMPU per string, or null if
* not present or not loaded
*/
String[] getIsimImpu();
}

View File

@@ -22,6 +22,8 @@ import android.os.Message;
import android.os.Registrant;
import android.os.RegistrantList;
import com.android.internal.telephony.ims.IsimRecords;
/**
* {@hide}
*/
@@ -70,6 +72,24 @@ public abstract class IccRecords extends Handler implements IccConstants {
// ***** Event Constants
protected static final int EVENT_SET_MSISDN_DONE = 30;
public static final int EVENT_GET_ICC_RECORD_DONE = 100;
/**
* Generic ICC record loaded callback. Subclasses can call EF load methods on
* {@link IccFileHandler} passing a Message for onLoaded with the what field set to
* {@link #EVENT_GET_ICC_RECORD_DONE} and the obj field set to an instance
* of this interface. The {@link #handleMessage} method in this class will print a
* log message using {@link #getEfName()} and decrement {@link #recordsToLoad}.
*
* If the record load was successful, {@link #onRecordLoaded} will be called with the result.
* Otherwise, an error log message will be output by {@link #handleMessage} and
* {@link #onRecordLoaded} will not be called.
*/
public interface IccRecordLoaded {
String getEfName();
void onRecordLoaded(AsyncResult ar);
}
// ***** Constructor
public IccRecords(PhoneBase p) {
@@ -234,7 +254,32 @@ public abstract class IccRecords extends Handler implements IccConstants {
//***** Overridden from Handler
@Override
public abstract void handleMessage(Message msg);
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_GET_ICC_RECORD_DONE:
try {
AsyncResult ar = (AsyncResult) msg.obj;
IccRecordLoaded recordLoaded = (IccRecordLoaded) ar.userObj;
if (DBG) log(recordLoaded.getEfName() + " LOADED");
if (ar.exception != null) {
loge("Record Load Exception: " + ar.exception);
} else {
recordLoaded.onRecordLoaded(ar);
}
}catch (RuntimeException exc) {
// I don't want these exceptions to be fatal
loge("Exception parsing SIM record: " + exc);
} finally {
// Count up record load responses even if they are fails
onRecordLoaded();
}
break;
default:
super.handleMessage(msg);
}
}
protected abstract void onRecordLoaded();
@@ -303,4 +348,19 @@ public abstract class IccRecords extends Handler implements IccConstants {
* @param s is the string to write
*/
protected abstract void log(String s);
/**
* Write error string to log file.
*
* @param s is the string to write
*/
protected abstract void loge(String s);
/**
* Return an interface to retrieve the ISIM records for IMS, if available.
* @return the interface to retrieve the ISIM records, or null if not supported
*/
public IsimRecords getIsimRecords() {
return null;
}
}

View File

@@ -28,6 +28,7 @@ import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import com.android.internal.telephony.DataConnection;
import com.android.internal.telephony.ims.IsimRecords;
import com.android.internal.telephony.test.SimulatedRadioControl;
import java.util.List;
@@ -1730,4 +1731,20 @@ public interface Phone {
* false otherwise
*/
boolean isCspPlmnEnabled();
/**
* Return an interface to retrieve the ISIM records for IMS, if available.
* @return the interface to retrieve the ISIM records, or null if not supported
*/
IsimRecords getIsimRecords();
/**
* Request the ISIM application on the UICC to perform the AKA
* challenge/response algorithm for IMS authentication. The nonce string
* and challenge response are Base64 encoded Strings.
*
* @param nonce the nonce string to pass with the ISIM authentication request
* @param response a callback message with the String response in the obj field
*/
void requestIsimAuthentication(String nonce, Message response);
}

View File

@@ -37,6 +37,7 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.internal.R;
import com.android.internal.telephony.ims.IsimRecords;
import com.android.internal.telephony.test.SimulatedRadioControl;
import com.android.internal.telephony.gsm.SIMRecords;
import com.android.internal.telephony.gsm.SimCard;
@@ -1114,6 +1115,15 @@ public abstract class PhoneBase extends Handler implements Phone {
return false;
}
public IsimRecords getIsimRecords() {
Log.e(LOG_TAG, "getIsimRecords() is only supported on LTE devices");
return null;
}
public void requestIsimAuthentication(String nonce, Message result) {
Log.e(LOG_TAG, "requestIsimAuthentication() is only supported on LTE devices");
}
/**
* Common error logger method for unexpected calls to CDMA-only methods.
*/

View File

@@ -32,6 +32,7 @@ import android.util.Log;
import com.android.internal.telephony.cdma.CDMAPhone;
import com.android.internal.telephony.gsm.GSMPhone;
import com.android.internal.telephony.ims.IsimRecords;
import com.android.internal.telephony.test.SimulatedRadioControl;
import java.util.List;
@@ -841,6 +842,14 @@ public class PhoneProxy extends Handler implements Phone {
return mActivePhone.isCspPlmnEnabled();
}
public IsimRecords getIsimRecords() {
return mActivePhone.getIsimRecords();
}
public void requestIsimAuthentication(String nonce, Message response) {
mActivePhone.requestIsimAuthentication(nonce, response);
}
/**
* {@inheritDoc}
*/

View File

@@ -24,15 +24,19 @@ import android.os.Binder;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import com.android.internal.telephony.ims.IsimRecords;
public class PhoneSubInfo extends IPhoneSubInfo.Stub {
static final String LOG_TAG = "PHONE";
private Phone mPhone;
private Context mContext;
private static final String READ_PHONE_STATE =
android.Manifest.permission.READ_PHONE_STATE;
// TODO: change getCompleteVoiceMailNumber() to require READ_PRIVILEGED_PHONE_STATE
private static final String CALL_PRIVILEGED =
// TODO Add core/res/AndriodManifest.xml#READ_PRIVILEGED_PHONE_STATE
android.Manifest.permission.CALL_PRIVILEGED;
private static final String READ_PRIVILEGED_PHONE_STATE =
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
public PhoneSubInfo(Phone phone) {
mPhone = phone;
@@ -131,6 +135,52 @@ public class PhoneSubInfo extends IPhoneSubInfo.Stub {
return (String) mPhone.getVoiceMailAlphaTag();
}
/**
* Returns the IMS private user identity (IMPI) that was loaded from the ISIM.
* @return the IMPI, or null if not present or not loaded
*/
public String getIsimImpi() {
mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
"Requires READ_PRIVILEGED_PHONE_STATE");
IsimRecords isim = mPhone.getIsimRecords();
if (isim != null) {
return isim.getIsimImpi();
} else {
return null;
}
}
/**
* Returns the IMS home network domain name that was loaded from the ISIM.
* @return the IMS domain name, or null if not present or not loaded
*/
public String getIsimDomain() {
mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
"Requires READ_PRIVILEGED_PHONE_STATE");
IsimRecords isim = mPhone.getIsimRecords();
if (isim != null) {
return isim.getIsimDomain();
} else {
return null;
}
}
/**
* Returns the IMS public user identities (IMPU) that were loaded from the ISIM.
* @return an array of IMPU strings, with one IMPU per string, or null if
* not present or not loaded
*/
public String[] getIsimImpu() {
mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
"Requires READ_PRIVILEGED_PHONE_STATE");
IsimRecords isim = mPhone.getIsimRecords();
if (isim != null) {
return isim.getIsimImpu();
} else {
return null;
}
}
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {

View File

@@ -95,6 +95,31 @@ public class PhoneSubInfoProxy extends IPhoneSubInfo.Stub {
return mPhoneSubInfo.getVoiceMailAlphaTag();
}
/**
* Returns the IMS private user identity (IMPI) that was loaded from the ISIM.
* @return the IMPI, or null if not present or not loaded
*/
public String getIsimImpi() {
return mPhoneSubInfo.getIsimImpi();
}
/**
* Returns the IMS home network domain name that was loaded from the ISIM.
* @return the IMS domain name, or null if not present or not loaded
*/
public String getIsimDomain() {
return mPhoneSubInfo.getIsimDomain();
}
/**
* Returns the IMS public user identities (IMPU) that were loaded from the ISIM.
* @return an array of IMPU strings, with one IMPU per string, or null if
* not present or not loaded
*/
public String[] getIsimImpu() {
return mPhoneSubInfo.getIsimImpu();
}
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mPhoneSubInfo.dump(fd, pw, args);
}

View File

@@ -47,17 +47,8 @@ import android.telephony.SmsMessage;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.telephony.CallForwardInfo;
import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.DataCallState;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
import com.android.internal.telephony.gsm.SuppServiceNotification;
import com.android.internal.telephony.IccCardApplication;
import com.android.internal.telephony.IccCardStatus;
import com.android.internal.telephony.IccUtils;
import com.android.internal.telephony.OperatorInfo;
import com.android.internal.telephony.RILConstants;
import com.android.internal.telephony.SmsResponse;
import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
import com.android.internal.telephony.cdma.CdmaInformationRecords;
@@ -2255,6 +2246,7 @@ public final class RIL extends BaseCommands implements CommandsInterface {
case RIL_REQUEST_REPORT_SMS_MEMORY_STATUS: ret = responseVoid(p); break;
case RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING: ret = responseVoid(p); break;
case RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE: ret = responseInts(p); break;
case RIL_REQUEST_ISIM_AUTHENTICATION: ret = responseString(p); break;
default:
throw new RuntimeException("Unrecognized solicited response: " + rr.mRequest);
//break;
@@ -3461,6 +3453,7 @@ public final class RIL extends BaseCommands implements CommandsInterface {
case RIL_REQUEST_REPORT_SMS_MEMORY_STATUS: return "RIL_REQUEST_REPORT_SMS_MEMORY_STATUS";
case RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING: return "RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING";
case RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE: return "RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE";
case RIL_REQUEST_ISIM_AUTHENTICATION: return "RIL_REQUEST_ISIM_AUTHENTICATION";
default: return "<unknown request>";
}
}
@@ -3704,4 +3697,13 @@ public final class RIL extends BaseCommands implements CommandsInterface {
send(rr);
}
public void requestIsimAuthentication(String nonce, Message response) {
RILRequest rr = RILRequest.obtain(RIL_REQUEST_ISIM_AUTHENTICATION, response);
rr.mp.writeString(nonce);
if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
send(rr);
}
}

View File

@@ -261,6 +261,7 @@ cat include/telephony/ril.h | \
int RIL_REQUEST_REPORT_SMS_MEMORY_STATUS = 102;
int RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING = 103;
int RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE = 104;
int RIL_REQUEST_ISIM_AUTHENTICATION = 105;
int RIL_UNSOL_RESPONSE_BASE = 1000;
int RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED = 1000;
int RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED = 1001;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2006 The Android Open Source Project
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,29 +16,19 @@
package com.android.internal.telephony.cdma;
import android.os.SystemProperties;
import android.content.Context;
import android.net.Uri;
import android.content.Context;
import android.provider.Telephony;
import android.content.ContentValues;
import android.content.Context;
import android.database.SQLException;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.net.Uri;
import android.os.Message;
import android.provider.Telephony;
import android.util.Log;
import com.android.internal.telephony.gsm.SIMRecords;
import com.android.internal.telephony.gsm.SimCard;
import com.android.internal.telephony.ServiceStateTracker;
import com.android.internal.telephony.DataConnectionTracker;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneBase;
import com.android.internal.telephony.PhoneNotifier;
import com.android.internal.telephony.PhoneProxy;
import com.android.internal.telephony.IccCard;
import com.android.internal.telephony.gsm.GsmDataConnectionTracker;
import android.util.Log;
import com.android.internal.telephony.gsm.SimCard;
import com.android.internal.telephony.ims.IsimRecords;
public class CDMALTEPhone extends CDMAPhone {
static final String LOG_TAG = "CDMA";
@@ -68,7 +58,7 @@ public class CDMALTEPhone extends CDMAPhone {
DataState ret = DataState.DISCONNECTED;
if (mSST == null) {
// Radio Technology Change is ongoning, dispose() and
// Radio Technology Change is ongoing, dispose() and
// removeReferences() have already been called
ret = DataState.DISCONNECTED;
@@ -145,6 +135,16 @@ public class CDMALTEPhone extends CDMAPhone {
return mImeiSv;
}
@Override
public IsimRecords getIsimRecords() {
return mIccRecords.getIsimRecords();
}
@Override
public void requestIsimAuthentication(String nonce, Message result) {
mCM.requestIsimAuthentication(nonce, result);
}
@Override
protected void log(String s) {
if (DBG)

View File

@@ -42,6 +42,10 @@ public final class CdmaLteUiccFileHandler extends IccFileHandler {
return MF_SIM + DF_CDMA;
case EF_AD:
return MF_SIM + DF_GSM;
case EF_IMPI:
case EF_DOMAIN:
case EF_IMPU:
return MF_SIM + DF_ADFISIM;
}
return getCommonIccEFPath(efid);
}

View File

@@ -15,8 +15,10 @@
*/
package com.android.internal.telephony.cdma;
import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA;
import static com.android.internal.telephony.TelephonyProperties.PROPERTY_TEST_CSIM;
import android.os.AsyncResult;
import android.os.SystemProperties;
import android.util.Log;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.IccCardApplication.AppType;
import com.android.internal.telephony.IccFileHandler;
@@ -25,12 +27,14 @@ import com.android.internal.telephony.MccTable;
import com.android.internal.telephony.PhoneBase;
import com.android.internal.telephony.cdma.sms.UserData;
import com.android.internal.telephony.gsm.SIMRecords;
import android.os.AsyncResult;
import android.os.Message;
import android.os.SystemProperties;
import android.util.Log;
import java.util.Locale;
import com.android.internal.telephony.ims.IsimRecords;
import com.android.internal.telephony.ims.IsimUiccRecords;
import java.util.ArrayList;
import java.util.Locale;
import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA;
import static com.android.internal.telephony.TelephonyProperties.PROPERTY_TEST_CSIM;
/**
* {@hide}
@@ -46,130 +50,191 @@ public final class CdmaLteUiccRecords extends SIMRecords {
private String mHomeSystemId;
private String mHomeNetworkId;
private static final int EVENT_GET_PL_DONE = CSIM_EVENT_BASE;
private static final int EVENT_GET_CSIM_LI_DONE = CSIM_EVENT_BASE + 1;
private static final int EVENT_GET_CSIM_SPN_DONE = CSIM_EVENT_BASE + 2;
private static final int EVENT_GET_CSIM_MDN_DONE = CSIM_EVENT_BASE + 3;
private static final int EVENT_GET_CSIM_IMSIM_DONE = CSIM_EVENT_BASE + 4;
private static final int EVENT_GET_CSIM_CDMAHOME_DONE = CSIM_EVENT_BASE + 5;
private static final int EVENT_GET_CSIM_EPRL_DONE = CSIM_EVENT_BASE + 6;
private final IsimUiccRecords mIsimUiccRecords = new IsimUiccRecords();
public CdmaLteUiccRecords(PhoneBase p) {
super(p);
}
@Override
public void handleMessage(Message msg) {
AsyncResult ar;
byte data[];
// Refer to ETSI TS 102.221
private class EfPlLoaded implements IccRecordLoaded {
public String getEfName() {
return "EF_PL";
}
boolean isCsimRecordLoadResponse = false;
public void onRecordLoaded(AsyncResult ar) {
mEFpl = (byte[]) ar.result;
if (DBG) log("EF_PL=" + IccUtils.bytesToHexString(mEFpl));
}
}
try { switch (msg.what) {
case EVENT_GET_PL_DONE:
// Refer to ETSI TS.102.221
if (DBG) log("EF_GET_EF_PL_DONE");
isCsimRecordLoadResponse = true;
// Refer to C.S0065 5.2.26
private class EfCsimLiLoaded implements IccRecordLoaded {
public String getEfName() {
return "EF_CSIM_LI";
}
ar = (AsyncResult) msg.obj;
if (ar.exception != null) {
Log.e(LOG_TAG, "ar.exception = " + ar.exception);
break;
public void onRecordLoaded(AsyncResult ar) {
mEFli = (byte[]) ar.result;
// convert csim efli data to iso 639 format
for (int i = 0; i < mEFli.length; i+=2) {
switch(mEFli[i+1]) {
case 0x01: mEFli[i] = 'e'; mEFli[i+1] = 'n';break;
case 0x02: mEFli[i] = 'f'; mEFli[i+1] = 'r';break;
case 0x03: mEFli[i] = 'e'; mEFli[i+1] = 's';break;
case 0x04: mEFli[i] = 'j'; mEFli[i+1] = 'a';break;
case 0x05: mEFli[i] = 'k'; mEFli[i+1] = 'o';break;
case 0x06: mEFli[i] = 'z'; mEFli[i+1] = 'h';break;
case 0x07: mEFli[i] = 'h'; mEFli[i+1] = 'e';break;
default: mEFli[i] = ' '; mEFli[i+1] = ' ';
}
mEFpl = (byte[]) ar.result;
if (DBG) log("EF_PL=" + IccUtils.bytesToHexString(mEFpl));
break;
case EVENT_GET_CSIM_LI_DONE:
// Refer to C.S0065 5.2.26
if (DBG) log("EVENT_GET_CSIM_LI_DONE");
isCsimRecordLoadResponse = true;
ar = (AsyncResult) msg.obj;
if (ar.exception != null) {
Log.e(LOG_TAG, "ar.exception = " + ar.exception);
break;
}
mEFli = (byte[]) ar.result;
// convert csim efli data to iso 639 format
for (int i = 0; i < mEFli.length; i+=2) {
switch(mEFli[i+1]) {
case 0x01: mEFli[i] = 'e'; mEFli[i+1] = 'n';break;
case 0x02: mEFli[i] = 'f'; mEFli[i+1] = 'r';break;
case 0x03: mEFli[i] = 'e'; mEFli[i+1] = 's';break;
case 0x04: mEFli[i] = 'j'; mEFli[i+1] = 'a';break;
case 0x05: mEFli[i] = 'k'; mEFli[i+1] = 'o';break;
case 0x06: mEFli[i] = 'z'; mEFli[i+1] = 'h';break;
case 0x07: mEFli[i] = 'h'; mEFli[i+1] = 'e';break;
default: mEFli[i] = ' '; mEFli[i+1] = ' ';
}
}
if (DBG) log("EF_LI=" + IccUtils.bytesToHexString(mEFli));
break;
case EVENT_GET_CSIM_SPN_DONE:
// Refer to C.S0065 5.2.32
if (DBG) log("EVENT_GET_CSIM_SPN_DONE");
isCsimRecordLoadResponse = true;
ar = (AsyncResult) msg.obj;
if (ar.exception != null) {
Log.e(LOG_TAG, "ar.exception=" + ar.exception);
break;
}
onGetCSimSpnDone(ar);
break;
case EVENT_GET_CSIM_MDN_DONE:
if (DBG) log("EVENT_GET_CSIM_MDN_DONE");
isCsimRecordLoadResponse = true;
ar = (AsyncResult) msg.obj;
if (ar.exception != null) {
Log.e(LOG_TAG, "ar.exception=" + ar.exception);
break;
}
onGetCSimMdnDone(ar);
break;
case EVENT_GET_CSIM_IMSIM_DONE:
if (DBG) log("EVENT_GET_CSIM_IMSIM_DONE");
isCsimRecordLoadResponse = true;
ar = (AsyncResult) msg.obj;
if (ar.exception != null) {
Log.e(LOG_TAG, "ar.exception=" + ar.exception);
break;
}
onGetCSimImsimDone(ar);
break;
case EVENT_GET_CSIM_CDMAHOME_DONE:
if (DBG) log("EVENT_GET_CSIM_CDMAHOME_DONE");
isCsimRecordLoadResponse = true;
ar = (AsyncResult) msg.obj;
if (ar.exception != null) {
Log.e(LOG_TAG, "ar.exception=" + ar.exception);
break;
}
onGetCSimCdmaHomeDone(ar);
break;
case EVENT_GET_CSIM_EPRL_DONE:
if (DBG) log("EVENT_GET_CSIM_EPRL_DONE");
isCsimRecordLoadResponse = true;
ar = (AsyncResult) msg.obj;
if (ar.exception != null) {
Log.e(LOG_TAG, "ar.exception=" + ar.exception);
break;
}
onGetCSimEprlDone(ar);
break;
default:
super.handleMessage(msg);
}}catch (RuntimeException exc) {
Log.w(LOG_TAG, "Exception parsing SIM record", exc);
} finally {
if (isCsimRecordLoadResponse) {
onRecordLoaded();
}
if (DBG) log("EF_LI=" + IccUtils.bytesToHexString(mEFli));
}
}
// Refer to C.S0065 5.2.32
private class EfCsimSpnLoaded implements IccRecordLoaded {
public String getEfName() {
return "EF_CSIM_SPN";
}
public void onRecordLoaded(AsyncResult ar) {
byte[] data = (byte[]) ar.result;
if (DBG) log("CSIM_SPN=" +
IccUtils.bytesToHexString(data));
// C.S0065 for EF_SPN decoding
mCsimSpnDisplayCondition = ((0x01 & data[0]) != 0);
int encoding = data[1];
int language = data[2];
byte[] spnData = new byte[32];
System.arraycopy(data, 3, spnData, 0, (data.length < 32) ? data.length : 32);
int numBytes;
for (numBytes = 0; numBytes < spnData.length; numBytes++) {
if ((spnData[numBytes] & 0xFF) == 0xFF) break;
}
if (numBytes == 0) {
spn = "";
return;
}
try {
switch (encoding) {
case UserData.ENCODING_OCTET:
case UserData.ENCODING_LATIN:
spn = new String(spnData, 0, numBytes, "ISO-8859-1");
break;
case UserData.ENCODING_IA5:
case UserData.ENCODING_GSM_7BIT_ALPHABET:
case UserData.ENCODING_7BIT_ASCII:
spn = GsmAlphabet.gsm7BitPackedToString(spnData, 0, (numBytes*8)/7);
break;
case UserData.ENCODING_UNICODE_16:
spn = new String(spnData, 0, numBytes, "utf-16");
break;
default:
log("SPN encoding not supported");
}
} catch(Exception e) {
log("spn decode error: " + e);
}
if (DBG) log("spn=" + spn);
if (DBG) log("spnCondition=" + mCsimSpnDisplayCondition);
phone.setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, spn);
}
}
private class EfCsimMdnLoaded implements IccRecordLoaded {
public String getEfName() {
return "EF_CSIM_MDN";
}
public void onRecordLoaded(AsyncResult ar) {
byte[] data = (byte[]) ar.result;
if (DBG) log("CSIM_MDN=" + IccUtils.bytesToHexString(data));
int mdnDigitsNum = 0x0F & data[0];
mMdn = IccUtils.cdmaBcdToString(data, 1, mdnDigitsNum);
if (DBG) log("CSIM MDN=" + mMdn);
}
}
private class EfCsimImsimLoaded implements IccRecordLoaded {
public String getEfName() {
return "EF_CSIM_IMSIM";
}
public void onRecordLoaded(AsyncResult ar) {
byte[] data = (byte[]) ar.result;
if (DBG) log("CSIM_IMSIM=" + IccUtils.bytesToHexString(data));
// C.S0065 section 5.2.2 for IMSI_M encoding
// C.S0005 section 2.3.1 for MIN encoding in IMSI_M.
boolean provisioned = ((data[7] & 0x80) == 0x80);
if (provisioned) {
int first3digits = ((0x03 & data[2]) << 8) + (0xFF & data[1]);
int second3digits = (((0xFF & data[5]) << 8) | (0xFF & data[4])) >> 6;
int digit7 = 0x0F & (data[4] >> 2);
if (digit7 > 0x09) digit7 = 0;
int last3digits = ((0x03 & data[4]) << 8) | (0xFF & data[3]);
first3digits = adjstMinDigits(first3digits);
second3digits = adjstMinDigits(second3digits);
last3digits = adjstMinDigits(last3digits);
StringBuilder builder = new StringBuilder();
builder.append(String.format(Locale.US, "%03d", first3digits));
builder.append(String.format(Locale.US, "%03d", second3digits));
builder.append(String.format(Locale.US, "%d", digit7));
builder.append(String.format(Locale.US, "%03d", last3digits));
mMin = builder.toString();
if (DBG) log("min present=" + mMin);
} else {
if (DBG) log("min not present");
}
}
}
private class EfCsimCdmaHomeLoaded implements IccRecordLoaded {
public String getEfName() {
return "EF_CSIM_CDMAHOME";
}
public void onRecordLoaded(AsyncResult ar) {
// Per C.S0065 section 5.2.8
ArrayList<byte[]> dataList = (ArrayList<byte[]>) ar.result;
if (DBG) log("CSIM_CDMAHOME data size=" + dataList.size());
if (dataList.isEmpty()) {
return;
}
StringBuilder sidBuf = new StringBuilder();
StringBuilder nidBuf = new StringBuilder();
for (byte[] data : dataList) {
if (data.length == 5) {
int sid = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF);
int nid = ((data[3] & 0xFF) << 8) | (data[2] & 0xFF);
sidBuf.append(sid).append(',');
nidBuf.append(nid).append(',');
}
}
// remove trailing ","
sidBuf.setLength(sidBuf.length()-1);
nidBuf.setLength(nidBuf.length()-1);
mHomeSystemId = sidBuf.toString();
mHomeNetworkId = nidBuf.toString();
}
}
private class EfCsimEprlLoaded implements IccRecordLoaded {
public String getEfName() {
return "EF_CSIM_EPRL";
}
public void onRecordLoaded(AsyncResult ar) {
onGetCSimEprlDone(ar);
}
}
@@ -189,8 +254,8 @@ public final class CdmaLteUiccRecords extends SIMRecords {
@Override
protected void onAllRecordsLoaded() {
super.onAllRecordsLoaded();
setLocaleFromCsim();
super.onAllRecordsLoaded(); // broadcasts ICC state change to "LOADED"
}
@Override
@@ -207,112 +272,36 @@ public final class CdmaLteUiccRecords extends SIMRecords {
iccFh.loadEFTransparent(EF_AD, obtainMessage(EVENT_GET_AD_DONE));
recordsToLoad++;
iccFh.loadEFTransparent(EF_PL, obtainMessage(EVENT_GET_PL_DONE));
iccFh.loadEFTransparent(EF_PL,
obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfPlLoaded()));
recordsToLoad++;
iccFh.loadEFTransparent(EF_CSIM_LI, obtainMessage(EVENT_GET_CSIM_LI_DONE));
iccFh.loadEFTransparent(EF_CSIM_LI,
obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimLiLoaded()));
recordsToLoad++;
iccFh.loadEFTransparent(EF_CSIM_SPN, obtainMessage(EVENT_GET_CSIM_SPN_DONE));
iccFh.loadEFTransparent(EF_CSIM_SPN,
obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimSpnLoaded()));
recordsToLoad++;
iccFh.loadEFLinearFixed(EF_CSIM_MDN, 1, obtainMessage(EVENT_GET_CSIM_MDN_DONE));
iccFh.loadEFLinearFixed(EF_CSIM_MDN, 1,
obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimMdnLoaded()));
recordsToLoad++;
iccFh.loadEFTransparent(EF_CSIM_IMSIM, obtainMessage(EVENT_GET_CSIM_IMSIM_DONE));
iccFh.loadEFTransparent(EF_CSIM_IMSIM,
obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimImsimLoaded()));
recordsToLoad++;
iccFh.loadEFLinearFixedAll(EF_CSIM_CDMAHOME,
obtainMessage(EVENT_GET_CSIM_CDMAHOME_DONE));
obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimCdmaHomeLoaded()));
recordsToLoad++;
iccFh.loadEFTransparent(EF_CSIM_EPRL, obtainMessage(EVENT_GET_CSIM_EPRL_DONE));
iccFh.loadEFTransparent(EF_CSIM_EPRL,
obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimEprlLoaded()));
recordsToLoad++;
}
private void onGetCSimSpnDone(AsyncResult ar) {
byte[] data = (byte[]) ar.result;
if (DBG) log("CSIM_SPN=" +
IccUtils.bytesToHexString(data));
// C.S0065 for EF_SPN decoding
mCsimSpnDisplayCondition = ((0x01 & data[0]) != 0) ? true : false;
int encoding = data[1];
int language = data[2];
byte[] spnData = new byte[32];
System.arraycopy(data, 3, spnData, 0, (data.length < 32)?data.length:32);
int numBytes;
for (numBytes = 0; numBytes < spnData.length; numBytes++) {
if ((spnData[numBytes] & 0xFF) == 0xFF) break;
}
if (numBytes == 0) {
spn = "";
return;
}
try {
switch (encoding) {
case UserData.ENCODING_OCTET:
case UserData.ENCODING_LATIN:
spn = new String(spnData, 0, numBytes, "ISO-8859-1");
break;
case UserData.ENCODING_IA5:
case UserData.ENCODING_GSM_7BIT_ALPHABET:
case UserData.ENCODING_7BIT_ASCII:
spn = GsmAlphabet.gsm7BitPackedToString(spnData, 0, (numBytes*8)/7);
break;
case UserData.ENCODING_UNICODE_16:
spn = new String(spnData, 0, numBytes, "utf-16");
break;
default:
log("SPN encoding not supported");
}
} catch(Exception e) {
log("spn decode error: " + e);
}
if (DBG) log("spn=" + spn);
if (DBG) log("spnCondition=" + mCsimSpnDisplayCondition);
phone.setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, spn);
}
private void onGetCSimMdnDone(AsyncResult ar) {
byte[] data = (byte[]) ar.result;
if (DBG) log("CSIM_MDN=" + IccUtils.bytesToHexString(data));
int mdnDigitsNum = 0x0F & data[0];
mMdn = IccUtils.cdmaBcdToString(data, 1, mdnDigitsNum);
if (DBG) log("CSIM MDN=" + mMdn);
}
private void onGetCSimImsimDone(AsyncResult ar) {
byte[] data = (byte[]) ar.result;
if (DBG) log("CSIM_IMSIM=" + IccUtils.bytesToHexString(data));
// C.S0065 section 5.2.2 for IMSI_M encoding
// C.S0005 section 2.3.1 for MIN encoding in IMSI_M.
boolean provisioned = ((data[7] & 0x80) == 0x80);
if (provisioned) {
int first3digits = ((0x03 & data[2]) << 8) + (0xFF & data[1]);
int second3digits = (((0xFF & data[5]) << 8) | (0xFF & data[4])) >> 6;
int digit7 = 0x0F & (data[4] >> 2);
if (digit7 > 0x09) digit7 = 0;
int last3digits = ((0x03 & data[4]) << 8) | (0xFF & data[3]);
first3digits = adjstMinDigits(first3digits);
second3digits = adjstMinDigits(second3digits);
last3digits = adjstMinDigits(last3digits);
StringBuilder builder = new StringBuilder();
builder.append(String.format(Locale.US, "%03d", first3digits));
builder.append(String.format(Locale.US, "%03d", second3digits));
builder.append(String.format(Locale.US, "%d", digit7));
builder.append(String.format(Locale.US, "%03d", last3digits));
if (DBG) log("min present=" + builder.toString());
mMin = builder.toString();
} else {
if (DBG) log("min not present");
}
// load ISIM records
recordsToLoad += mIsimUiccRecords.fetchIsimRecords(iccFh, this);
}
private int adjstMinDigits (int digits) {
@@ -324,32 +313,6 @@ public final class CdmaLteUiccRecords extends SIMRecords {
return digits;
}
private void onGetCSimCdmaHomeDone(AsyncResult ar) {
// Per C.S0065 section 5.2.8
ArrayList<byte[]> dataList = (ArrayList<byte[]>) ar.result;
if (DBG) log("CSIM_CDMAHOME data size=" + dataList.size());
if (dataList.isEmpty()) {
return;
}
StringBuilder sidBuf = new StringBuilder();
StringBuilder nidBuf = new StringBuilder();
for (byte[] data : dataList) {
if (data.length == 5) {
int sid = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF);
int nid = ((data[3] & 0xFF) << 8) | (data[2] & 0xFF);
sidBuf.append(sid).append(",");
nidBuf.append(nid).append(",");
}
}
// remove trailing ","
sidBuf.setLength(sidBuf.length()-1);
nidBuf.setLength(nidBuf.length()-1);
mHomeSystemId = sidBuf.toString();
mHomeNetworkId = nidBuf.toString();
}
private void onGetCSimEprlDone(AsyncResult ar) {
// C.S0065 section 5.2.57 for EFeprl encoding
// C.S0016 section 3.5.5 for PRL format.
@@ -418,6 +381,11 @@ public final class CdmaLteUiccRecords extends SIMRecords {
if (DBG) Log.d(LOG_TAG, "[CSIM] " + s);
}
@Override
protected void loge(String s) {
if (DBG) Log.e(LOG_TAG, "[CSIM] " + s);
}
public String getMdn() {
return mMdn;
}
@@ -442,6 +410,11 @@ public final class CdmaLteUiccRecords extends SIMRecords {
return mCsimSpnDisplayCondition;
}
@Override
public IsimRecords getIsimRecords() {
return mIsimUiccRecords;
}
@Override
public boolean isProvisioned() {
// If UICC card has CSIM app, look for MDN and MIN field

View File

@@ -443,4 +443,8 @@ public final class RuimRecords extends IccRecords {
Log.d(LOG_TAG, "[RuimRecords] " + s);
}
@Override
protected void loge(String s) {
Log.e(LOG_TAG, "[RuimRecords] " + s);
}
}

View File

@@ -147,8 +147,6 @@ public class SIMRecords extends IccRecords {
private static final int EVENT_GET_CFIS_DONE = 32;
private static final int EVENT_GET_CSP_CPHS_DONE = 33;
protected static final int CSIM_EVENT_BASE = 100;
// Lookup table for carriers known to produce SIMs which incorrectly indicate MNC length.
private static final String[] MCCMNC_CODES_HAVING_3DIGITS_MNC = {
@@ -1089,6 +1087,9 @@ public class SIMRecords extends IccRecords {
handleEfCspData(data);
break;
default:
super.handleMessage(msg); // IccRecords handles generic record load responses
}}catch (RuntimeException exc) {
// I don't want these exceptions to be fatal
Log.w(LOG_TAG, "Exception parsing SIM record", exc);
@@ -1573,6 +1574,10 @@ public class SIMRecords extends IccRecords {
Log.d(LOG_TAG, "[SIMRecords] " + s);
}
protected void loge(String s) {
Log.e(LOG_TAG, "[SIMRecords] " + s);
}
/**
* Return true if "Restriction of menu options for manual PLMN selection"
* bit is set or EF_CSP data is unavailable, return false otherwise.

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony.ims;
/**
* {@hide}
*/
public interface IsimRecords {
/**
* Return the IMS private user identity (IMPI).
* Returns null if the IMPI hasn't been loaded or isn't present on the ISIM.
* @return the IMS private user identity string, or null if not available
*/
String getIsimImpi();
/**
* Return the IMS home network domain name.
* Returns null if the IMS domain hasn't been loaded or isn't present on the ISIM.
* @return the IMS home network domain name, or null if not available
*/
String getIsimDomain();
/**
* Return an array of IMS public user identities (IMPU).
* Returns null if the IMPU hasn't been loaded or isn't present on the ISIM.
* @return an array of IMS public user identity strings, or null if not available
*/
String[] getIsimImpu();
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony.ims;
import android.os.AsyncResult;
import android.os.Handler;
import android.util.Log;
import com.android.internal.telephony.IccFileHandler;
import com.android.internal.telephony.IccRecords;
import com.android.internal.telephony.gsm.SimTlv;
import java.nio.charset.Charset;
import java.util.ArrayList;
import static com.android.internal.telephony.IccConstants.EF_DOMAIN;
import static com.android.internal.telephony.IccConstants.EF_IMPI;
import static com.android.internal.telephony.IccConstants.EF_IMPU;
/**
* {@hide}
*/
public final class IsimUiccRecords implements IsimRecords {
protected static final String LOG_TAG = "GSM";
private static final boolean DBG = true;
private static final boolean DUMP_RECORDS = false; // Note: PII is logged when this is true
// ISIM EF records (see 3GPP TS 31.103)
private String mIsimImpi; // IMS private user identity
private String mIsimDomain; // IMS home network domain name
private String[] mIsimImpu; // IMS public user identity(s)
private static final int TAG_ISIM_VALUE = 0x80; // From 3GPP TS 31.103
private class EfIsimImpiLoaded implements IccRecords.IccRecordLoaded {
public String getEfName() {
return "EF_ISIM_IMPI";
}
public void onRecordLoaded(AsyncResult ar) {
byte[] data = (byte[]) ar.result;
mIsimImpi = isimTlvToString(data);
if (DUMP_RECORDS) log("EF_IMPI=" + mIsimImpi);
}
}
private class EfIsimImpuLoaded implements IccRecords.IccRecordLoaded {
public String getEfName() {
return "EF_ISIM_IMPU";
}
public void onRecordLoaded(AsyncResult ar) {
ArrayList<byte[]> impuList = (ArrayList<byte[]>) ar.result;
if (DBG) log("EF_IMPU record count: " + impuList.size());
mIsimImpu = new String[impuList.size()];
int i = 0;
for (byte[] identity : impuList) {
String impu = isimTlvToString(identity);
if (DUMP_RECORDS) log("EF_IMPU[" + i + "]=" + impu);
mIsimImpu[i++] = impu;
}
}
}
private class EfIsimDomainLoaded implements IccRecords.IccRecordLoaded {
public String getEfName() {
return "EF_ISIM_DOMAIN";
}
public void onRecordLoaded(AsyncResult ar) {
byte[] data = (byte[]) ar.result;
mIsimDomain = isimTlvToString(data);
if (DUMP_RECORDS) log("EF_DOMAIN=" + mIsimDomain);
}
}
/**
* Request the ISIM records to load.
* @param iccFh the IccFileHandler to load the records from
* @param h the Handler to which the response message will be sent
* @return the number of EF record requests that were added
*/
public int fetchIsimRecords(IccFileHandler iccFh, Handler h) {
iccFh.loadEFTransparent(EF_IMPI, h.obtainMessage(
IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimImpiLoaded()));
iccFh.loadEFLinearFixedAll(EF_IMPU, h.obtainMessage(
IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimImpuLoaded()));
iccFh.loadEFTransparent(EF_DOMAIN, h.obtainMessage(
IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimDomainLoaded()));
return 3; // number of EF record load requests
}
/**
* ISIM records for IMS are stored inside a Tag-Length-Value record as a UTF-8 string
* with tag value 0x80.
* @param record the byte array containing the IMS data string
* @return the decoded String value, or null if the record can't be decoded
*/
private static String isimTlvToString(byte[] record) {
SimTlv tlv = new SimTlv(record, 0, record.length);
do {
if (tlv.getTag() == TAG_ISIM_VALUE) {
return new String(tlv.getData(), Charset.forName("UTF-8"));
}
} while (tlv.nextObject());
Log.e(LOG_TAG, "[ISIM] can't find TLV tag in ISIM record, returning null");
return null;
}
void log(String s) {
if (DBG) Log.d(LOG_TAG, "[ISIM] " + s);
}
void loge(String s) {
if (DBG) Log.e(LOG_TAG, "[ISIM] " + s);
}
/**
* Return the IMS private user identity (IMPI).
* Returns null if the IMPI hasn't been loaded or isn't present on the ISIM.
* @return the IMS private user identity string, or null if not available
*/
public String getIsimImpi() {
return mIsimImpi;
}
/**
* Return the IMS home network domain name.
* Returns null if the IMS domain hasn't been loaded or isn't present on the ISIM.
* @return the IMS home network domain name, or null if not available
*/
public String getIsimDomain() {
return mIsimDomain;
}
/**
* Return an array of IMS public user identities (IMPU).
* Returns null if the IMPU hasn't been loaded or isn't present on the ISIM.
* @return an array of IMS public user identity strings, or null if not available
*/
public String[] getIsimImpu() {
return (mIsimImpu != null) ? mIsimImpu.clone() : null;
}
}

View File

@@ -186,7 +186,7 @@ class SipCommandInterface extends BaseCommands implements CommandsInterface {
public void setupDataCall(String radioTechnology, String profile,
String apn, String user, String password, String authType,
String protcol, Message result) {
String protocol, Message result) {
}
public void deactivateDataCall(int cid, int reason, Message result) {
@@ -403,4 +403,7 @@ class SipCommandInterface extends BaseCommands implements CommandsInterface {
public void changeIccPin2ForApp(String oldPin2, String newPin2, String aidPtr,
Message response) {
}
public void requestIsimAuthentication(String nonce, Message response) {
}
}

View File

@@ -1501,4 +1501,8 @@ public final class SimulatedCommands extends BaseCommands
Message response) {
unimplemented(response);
}
public void requestIsimAuthentication(String nonce, Message response) {
unimplemented(response);
}
}