Merge "Add support for SMS-PP data download to USIM."
diff --git a/telephony/java/com/android/internal/telephony/CommandsInterface.java b/telephony/java/com/android/internal/telephony/CommandsInterface.java
index 9b42dbe..33ead75 100644
--- a/telephony/java/com/android/internal/telephony/CommandsInterface.java
+++ b/telephony/java/com/android/internal/telephony/CommandsInterface.java
@@ -157,6 +157,8 @@
 
     // 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 a6e0ec3..7043da5 100644
--- a/telephony/java/com/android/internal/telephony/IccIoResult.java
+++ b/telephony/java/com/android/internal/telephony/IccIoResult.java
@@ -21,8 +21,8 @@
  */
 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 51ebd99..fc011c0 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.Registrant;
 import android.os.RegistrantList;
 
+import com.android.internal.telephony.gsm.UsimServiceTable;
 import com.android.internal.telephony.ims.IsimRecords;
 
 /**
@@ -362,4 +363,8 @@
     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 ca04eb2..9d189c1 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.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 @@
      *                      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 10121dd..94f7a13 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.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 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 b497ec8..60f364e 100644
--- a/telephony/java/com/android/internal/telephony/PhoneProxy.java
+++ b/telephony/java/com/android/internal/telephony/PhoneProxy.java
@@ -32,6 +32,7 @@
 
 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 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 fb53686..5420264 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 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 0000000..973dbc8
--- /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 c1553d8..d29e488 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.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.SmsUsageMonitor;
 import com.android.internal.telephony.TelephonyProperties;
 
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 
@@ -56,9 +56,16 @@
     /** 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 @@
             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 @@
             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 8e965a3..495b5bc 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 @@
         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 2da9642..677923f 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 @@
         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 @@
         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 0000000..f47ff1b
--- /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 0000000..7e0d3c4
--- /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 0000000..6c8ba5e
--- /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();
+    }
+}