diff options
14 files changed, 1191 insertions, 59 deletions
diff --git a/telephony/java/com/android/internal/telephony/CommandsInterface.java b/telephony/java/com/android/internal/telephony/CommandsInterface.java index 9b42dbed4e1a..33ead75e8008 100644 --- a/telephony/java/com/android/internal/telephony/CommandsInterface.java +++ b/telephony/java/com/android/internal/telephony/CommandsInterface.java @@ -157,6 +157,8 @@ public interface CommandsInterface { // GSM SMS fail cause for acknowledgeLastIncomingSMS. From TS 23.040, 9.2.3.22. static final int GSM_SMS_FAIL_CAUSE_MEMORY_CAPACITY_EXCEEDED = 0xD3; + static final int GSM_SMS_FAIL_CAUSE_USIM_APP_TOOLKIT_BUSY = 0xD4; + static final int GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR = 0xD5; static final int GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR = 0xFF; // CDMA SMS fail cause for acknowledgeLastIncomingCdmaSms. From TS N.S0005, 6.5.2.125. diff --git a/telephony/java/com/android/internal/telephony/IccIoResult.java b/telephony/java/com/android/internal/telephony/IccIoResult.java index a6e0ec365bb3..7043da53f0e0 100644 --- a/telephony/java/com/android/internal/telephony/IccIoResult.java +++ b/telephony/java/com/android/internal/telephony/IccIoResult.java @@ -21,8 +21,8 @@ package com.android.internal.telephony; */ public class IccIoResult { - int sw1; - int sw2; + public int sw1; + public int sw2; public byte[] payload; diff --git a/telephony/java/com/android/internal/telephony/IccRecords.java b/telephony/java/com/android/internal/telephony/IccRecords.java index 51ebd99fafeb..fc011c0b8668 100644 --- a/telephony/java/com/android/internal/telephony/IccRecords.java +++ b/telephony/java/com/android/internal/telephony/IccRecords.java @@ -22,6 +22,7 @@ import android.os.Message; import android.os.Registrant; import android.os.RegistrantList; +import com.android.internal.telephony.gsm.UsimServiceTable; import com.android.internal.telephony.ims.IsimRecords; /** @@ -362,4 +363,8 @@ public abstract class IccRecords extends Handler implements IccConstants { public IsimRecords getIsimRecords() { return null; } + + public UsimServiceTable getUsimServiceTable() { + return null; + } } diff --git a/telephony/java/com/android/internal/telephony/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java index ca04eb2daa95..9d189c1ad0b1 100644 --- a/telephony/java/com/android/internal/telephony/Phone.java +++ b/telephony/java/com/android/internal/telephony/Phone.java @@ -28,6 +28,7 @@ import android.telephony.ServiceState; import android.telephony.SignalStrength; import com.android.internal.telephony.DataConnection; +import com.android.internal.telephony.gsm.UsimServiceTable; import com.android.internal.telephony.ims.IsimRecords; import com.android.internal.telephony.test.SimulatedRadioControl; @@ -1765,4 +1766,10 @@ public interface Phone { * messages are waiting */ void setVoiceMessageWaiting(int line, int countWaiting); + + /** + * Gets the USIM service table from the UICC, if present and available. + * @return an interface to the UsimServiceTable record, or null if not available + */ + UsimServiceTable getUsimServiceTable(); } diff --git a/telephony/java/com/android/internal/telephony/PhoneBase.java b/telephony/java/com/android/internal/telephony/PhoneBase.java index 10121dd76a37..94f7a133578c 100644 --- a/telephony/java/com/android/internal/telephony/PhoneBase.java +++ b/telephony/java/com/android/internal/telephony/PhoneBase.java @@ -37,6 +37,7 @@ import android.text.TextUtils; import android.util.Log; import com.android.internal.R; +import com.android.internal.telephony.gsm.UsimServiceTable; import com.android.internal.telephony.ims.IsimRecords; import com.android.internal.telephony.test.SimulatedRadioControl; import com.android.internal.telephony.gsm.SIMRecords; @@ -1178,4 +1179,13 @@ public abstract class PhoneBase extends Handler implements Phone { public void setVoiceMessageWaiting(int line, int countWaiting) { mIccRecords.setVoiceMessageWaiting(line, countWaiting); } + + /** + * Gets the USIM service table from the UICC, if present and available. + * @return an interface to the UsimServiceTable record, or null if not available + */ + @Override + public UsimServiceTable getUsimServiceTable() { + return mIccRecords.getUsimServiceTable(); + } } diff --git a/telephony/java/com/android/internal/telephony/PhoneProxy.java b/telephony/java/com/android/internal/telephony/PhoneProxy.java index b497ec8faf43..60f364e8c7d4 100644 --- a/telephony/java/com/android/internal/telephony/PhoneProxy.java +++ b/telephony/java/com/android/internal/telephony/PhoneProxy.java @@ -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.gsm.UsimServiceTable; import com.android.internal.telephony.ims.IsimRecords; import com.android.internal.telephony.test.SimulatedRadioControl; @@ -853,4 +854,9 @@ public class PhoneProxy extends Handler implements Phone { public void setVoiceMessageWaiting(int line, int countWaiting) { mActivePhone.setVoiceMessageWaiting(line, countWaiting); } + + @Override + public UsimServiceTable getUsimServiceTable() { + return mActivePhone.getUsimServiceTable(); + } } diff --git a/telephony/java/com/android/internal/telephony/cat/CatService.java b/telephony/java/com/android/internal/telephony/cat/CatService.java index fb53686d1afe..542026498eaa 100644 --- a/telephony/java/com/android/internal/telephony/cat/CatService.java +++ b/telephony/java/com/android/internal/telephony/cat/CatService.java @@ -34,61 +34,6 @@ import com.android.internal.telephony.IccRecords; import java.io.ByteArrayOutputStream; import java.util.Locale; -/** - * Enumeration for representing the tag value of COMPREHENSION-TLV objects. If - * you want to get the actual value, call {@link #value() value} method. - * - * {@hide} - */ -enum ComprehensionTlvTag { - COMMAND_DETAILS(0x01), - DEVICE_IDENTITIES(0x02), - RESULT(0x03), - DURATION(0x04), - ALPHA_ID(0x05), - USSD_STRING(0x0a), - TEXT_STRING(0x0d), - TONE(0x0e), - ITEM(0x0f), - ITEM_ID(0x10), - RESPONSE_LENGTH(0x11), - FILE_LIST(0x12), - HELP_REQUEST(0x15), - DEFAULT_TEXT(0x17), - EVENT_LIST(0x19), - ICON_ID(0x1e), - ITEM_ICON_ID_LIST(0x1f), - IMMEDIATE_RESPONSE(0x2b), - LANGUAGE(0x2d), - URL(0x31), - BROWSER_TERMINATION_CAUSE(0x34), - TEXT_ATTRIBUTE(0x50); - - private int mValue; - - ComprehensionTlvTag(int value) { - mValue = value; - } - - /** - * Returns the actual value of this COMPREHENSION-TLV object. - * - * @return Actual tag value of this object - */ - public int value() { - return mValue; - } - - public static ComprehensionTlvTag fromInt(int value) { - for (ComprehensionTlvTag e : ComprehensionTlvTag.values()) { - if (e.mValue == value) { - return e; - } - } - return null; - } -} - class RilMessage { int mId; Object mData; diff --git a/telephony/java/com/android/internal/telephony/cat/ComprehensionTlvTag.java b/telephony/java/com/android/internal/telephony/cat/ComprehensionTlvTag.java new file mode 100644 index 000000000000..973dbc8132e2 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/cat/ComprehensionTlvTag.java @@ -0,0 +1,74 @@ +/* + * 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.cat; + +/** + * Enumeration for representing the tag value of COMPREHENSION-TLV objects. If + * you want to get the actual value, call {@link #value() value} method. + * + * {@hide} + */ +public enum ComprehensionTlvTag { + COMMAND_DETAILS(0x01), + DEVICE_IDENTITIES(0x02), + RESULT(0x03), + DURATION(0x04), + ALPHA_ID(0x05), + ADDRESS(0x06), + USSD_STRING(0x0a), + SMS_TPDU(0x0b), + TEXT_STRING(0x0d), + TONE(0x0e), + ITEM(0x0f), + ITEM_ID(0x10), + RESPONSE_LENGTH(0x11), + FILE_LIST(0x12), + HELP_REQUEST(0x15), + DEFAULT_TEXT(0x17), + EVENT_LIST(0x19), + ICON_ID(0x1e), + ITEM_ICON_ID_LIST(0x1f), + IMMEDIATE_RESPONSE(0x2b), + LANGUAGE(0x2d), + URL(0x31), + BROWSER_TERMINATION_CAUSE(0x34), + TEXT_ATTRIBUTE(0x50); + + private int mValue; + + ComprehensionTlvTag(int value) { + mValue = value; + } + + /** + * Returns the actual value of this COMPREHENSION-TLV object. + * + * @return Actual tag value of this object + */ + public int value() { + return mValue; + } + + public static ComprehensionTlvTag fromInt(int value) { + for (ComprehensionTlvTag e : ComprehensionTlvTag.values()) { + if (e.mValue == value) { + return e; + } + } + return null; + } +} diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java index c1553d831c9b..d29e4884cc1d 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java @@ -25,8 +25,9 @@ import android.os.Message; import android.os.SystemProperties; import android.provider.Telephony.Sms; import android.provider.Telephony.Sms.Intents; -import android.telephony.ServiceState; +import android.telephony.PhoneNumberUtils; import android.telephony.SmsCbMessage; +import android.telephony.SmsManager; import android.telephony.gsm.GsmCellLocation; import android.util.Log; @@ -41,7 +42,6 @@ import com.android.internal.telephony.SmsStorageMonitor; import com.android.internal.telephony.SmsUsageMonitor; import com.android.internal.telephony.TelephonyProperties; -import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -56,9 +56,16 @@ public final class GsmSMSDispatcher extends SMSDispatcher { /** New broadcast SMS */ private static final int EVENT_NEW_BROADCAST_SMS = 101; + /** Result of writing SM to UICC (when SMS-PP service is not available). */ + private static final int EVENT_WRITE_SMS_COMPLETE = 102; + + /** Handler for SMS-PP data download messages to UICC. */ + private final UsimDataDownloadHandler mDataDownloadHandler; + public GsmSMSDispatcher(PhoneBase phone, SmsStorageMonitor storageMonitor, SmsUsageMonitor usageMonitor) { super(phone, storageMonitor, usageMonitor); + mDataDownloadHandler = new UsimDataDownloadHandler(mCm); mCm.setOnNewGsmSms(this, EVENT_NEW_SMS, null); mCm.setOnSmsStatus(this, EVENT_NEW_SMS_STATUS_REPORT, null); mCm.setOnNewGsmBroadcastSms(this, EVENT_NEW_BROADCAST_SMS, null); @@ -93,6 +100,18 @@ public final class GsmSMSDispatcher extends SMSDispatcher { handleBroadcastSms((AsyncResult)msg.obj); break; + case EVENT_WRITE_SMS_COMPLETE: + AsyncResult ar = (AsyncResult) msg.obj; + if (ar.exception == null) { + Log.d(TAG, "Successfully wrote SMS-PP message to UICC"); + mCm.acknowledgeLastIncomingGsmSms(true, 0, null); + } else { + Log.d(TAG, "Failed to write SMS-PP message to UICC", ar.exception); + mCm.acknowledgeLastIncomingGsmSms(false, + CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR, null); + } + break; + default: super.handleMessage(msg); } @@ -154,6 +173,29 @@ public final class GsmSMSDispatcher extends SMSDispatcher { return Intents.RESULT_SMS_HANDLED; } + // Send SMS-PP data download messages to UICC. See 3GPP TS 31.111 section 7.1.1. + if (sms.isUsimDataDownload()) { + UsimServiceTable ust = mPhone.getUsimServiceTable(); + // If we receive an SMS-PP message before the UsimServiceTable has been loaded, + // assume that the data download service is not present. This is very unlikely to + // happen because the IMS connection will not be established until after the ISIM + // records have been loaded, after the USIM service table has been loaded. + if (ust != null && ust.isAvailable( + UsimServiceTable.UsimService.DATA_DL_VIA_SMS_PP)) { + Log.d(TAG, "Received SMS-PP data download, sending to UICC."); + return mDataDownloadHandler.startDataDownload(sms); + } else { + Log.d(TAG, "DATA_DL_VIA_SMS_PP service not available, storing message to UICC."); + String smsc = IccUtils.bytesToHexString( + PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength( + sms.getServiceCenterAddress())); + mCm.writeSmsToSim(SmsManager.STATUS_ON_ICC_UNREAD, smsc, + IccUtils.bytesToHexString(sms.getPdu()), + obtainMessage(EVENT_WRITE_SMS_COMPLETE)); + return Activity.RESULT_OK; // acknowledge after response from write to USIM + } + } + if (mSmsReceiveDisabled) { // Device doesn't support SMS service, Log.d(TAG, "Received short message on device which doesn't support " diff --git a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java index 8e965a3413db..495b5bc14a27 100755 --- a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java +++ b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java @@ -248,6 +248,7 @@ public class SIMRecords extends IccRecords { return msisdn; } + @Override public UsimServiceTable getUsimServiceTable() { return mUsimServiceTable; } diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java index 2da9642d27e0..677923f44756 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java @@ -769,6 +769,14 @@ public class SmsMessage extends SmsMessageBase { return protocolIdentifier; } + /** + * Returns the TP-Data-Coding-Scheme byte, for acknowledgement of SMS-PP download messages. + * @return the TP-DCS field of the SMS header + */ + int getDataCodingScheme() { + return dataCodingScheme; + } + /** {@inheritDoc} */ @Override public boolean isReplace() { @@ -1129,4 +1137,14 @@ public class SmsMessage extends SmsMessageBase { return messageClass; } + /** + * Returns true if this is a (U)SIM data download type SM. + * See 3GPP TS 31.111 section 9.1 and TS 23.040 section 9.2.3.9. + * + * @return true if this is a USIM data download message; false otherwise + */ + boolean isUsimDataDownload() { + return messageClass == MessageClass.CLASS_2 && + (protocolIdentifier == 0x7f || protocolIdentifier == 0x7c); + } } diff --git a/telephony/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java b/telephony/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java new file mode 100644 index 000000000000..f47ff1b03012 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java @@ -0,0 +1,267 @@ +/* + * 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.gsm; + +import android.app.Activity; +import android.os.AsyncResult; +import android.os.Handler; +import android.os.Message; +import android.provider.Telephony.Sms.Intents; +import android.util.Log; + +import com.android.internal.telephony.CommandsInterface; +import com.android.internal.telephony.IccIoResult; +import com.android.internal.telephony.IccUtils; +import com.android.internal.telephony.cat.ComprehensionTlvTag; + +/** + * Handler for SMS-PP data download messages. + * See 3GPP TS 31.111 section 7.1.1 + */ +public class UsimDataDownloadHandler extends Handler { + private static final String TAG = "UsimDataDownloadHandler"; + + /** BER-TLV tag for SMS-PP download. TS 31.111 section 9.1. */ + private static final int BER_SMS_PP_DOWNLOAD_TAG = 0xd1; + + /** Device identity value for UICC (destination). */ + private static final int DEV_ID_UICC = 0x81; + + /** Device identity value for network (source). */ + private static final int DEV_ID_NETWORK = 0x83; + + /** Message containing new SMS-PP message to process. */ + private static final int EVENT_START_DATA_DOWNLOAD = 1; + + /** Response to SMS-PP download envelope command. */ + private static final int EVENT_SEND_ENVELOPE_RESPONSE = 2; + + private final CommandsInterface mCI; + + public UsimDataDownloadHandler(CommandsInterface commandsInterface) { + mCI = commandsInterface; + } + + /** + * Start an SMS-PP data download for the specified message. Can be called from a different + * thread than this Handler is running on. + * + * @param smsMessage the message to process + * @return Activity.RESULT_OK on success; Intents.RESULT_SMS_GENERIC_ERROR on failure + */ + public int startDataDownload(SmsMessage smsMessage) { + if (sendMessage(obtainMessage(EVENT_START_DATA_DOWNLOAD, smsMessage))) { + return Activity.RESULT_OK; // we will send SMS ACK/ERROR based on UICC response + } else { + Log.e(TAG, "startDataDownload failed to send message to start data download."); + return Intents.RESULT_SMS_GENERIC_ERROR; + } + } + + private void handleDataDownload(SmsMessage smsMessage) { + int dcs = smsMessage.getDataCodingScheme(); + int pid = smsMessage.getProtocolIdentifier(); + byte[] pdu = smsMessage.getPdu(); // includes SC address + + int scAddressLength = pdu[0] & 0xff; + int tpduIndex = scAddressLength + 1; // start of TPDU + int tpduLength = pdu.length - tpduIndex; + + int bodyLength = getEnvelopeBodyLength(scAddressLength, tpduLength); + + // Add 1 byte for SMS-PP download tag and 1-2 bytes for BER-TLV length. + // See ETSI TS 102 223 Annex C for encoding of length and tags. + int totalLength = bodyLength + 1 + (bodyLength > 127 ? 2 : 1); + + byte[] envelope = new byte[totalLength]; + int index = 0; + + // SMS-PP download tag and length (assumed to be < 256 bytes). + envelope[index++] = (byte) BER_SMS_PP_DOWNLOAD_TAG; + if (bodyLength > 127) { + envelope[index++] = (byte) 0x81; // length 128-255 encoded as 0x81 + length + } + envelope[index++] = (byte) bodyLength; + + // Device identities TLV + envelope[index++] = (byte) (0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value()); + envelope[index++] = (byte) 2; + envelope[index++] = (byte) DEV_ID_NETWORK; + envelope[index++] = (byte) DEV_ID_UICC; + + // Address TLV (if present). Encoded length is assumed to be < 127 bytes. + if (scAddressLength != 0) { + envelope[index++] = (byte) ComprehensionTlvTag.ADDRESS.value(); + envelope[index++] = (byte) scAddressLength; + System.arraycopy(pdu, 1, envelope, index, scAddressLength); + index += scAddressLength; + } + + // SMS TPDU TLV. Length is assumed to be < 256 bytes. + envelope[index++] = (byte) (0x80 | ComprehensionTlvTag.SMS_TPDU.value()); + if (tpduLength > 127) { + envelope[index++] = (byte) 0x81; // length 128-255 encoded as 0x81 + length + } + envelope[index++] = (byte) tpduLength; + System.arraycopy(pdu, tpduIndex, envelope, index, tpduLength); + index += tpduLength; + + // Verify that we calculated the payload size correctly. + if (index != envelope.length) { + Log.e(TAG, "startDataDownload() calculated incorrect envelope length, aborting."); + acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR); + return; + } + + String encodedEnvelope = IccUtils.bytesToHexString(envelope); + mCI.sendEnvelopeWithStatus(encodedEnvelope, obtainMessage( + EVENT_SEND_ENVELOPE_RESPONSE, new int[]{ dcs, pid })); + } + + /** + * Return the size in bytes of the envelope to send to the UICC, excluding the + * SMS-PP download tag byte and length byte(s). If the size returned is <= 127, + * the BER-TLV length will be encoded in 1 byte, otherwise 2 bytes are required. + * + * @param scAddressLength the length of the SMSC address, or zero if not present + * @param tpduLength the length of the TPDU from the SMS-PP message + * @return the number of bytes to allocate for the envelope command + */ + private static int getEnvelopeBodyLength(int scAddressLength, int tpduLength) { + // Add 4 bytes for device identities TLV + 1 byte for SMS TPDU tag byte + int length = tpduLength + 5; + // Add 1 byte for TPDU length, or 2 bytes if length > 127 + length += (tpduLength > 127 ? 2 : 1); + // Add length of address tag, if present (+ 2 bytes for tag and length) + if (scAddressLength != 0) { + length = length + 2 + scAddressLength; + } + return length; + } + + /** + * Handle the response to the ENVELOPE command. + * @param response UICC response encoded as hexadecimal digits. First two bytes are the + * UICC SW1 and SW2 status bytes. + */ + private void sendSmsAckForEnvelopeResponse(IccIoResult response, int dcs, int pid) { + int sw1 = response.sw1; + int sw2 = response.sw2; + + boolean success; + if ((sw1 == 0x90 && sw2 == 0x00) || sw1 == 0x91) { + Log.d(TAG, "USIM data download succeeded: " + response.toString()); + success = true; + } else if (sw1 == 0x93 && sw2 == 0x00) { + Log.e(TAG, "USIM data download failed: Toolkit busy"); + acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_APP_TOOLKIT_BUSY); + return; + } else if (sw1 == 0x62 || sw1 == 0x63) { + Log.e(TAG, "USIM data download failed: " + response.toString()); + success = false; + } else { + Log.e(TAG, "Unexpected SW1/SW2 response from UICC: " + response.toString()); + success = false; + } + + byte[] responseBytes = response.payload; + if (responseBytes == null || responseBytes.length == 0) { + if (success) { + mCI.acknowledgeLastIncomingGsmSms(true, 0, null); + } else { + acknowledgeSmsWithError( + CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR); + } + return; + } + + byte[] smsAckPdu; + int index = 0; + if (success) { + smsAckPdu = new byte[responseBytes.length + 5]; + smsAckPdu[index++] = 0x00; // TP-MTI, TP-UDHI + smsAckPdu[index++] = 0x07; // TP-PI: TP-PID, TP-DCS, TP-UDL present + } else { + smsAckPdu = new byte[responseBytes.length + 6]; + smsAckPdu[index++] = 0x00; // TP-MTI, TP-UDHI + smsAckPdu[index++] = (byte) + CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR; // TP-FCS + smsAckPdu[index++] = 0x07; // TP-PI: TP-PID, TP-DCS, TP-UDL present + } + + smsAckPdu[index++] = (byte) pid; + smsAckPdu[index++] = (byte) dcs; + + if (is7bitDcs(dcs)) { + int septetCount = responseBytes.length * 8 / 7; + smsAckPdu[index++] = (byte) septetCount; + } else { + smsAckPdu[index++] = (byte) responseBytes.length; + } + + System.arraycopy(responseBytes, 0, smsAckPdu, index, responseBytes.length); + + mCI.acknowledgeIncomingGsmSmsWithPdu(success, + IccUtils.bytesToHexString(smsAckPdu), null); + } + + private void acknowledgeSmsWithError(int cause) { + mCI.acknowledgeLastIncomingGsmSms(false, cause, null); + } + + /** + * Returns whether the DCS is 7 bit. If so, set TP-UDL to the septet count of TP-UD; + * otherwise, set TP-UDL to the octet count of TP-UD. + * @param dcs the TP-Data-Coding-Scheme field from the original download SMS + * @return true if the DCS specifies 7 bit encoding; false otherwise + */ + private static boolean is7bitDcs(int dcs) { + // See 3GPP TS 23.038 section 4 + return ((dcs & 0x8C) == 0x00) || ((dcs & 0xF4) == 0xF0); + } + + /** + * Handle UICC envelope response and send SMS acknowledgement. + * + * @param msg the message to handle + */ + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_START_DATA_DOWNLOAD: + handleDataDownload((SmsMessage) msg.obj); + break; + + case EVENT_SEND_ENVELOPE_RESPONSE: + AsyncResult ar = (AsyncResult) msg.obj; + + if (ar.exception != null) { + Log.e(TAG, "UICC Send Envelope failure, exception: " + ar.exception); + acknowledgeSmsWithError( + CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR); + return; + } + + int[] dcsPid = (int[]) ar.userObj; + sendSmsAckForEnvelopeResponse((IccIoResult) ar.result, dcsPid[0], dcsPid[1]); + break; + + default: + Log.e(TAG, "Ignoring unexpected message, what=" + msg.what); + } + } +} diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java new file mode 100644 index 000000000000..7e0d3c47e4c5 --- /dev/null +++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java @@ -0,0 +1,611 @@ +/* + * 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.gsm; + +import android.content.Context; +import android.os.AsyncResult; +import android.os.Message; +import android.os.SystemClock; +import android.util.Log; + +import com.android.internal.telephony.BaseCommands; +import com.android.internal.telephony.IccIoResult; +import com.android.internal.telephony.UUSInfo; + +import junit.framework.Assert; + +/** + * Dummy BaseCommands for UsimDataDownloadTest. Only implements UICC envelope and + * SMS acknowledgement commands. + */ +class UsimDataDownloadCommands extends BaseCommands { + private static final String TAG = "UsimDataDownloadCommands"; + + private boolean mExpectingAcknowledgeGsmSms; // true if expecting ack GSM SMS + private boolean mExpectingAcknowledgeGsmSmsSuccess; // true if expecting ack SMS success + private int mExpectingAcknowledgeGsmSmsFailureCause; // expecting ack SMS failure cause + private String mExpectingAcknowledgeGsmSmsPdu; // expecting ack SMS PDU + + private boolean mExpectingSendEnvelope; // true to expect a send envelope command + private String mExpectingSendEnvelopeContents; // expected string for send envelope + private int mExpectingSendEnvelopeResponseSw1; // SW1/SW2 response status + private int mExpectingSendEnvelopeResponseSw2; // SW1/SW2 response status + private String mExpectingSendEnvelopeResponse; // Response string for Send Envelope + + UsimDataDownloadCommands(Context context) { + super(context); + } + + /** + * Expect a call to acknowledgeLastIncomingGsmSms with success flag and failure cause. + * @param success true if expecting success; false if expecting failure + * @param cause the failure cause, if success is false + */ + synchronized void expectAcknowledgeGsmSms(boolean success, int cause) { + Assert.assertFalse("expectAcknowledgeGsmSms called twice", mExpectingAcknowledgeGsmSms); + mExpectingAcknowledgeGsmSms = true; + mExpectingAcknowledgeGsmSmsSuccess = success; + mExpectingAcknowledgeGsmSmsFailureCause = cause; + } + + /** + * Expect a call to acknowledgeLastIncomingGsmSmsWithPdu with success flag and PDU. + * @param success true if expecting success; false if expecting failure + * @param ackPdu the acknowledgement PDU to expect + */ + synchronized void expectAcknowledgeGsmSmsWithPdu(boolean success, String ackPdu) { + Assert.assertFalse("expectAcknowledgeGsmSms called twice", mExpectingAcknowledgeGsmSms); + mExpectingAcknowledgeGsmSms = true; + mExpectingAcknowledgeGsmSmsSuccess = success; + mExpectingAcknowledgeGsmSmsPdu = ackPdu; + } + + /** + * Expect a call to sendEnvelopeWithStatus(). + * @param contents expected envelope contents to send + * @param sw1 simulated SW1 status to return + * @param sw2 simulated SW2 status to return + * @param response simulated envelope response to return + */ + synchronized void expectSendEnvelope(String contents, int sw1, int sw2, String response) { + Assert.assertFalse("expectSendEnvelope called twice", mExpectingSendEnvelope); + mExpectingSendEnvelope = true; + mExpectingSendEnvelopeContents = contents; + mExpectingSendEnvelopeResponseSw1 = sw1; + mExpectingSendEnvelopeResponseSw2 = sw2; + mExpectingSendEnvelopeResponse = response; + } + + synchronized void assertExpectedMethodsCalled() { + long stopTime = SystemClock.elapsedRealtime() + 5000; + while ((mExpectingAcknowledgeGsmSms || mExpectingSendEnvelope) + && SystemClock.elapsedRealtime() < stopTime) { + try { + wait(); + } catch (InterruptedException ignored) {} + } + Assert.assertFalse("expecting SMS acknowledge call", mExpectingAcknowledgeGsmSms); + Assert.assertFalse("expecting send envelope call", mExpectingSendEnvelope); + } + + @Override + public synchronized void acknowledgeLastIncomingGsmSms(boolean success, int cause, + Message response) { + Log.d(TAG, "acknowledgeLastIncomingGsmSms: success=" + success + ", cause=" + cause); + Assert.assertTrue("unexpected call to acknowledge SMS", mExpectingAcknowledgeGsmSms); + Assert.assertEquals(mExpectingAcknowledgeGsmSmsSuccess, success); + Assert.assertEquals(mExpectingAcknowledgeGsmSmsFailureCause, cause); + mExpectingAcknowledgeGsmSms = false; + if (response != null) { + AsyncResult.forMessage(response); + response.sendToTarget(); + } + notifyAll(); // wake up assertExpectedMethodsCalled() + } + + @Override + public synchronized void acknowledgeIncomingGsmSmsWithPdu(boolean success, String ackPdu, + Message response) { + Log.d(TAG, "acknowledgeLastIncomingGsmSmsWithPdu: success=" + success + + ", ackPDU= " + ackPdu); + Assert.assertTrue("unexpected call to acknowledge SMS", mExpectingAcknowledgeGsmSms); + Assert.assertEquals(mExpectingAcknowledgeGsmSmsSuccess, success); + Assert.assertEquals(mExpectingAcknowledgeGsmSmsPdu, ackPdu); + mExpectingAcknowledgeGsmSms = false; + if (response != null) { + AsyncResult.forMessage(response); + response.sendToTarget(); + } + notifyAll(); // wake up assertExpectedMethodsCalled() + } + + @Override + public synchronized void sendEnvelopeWithStatus(String contents, Message response) { + // Add spaces between hex bytes for readability + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < contents.length(); i += 2) { + builder.append(contents.charAt(i)).append(contents.charAt(i+1)).append(' '); + } + Log.d(TAG, "sendEnvelopeWithStatus: " + builder.toString()); + + Assert.assertTrue("unexpected call to send envelope", mExpectingSendEnvelope); + Assert.assertEquals(mExpectingSendEnvelopeContents, contents); + mExpectingSendEnvelope = false; + + IccIoResult result = new IccIoResult(mExpectingSendEnvelopeResponseSw1, + mExpectingSendEnvelopeResponseSw2, mExpectingSendEnvelopeResponse); + + if (response != null) { + AsyncResult.forMessage(response, result, null); + response.sendToTarget(); + } + notifyAll(); // wake up assertExpectedMethodsCalled() + } + + @Override + public void setSuppServiceNotifications(boolean enable, Message result) { + } + + @Override + public void supplyIccPin(String pin, Message result) { + } + + @Override + public void supplyIccPinForApp(String pin, String aid, Message result) { + } + + @Override + public void supplyIccPuk(String puk, String newPin, Message result) { + } + + @Override + public void supplyIccPukForApp(String puk, String newPin, String aid, Message result) { + } + + @Override + public void supplyIccPin2(String pin2, Message result) { + } + + @Override + public void supplyIccPin2ForApp(String pin2, String aid, Message result) { + } + + @Override + public void supplyIccPuk2(String puk2, String newPin2, Message result) { + } + + @Override + public void supplyIccPuk2ForApp(String puk2, String newPin2, String aid, Message result) { + } + + @Override + public void changeIccPin(String oldPin, String newPin, Message result) { + } + + @Override + public void changeIccPinForApp(String oldPin, String newPin, String aidPtr, Message result) { + } + + @Override + public void changeIccPin2(String oldPin2, String newPin2, Message result) { + } + + @Override + public void changeIccPin2ForApp(String oldPin2, String newPin2, String aidPtr, Message result) { + } + + @Override + public void changeBarringPassword(String facility, String oldPwd, String newPwd, + Message result) { + } + + @Override + public void supplyNetworkDepersonalization(String netpin, Message result) { + } + + @Override + public void getCurrentCalls(Message result) { + } + + @Override + public void getPDPContextList(Message result) { + } + + @Override + public void getDataCallList(Message result) { + } + + @Override + public void dial(String address, int clirMode, Message result) { + } + + @Override + public void dial(String address, int clirMode, UUSInfo uusInfo, Message result) { + } + + @Override + public void getIMSI(Message result) { + } + + @Override + public void getIMEI(Message result) { + } + + @Override + public void getIMEISV(Message result) { + } + + @Override + public void hangupConnection(int gsmIndex, Message result) { + } + + @Override + public void hangupWaitingOrBackground(Message result) { + } + + @Override + public void hangupForegroundResumeBackground(Message result) { + } + + @Override + public void switchWaitingOrHoldingAndActive(Message result) { + } + + @Override + public void conference(Message result) { + } + + @Override + public void setPreferredVoicePrivacy(boolean enable, Message result) { + } + + @Override + public void getPreferredVoicePrivacy(Message result) { + } + + @Override + public void separateConnection(int gsmIndex, Message result) { + } + + @Override + public void acceptCall(Message result) { + } + + @Override + public void rejectCall(Message result) { + } + + @Override + public void explicitCallTransfer(Message result) { + } + + @Override + public void getLastCallFailCause(Message result) { + } + + @Override + public void getLastPdpFailCause(Message result) { + } + + @Override + public void getLastDataCallFailCause(Message result) { + } + + @Override + public void setMute(boolean enableMute, Message response) { + } + + @Override + public void getMute(Message response) { + } + + @Override + public void getSignalStrength(Message response) { + } + + @Override + public void getVoiceRegistrationState(Message response) { + } + + @Override + public void getDataRegistrationState(Message response) { + } + + @Override + public void getOperator(Message response) { + } + + @Override + public void sendDtmf(char c, Message result) { + } + + @Override + public void startDtmf(char c, Message result) { + } + + @Override + public void stopDtmf(Message result) { + } + + @Override + public void sendBurstDtmf(String dtmfString, int on, int off, Message result) { + } + + @Override + public void sendSMS(String smscPDU, String pdu, Message response) { + } + + @Override + public void sendCdmaSms(byte[] pdu, Message response) { + } + + @Override + public void deleteSmsOnSim(int index, Message response) { + } + + @Override + public void deleteSmsOnRuim(int index, Message response) { + } + + @Override + public void writeSmsToSim(int status, String smsc, String pdu, Message response) { + } + + @Override + public void writeSmsToRuim(int status, String pdu, Message response) { + } + + @Override + public void setRadioPower(boolean on, Message response) { + } + + @Override + public void acknowledgeLastIncomingCdmaSms(boolean success, int cause, Message response) { + } + + @Override + public void iccIO(int command, int fileid, String path, int p1, int p2, int p3, String data, + String pin2, Message response) { + } + + @Override + public void queryCLIP(Message response) { + } + + @Override + public void getCLIR(Message response) { + } + + @Override + public void setCLIR(int clirMode, Message response) { + } + + @Override + public void queryCallWaiting(int serviceClass, Message response) { + } + + @Override + public void setCallWaiting(boolean enable, int serviceClass, Message response) { + } + + @Override + public void setCallForward(int action, int cfReason, int serviceClass, String number, + int timeSeconds, Message response) { + } + + @Override + public void queryCallForwardStatus(int cfReason, int serviceClass, String number, + Message response) { + } + + @Override + public void setNetworkSelectionModeAutomatic(Message response) { + } + + @Override + public void setNetworkSelectionModeManual(String operatorNumeric, Message response) { + } + + @Override + public void getNetworkSelectionMode(Message response) { + } + + @Override + public void getAvailableNetworks(Message response) { + } + + @Override + public void getBasebandVersion(Message response) { + } + + @Override + public void queryFacilityLock(String facility, String password, int serviceClass, + Message response) { + } + + @Override + public void queryFacilityLockForApp(String facility, String password, int serviceClass, + String appId, Message response) { + } + + @Override + public void setFacilityLock(String facility, boolean lockState, String password, + int serviceClass, Message response) { + } + + @Override + public void setFacilityLockForApp(String facility, boolean lockState, String password, + int serviceClass, String appId, Message response) { + } + + @Override + public void sendUSSD(String ussdString, Message response) { + } + + @Override + public void cancelPendingUssd(Message response) { + } + + @Override + public void resetRadio(Message result) { + } + + @Override + public void setBandMode(int bandMode, Message response) { + } + + @Override + public void queryAvailableBandMode(Message response) { + } + + @Override + public void setPreferredNetworkType(int networkType, Message response) { + } + + @Override + public void getPreferredNetworkType(Message response) { + } + + @Override + public void getNeighboringCids(Message response) { + } + + @Override + public void setLocationUpdates(boolean enable, Message response) { + } + + @Override + public void getSmscAddress(Message result) { + } + + @Override + public void setSmscAddress(String address, Message result) { + } + + @Override + public void reportSmsMemoryStatus(boolean available, Message result) { + } + + @Override + public void reportStkServiceIsRunning(Message result) { + } + + @Override + public void invokeOemRilRequestRaw(byte[] data, Message response) { + } + + @Override + public void invokeOemRilRequestStrings(String[] strings, Message response) { + } + + @Override + public void sendTerminalResponse(String contents, Message response) { + } + + @Override + public void sendEnvelope(String contents, Message response) { + } + + @Override + public void handleCallSetupRequestFromSim(boolean accept, Message response) { + } + + @Override + public void setGsmBroadcastActivation(boolean activate, Message result) { + } + + @Override + public void setGsmBroadcastConfig(SmsBroadcastConfigInfo[] config, Message response) { + } + + @Override + public void getGsmBroadcastConfig(Message response) { + } + + @Override + public void getDeviceIdentity(Message response) { + } + + @Override + public void getCDMASubscription(Message response) { + } + + @Override + public void sendCDMAFeatureCode(String FeatureCode, Message response) { + } + + @Override + public void setPhoneType(int phoneType) { + } + + @Override + public void queryCdmaRoamingPreference(Message response) { + } + + @Override + public void setCdmaRoamingPreference(int cdmaRoamingType, Message response) { + } + + @Override + public void setCdmaSubscriptionSource(int cdmaSubscriptionType, Message response) { + } + + @Override + public void getCdmaSubscriptionSource(Message response) { + } + + @Override + public void setTTYMode(int ttyMode, Message response) { + } + + @Override + public void queryTTYMode(Message response) { + } + + @Override + public void setupDataCall(String radioTechnology, String profile, String apn, String user, + String password, String authType, String protocol, Message result) { + } + + @Override + public void deactivateDataCall(int cid, int reason, Message result) { + } + + @Override + public void setCdmaBroadcastActivation(boolean activate, Message result) { + } + + @Override + public void setCdmaBroadcastConfig(int[] configValuesArray, Message result) { + } + + @Override + public void getCdmaBroadcastConfig(Message result) { + } + + @Override + public void exitEmergencyCallbackMode(Message response) { + } + + @Override + public void getIccCardStatus(Message result) { + } + + @Override + public void requestIsimAuthentication(String nonce, Message response) { + } +} diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadTest.java new file mode 100644 index 000000000000..6c8ba5e1e23d --- /dev/null +++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadTest.java @@ -0,0 +1,144 @@ +/* + * 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.gsm; + +import android.os.HandlerThread; +import android.test.AndroidTestCase; +import android.util.Log; + +import java.nio.charset.Charset; + +/** + * Test SMS-PP data download to UICC. + * Uses test messages from 3GPP TS 31.124 section 27.22.5. + */ +public class UsimDataDownloadTest extends AndroidTestCase { + private static final String TAG = "UsimDataDownloadTest"; + + class TestHandlerThread extends HandlerThread { + private UsimDataDownloadHandler mHandler; + + TestHandlerThread() { + super("TestHandlerThread"); + } + + @Override + protected void onLooperPrepared() { + synchronized (this) { + mHandler = new UsimDataDownloadHandler(mCm); + notifyAll(); + } + } + + UsimDataDownloadHandler getHandler() { + synchronized (this) { + while (mHandler == null) { + try { + wait(); + } catch (InterruptedException ignored) {} + } + return mHandler; + } + } + } + + private UsimDataDownloadCommands mCm; + private TestHandlerThread mHandlerThread; + UsimDataDownloadHandler mHandler; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mCm = new UsimDataDownloadCommands(mContext); + mHandlerThread = new TestHandlerThread(); + mHandlerThread.start(); + mHandler = mHandlerThread.getHandler(); + Log.d(TAG, "mHandler is constructed"); + } + + @Override + protected void tearDown() throws Exception { + mHandlerThread.quit(); + super.tearDown(); + } + + // SMS-PP Message 3.1.1 + private static final byte[] SMS_PP_MESSAGE_3_1_1 = { + // Service center address + 0x09, (byte) 0x91, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, (byte) 0xf8, + + 0x04, 0x04, (byte) 0x91, 0x21, 0x43, 0x7f, 0x16, (byte) 0x89, 0x10, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x0d, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x20, 0x31 + }; + + // SMS-PP Download Envelope 3.1.1 + private static final String SMS_PP_ENVELOPE_3_1_1 = "d12d8202838106099111223344556677f88b1c04" + + "049121437f16891010000000000d546573744d6573736167652031"; + + // SMS-PP Message 3.1.5 + private static final byte[] SMS_PP_MESSAGE_3_1_5 = { + // Service center address + 0x09, (byte) 0x91, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, (byte) 0xf8, + + 0x44, 0x04, (byte) 0x91, 0x21, 0x43, 0x7f, (byte) 0xf6, (byte) 0x89, 0x10, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x1e, 0x02, 0x70, 0x00, 0x00, 0x19, 0x00, 0x0d, 0x00, 0x00, + 0x00, 0x00, (byte) 0xbf, (byte) 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + (byte) 0xdc, (byte) 0xdc, (byte) 0xdc, (byte) 0xdc, (byte) 0xdc, (byte) 0xdc, + (byte) 0xdc, (byte) 0xdc, (byte) 0xdc, (byte) 0xdc + }; + + // SMS-PP Download Envelope 3.1.5 + private static final String SMS_PP_ENVELOPE_3_1_5 = "d13e8202838106099111223344556677f88b2d44" + + "049121437ff6891010000000001e0270000019000d00000000bfff00000000000100" + + "dcdcdcdcdcdcdcdcdcdc"; + + public void testDataDownloadMessage1() { + SmsMessage message = SmsMessage.createFromPdu(SMS_PP_MESSAGE_3_1_1); + assertTrue("message is SMS-PP data download", message.isUsimDataDownload()); + + mCm.expectSendEnvelope(SMS_PP_ENVELOPE_3_1_1, 0x90, 0x00, ""); + mCm.expectAcknowledgeGsmSms(true, 0); + mHandler.startDataDownload(message); + mCm.assertExpectedMethodsCalled(); + + mCm.expectSendEnvelope(SMS_PP_ENVELOPE_3_1_1, 0x90, 0x00, "0123456789"); + mCm.expectAcknowledgeGsmSmsWithPdu(true, "00077f16050123456789"); + mHandler.startDataDownload(message); + mCm.assertExpectedMethodsCalled(); + + mCm.expectSendEnvelope(SMS_PP_ENVELOPE_3_1_1, 0x62, 0xff, "0123456789abcdef"); + mCm.expectAcknowledgeGsmSmsWithPdu(false, "00d5077f16080123456789abcdef"); + mHandler.startDataDownload(message); + mCm.assertExpectedMethodsCalled(); + } + + public void testDataDownloadMessage5() { + SmsMessage message = SmsMessage.createFromPdu(SMS_PP_MESSAGE_3_1_5); + assertTrue("message is SMS-PP data download", message.isUsimDataDownload()); + + mCm.expectSendEnvelope(SMS_PP_ENVELOPE_3_1_5, 0x90, 0x00, "9876543210"); + mCm.expectAcknowledgeGsmSmsWithPdu(true, "00077ff6059876543210"); + mHandler.startDataDownload(message); + mCm.assertExpectedMethodsCalled(); + + mCm.expectSendEnvelope(SMS_PP_ENVELOPE_3_1_5, 0x93, 0x00, ""); + mCm.expectAcknowledgeGsmSms(false, 0xd4); // SIM toolkit busy + mHandler.startDataDownload(message); + mCm.assertExpectedMethodsCalled(); + } +} |