Snap for 4448085 from 551e63a25455174ee5da8bf7d9439c4046ca8f38 to oc-m3-release

Change-Id: Ided9fdd740a1b2f5afb2863b9214779c3e37220f
diff --git a/Android.mk b/Android.mk
index 70a88db..b440b28 100644
--- a/Android.mk
+++ b/Android.mk
@@ -25,7 +25,7 @@
 	$(call all-logtags-files-under, src/java) \
 	$(call all-proto-files-under, proto)
 
-LOCAL_JAVA_LIBRARIES := voip-common ims-common services
+LOCAL_JAVA_LIBRARIES := voip-common ims-common services bouncycastle
 LOCAL_STATIC_JAVA_LIBRARIES := android.hardware.radio-V1.1-java-static \
     android.hardware.radio.deprecated-V1.0-java-static
 
diff --git a/src/java/com/android/internal/telephony/CallForwardInfo.java b/src/java/com/android/internal/telephony/CallForwardInfo.java
index dccf306..e40028f 100644
--- a/src/java/com/android/internal/telephony/CallForwardInfo.java
+++ b/src/java/com/android/internal/telephony/CallForwardInfo.java
@@ -16,12 +16,16 @@
 
 package com.android.internal.telephony;
 
+import android.telecom.Log;
+
 /**
  * See also RIL_CallForwardInfo in include/telephony/ril.h
  *
  * {@hide}
  */
 public class CallForwardInfo {
+    private static final String TAG = "CallForwardInfo";
+
     public int             status;      /*1 = active, 0 = not active */
     public int             reason;      /* from TS 27.007 7.11 "reason" */
     public int             serviceClass; /* Saum of CommandsInterface.SERVICE_CLASS */
@@ -31,9 +35,9 @@
 
     @Override
     public String toString() {
-        return super.toString() + (status == 0 ? " not active " : " active ")
-            + " reason: " + reason
-            + " serviceClass: " + serviceClass + " " + timeSeconds + " seconds";
-
+        return "[CallForwardInfo: status=" + (status == 0 ? " not active " : " active ")
+                + ", reason= " + reason
+                + ", serviceClass= " + serviceClass + ", timeSec= " + timeSeconds + " seconds"
+                + ", number=" + Log.pii(number) + "]";
     }
 }
diff --git a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
index bca337d..66bc529 100644
--- a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
+++ b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
@@ -18,6 +18,8 @@
 
 import static android.preference.PreferenceManager.getDefaultSharedPreferences;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import android.app.AlarmManager;
 import android.app.DownloadManager;
 import android.app.PendingIntent;
@@ -34,21 +36,28 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.util.Base64;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.org.bouncycastle.util.io.pem.PemReader;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.Reader;
+import java.security.PublicKey;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
 import java.util.Date;
+import java.util.zip.GZIPInputStream;
 
 /**
  * This class contains logic to get Certificates and keep them current.
@@ -68,16 +77,19 @@
     private static final String INTENT_KEY_RENEWAL_ALARM_PREFIX =
             "com.android.internal.telephony.carrier_key_download_alarm";
 
-    private int mKeyAvailability = 0;
+    @VisibleForTesting
+    public int mKeyAvailability = 0;
 
     public static final String MNC = "MNC";
     public static final String MCC = "MCC";
     private static final String SEPARATOR = ":";
 
-    private static final String JSON_KEY = "key";
-    private static final String JSON_TYPE = "type";
-    private static final String JSON_IDENTIFIER = "identifier";
-    private static final String JSON_EXPIRATION_DATE = "expiration-date";
+    private static final String JSON_CERTIFICATE = "certificate";
+    // This is a hack to accommodate certain Carriers who insists on using the public-key
+    // field to store the certificate. We'll just use which-ever is not null.
+    private static final String JSON_CERTIFICATE_ALTERNATE = "public-key";
+    private static final String JSON_TYPE = "key-type";
+    private static final String JSON_IDENTIFIER = "key-identifier";
     private static final String JSON_CARRIER_KEYS = "carrier-keys";
     private static final String JSON_TYPE_VALUE_WLAN = "WLAN";
     private static final String JSON_TYPE_VALUE_EPDG = "EPDG";
@@ -89,7 +101,7 @@
 
     private final Phone mPhone;
     private final Context mContext;
-    private final DownloadManager mDownloadManager;
+    public final DownloadManager mDownloadManager;
     private String mURL;
 
     public CarrierKeyDownloadManager(Phone phone) {
@@ -173,14 +185,11 @@
     }
 
     /**
-     * this method resets the alarm. Starts by cleaning up the existing alarms.
-     * We look at the earliest expiration date, and setup an alarms X days prior.
-     * If the expiration date is in the past, we'll setup an alarm to run the next day. This
-     * could happen if the download has failed.
+     * this method returns the date to be used to decide on when to start downloading the key.
+     * from the carrier.
      **/
-    private void resetRenewalAlarm() {
-        cleanupRenewalAlarms();
-        int slotId = mPhone.getPhoneId();
+    @VisibleForTesting
+    public long getExpirationDate()  {
         long minExpirationDate = Long.MAX_VALUE;
         for (int key_type : CARRIER_KEY_TYPES) {
             if (!isKeyEnabled(key_type)) {
@@ -204,6 +213,20 @@
         } else {
             minExpirationDate = minExpirationDate - DEFAULT_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS;
         }
+        return minExpirationDate;
+    }
+
+    /**
+     * this method resets the alarm. Starts by cleaning up the existing alarms.
+     * We look at the earliest expiration date, and setup an alarms X days prior.
+     * If the expiration date is in the past, we'll setup an alarm to run the next day. This
+     * could happen if the download has failed.
+     **/
+    @VisibleForTesting
+    public void resetRenewalAlarm() {
+        cleanupRenewalAlarms();
+        int slotId = mPhone.getPhoneId();
+        long minExpirationDate = getExpirationDate();
         Log.d(LOG_TAG, "minExpirationDate: " + new Date(minExpirationDate));
         final AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(
                 Context.ALARM_SERVICE);
@@ -225,21 +248,30 @@
     }
 
     /**
+     * Returns the sim operator.
+     **/
+    @VisibleForTesting
+    public String getSimOperator() {
+        final TelephonyManager telephonyManager =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        return telephonyManager.getSimOperator(mPhone.getSubId());
+    }
+
+    /**
      *  checks if the download was sent by this particular instance. We do this by including the
      *  slot id in the key. If no value is found, we know that the download was not for this
      *  instance of the phone.
      **/
-    private boolean isValidDownload(String mccMnc) {
+    @VisibleForTesting
+    public boolean isValidDownload(String mccMnc) {
         String mccCurrent = "";
         String mncCurrent = "";
         String mccSource = "";
         String mncSource = "";
-        final TelephonyManager telephonyManager =
-                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-        String networkOperator = telephonyManager.getNetworkOperator(mPhone.getSubId());
 
-        if (TextUtils.isEmpty(networkOperator) || TextUtils.isEmpty(mccMnc)) {
-            Log.e(LOG_TAG, "networkOperator or mcc/mnc is empty");
+        String simOperator = getSimOperator();
+        if (TextUtils.isEmpty(simOperator) || TextUtils.isEmpty(mccMnc)) {
+            Log.e(LOG_TAG, "simOperator or mcc/mnc is empty");
             return false;
         }
 
@@ -248,8 +280,8 @@
         mncSource = splitValue[1];
         Log.d(LOG_TAG, "values from sharedPrefs mcc, mnc: " + mccSource + "," + mncSource);
 
-        mccCurrent = networkOperator.substring(0, 3);
-        mncCurrent = networkOperator.substring(3);
+        mccCurrent = simOperator.substring(0, 3);
+        mncCurrent = simOperator.substring(3);
         Log.d(LOG_TAG, "using values for mcc, mnc: " + mccCurrent + "," + mncCurrent);
 
         if (TextUtils.equals(mncSource, mncCurrent) &&  TextUtils.equals(mccSource, mccCurrent)) {
@@ -267,6 +299,7 @@
         DownloadManager.Query query = new DownloadManager.Query();
         query.setFilterById(carrierKeyDownloadIdentifier);
         Cursor cursor = mDownloadManager.query(query);
+        InputStream source = null;
 
         if (cursor == null) {
             return;
@@ -275,7 +308,7 @@
             int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
             if (DownloadManager.STATUS_SUCCESSFUL == cursor.getInt(columnIndex)) {
                 try {
-                    final InputStream source = new FileInputStream(
+                    source = new FileInputStream(
                             mDownloadManager.openDownloadedFile(carrierKeyDownloadIdentifier)
                                     .getFileDescriptor());
                     jsonStr = convertToString(source);
@@ -285,6 +318,11 @@
                             + ". " + e);
                 } finally {
                     mDownloadManager.remove(carrierKeyDownloadIdentifier);
+                    try {
+                        source.close();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
                 }
             }
             Log.d(LOG_TAG, "Completed downloading keys");
@@ -324,43 +362,43 @@
     }
 
     private static String convertToString(InputStream is) {
-        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
-        StringBuilder sb = new StringBuilder();
-
-        String line;
         try {
+            // The current implementation at certain Carriers has the data gzipped, which requires
+            // us to unzip the contents. Longer term, we want to add a flag in carrier config which
+            // determines if the data needs to be zipped or not.
+            GZIPInputStream gunzip = new GZIPInputStream(is);
+            BufferedReader reader = new BufferedReader(new InputStreamReader(gunzip, UTF_8));
+            StringBuilder sb = new StringBuilder();
+
+            String line;
             while ((line = reader.readLine()) != null) {
                 sb.append(line).append('\n');
             }
+            return sb.toString();
         } catch (IOException e) {
             e.printStackTrace();
-        } finally {
-            try {
-                is.close();
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
         }
-        return sb.toString();
+        return null;
     }
 
     /**
      * Converts the string into a json object to retreive the nodes. The Json should have 3 nodes,
      * including the Carrier public key, the key type and the key identifier. Once the nodes have
      * been extracted, they get persisted to the database. Sample:
-     *      "carrier-keys": [ { "key": "",
-     *                         "type": WLAN,
-     *                         "identifier": "",
-     *                         "expiration-date": 1502577746000
+     *      "carrier-keys": [ { "certificate": "",
+     *                         "key-type": "WLAN",
+     *                         "key-identifier": ""
      *                        } ]
      * @param jsonStr the json string.
-     * @param mccMnc contains the mcc, mnc
+     * @param mccMnc contains the mcc, mnc.
      */
-    private void parseJsonAndPersistKey(String jsonStr, String mccMnc) {
+    @VisibleForTesting
+    public void parseJsonAndPersistKey(String jsonStr, String mccMnc) {
         if (TextUtils.isEmpty(jsonStr) || TextUtils.isEmpty(mccMnc)) {
             Log.e(LOG_TAG, "jsonStr or mcc, mnc: is empty");
             return;
         }
+        PemReader reader = null;
         try {
             String mcc = "";
             String mnc = "";
@@ -369,10 +407,16 @@
             mnc = splitValue[1];
             JSONObject jsonObj = new JSONObject(jsonStr);
             JSONArray keys = jsonObj.getJSONArray(JSON_CARRIER_KEYS);
-
             for (int i = 0; i < keys.length(); i++) {
                 JSONObject key = keys.getJSONObject(i);
-                String carrierKey = key.getString(JSON_KEY);
+                // This is a hack to accommodate certain carriers who insist on using the public-key
+                // field to store the certificate. We'll just use which-ever is not null.
+                String cert = null;
+                if (key.has(JSON_CERTIFICATE)) {
+                    cert = key.getString(JSON_CERTIFICATE);
+                } else {
+                    cert = key.getString(JSON_CERTIFICATE_ALTERNATE);
+                }
                 String typeString = key.getString(JSON_TYPE);
                 int type = UNINITIALIZED_KEY_TYPE;
                 if (typeString.equals(JSON_TYPE_VALUE_WLAN)) {
@@ -380,13 +424,27 @@
                 } else if (typeString.equals(JSON_TYPE_VALUE_EPDG)) {
                     type = TelephonyManager.KEY_TYPE_EPDG;
                 }
-                long expiration_date = key.getLong(JSON_EXPIRATION_DATE);
                 String identifier = key.getString(JSON_IDENTIFIER);
-                savePublicKey(carrierKey, type, identifier, expiration_date,
-                        mcc, mnc);
+                ByteArrayInputStream inStream = new ByteArrayInputStream(cert.getBytes());
+                Reader fReader = new BufferedReader(new InputStreamReader(inStream));
+                reader = new PemReader(fReader);
+                Pair<PublicKey, Long> keyInfo =
+                        getKeyInformation(reader.readPemObject().getContent());
+                reader.close();
+                savePublicKey(keyInfo.first, type, identifier, keyInfo.second, mcc, mnc);
             }
         } catch (final JSONException e) {
             Log.e(LOG_TAG, "Json parsing error: " + e.getMessage());
+        } catch (final Exception e) {
+            Log.e(LOG_TAG, "Exception getting certificate: " + e);
+        } finally {
+            try {
+                if (reader != null) {
+                    reader.close();
+                }
+            } catch (final Exception e) {
+                Log.e(LOG_TAG, "Exception getting certificate: " + e);
+            }
         }
     }
 
@@ -394,8 +452,8 @@
      * introspects the mKeyAvailability bitmask
      * @return true if the digit at position k is 1, else false.
      */
-
-    private boolean isKeyEnabled(int keyType) {
+    @VisibleForTesting
+    public boolean isKeyEnabled(int keyType) {
         //since keytype has values of 1, 2.... we need to subtract 1 from the keytype.
         int returnValue = (mKeyAvailability >> (keyType - 1)) & 1;
         return (returnValue == 1) ? true : false;
@@ -427,15 +485,13 @@
 
     private boolean downloadKey() {
         Log.d(LOG_TAG, "starting download from: " + mURL);
-        final TelephonyManager telephonyManager =
-                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         String mcc = "";
         String mnc = "";
-        String networkOperator = telephonyManager.getNetworkOperator(mPhone.getSubId());
+        String simOperator = getSimOperator();
 
-        if (!TextUtils.isEmpty(networkOperator)) {
-            mcc = networkOperator.substring(0, 3);
-            mnc = networkOperator.substring(3);
+        if (!TextUtils.isEmpty(simOperator)) {
+            mcc = simOperator.substring(0, 3);
+            mnc = simOperator.substring(3);
             Log.d(LOG_TAG, "using values for mcc, mnc: " + mcc + "," + mnc);
         } else {
             Log.e(LOG_TAG, "mcc, mnc: is empty");
@@ -461,11 +517,35 @@
         return true;
     }
 
-    private void savePublicKey(String key, int type, String identifier, long expirationDate,
+    /**
+     * Save the public key
+     * @param certificate certificate that contains the public key.
+     * @return Pair containing the Public Key and the expiration date.
+     **/
+    @VisibleForTesting
+    public static Pair<PublicKey, Long> getKeyInformation(byte[] certificate) throws Exception {
+        InputStream inStream = new ByteArrayInputStream(certificate);
+        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+        X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream);
+        Pair<PublicKey, Long> keyInformation =
+                new Pair(cert.getPublicKey(), cert.getNotAfter().getTime());
+        return keyInformation;
+    }
+
+    /**
+     * Save the public key
+     * @param publicKey public key.
+     * @param type key-type.
+     * @param identifier which is an opaque string.
+     * @param expirationDate expiration date of the key.
+     * @param mcc
+     * @param mnc
+     **/
+    @VisibleForTesting
+    public void savePublicKey(PublicKey publicKey, int type, String identifier, long expirationDate,
                                String mcc, String mnc) {
-        byte[] keyBytes = Base64.decode(key.getBytes(), Base64.DEFAULT);
         ImsiEncryptionInfo imsiEncryptionInfo = new ImsiEncryptionInfo(mcc, mnc, type, identifier,
-                keyBytes, new Date(expirationDate));
+                publicKey, new Date(expirationDate));
         mPhone.setCarrierInfoForImsiEncryption(imsiEncryptionInfo);
     }
 }
diff --git a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
index 8df201e..77a39eb 100644
--- a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
@@ -79,13 +79,8 @@
         switch (msg.what) {
             case CARRIER_EVENT_VOICE_REGISTRATION:
             case CARRIER_EVENT_DATA_REGISTRATION:
-                handleConfigChanges();
-                break;
             case CARRIER_EVENT_VOICE_DEREGISTRATION:
             case CARRIER_EVENT_DATA_DEREGISTRATION:
-                if (isRadioOffOrAirplaneMode()) {
-                    break;
-                }
                 handleConfigChanges();
                 break;
             case NOTIFICATION_EMERGENCY_NETWORK:
@@ -317,8 +312,8 @@
             Rlog.i(LOG_TAG, "PrefNetworkNotification: sendMessage() w/values: "
                     + "," + isPhoneStillRegistered() + "," + mDelay + "," + isGlobalMode()
                     + "," + mSST.isRadioOn());
-            if (mDelay == UNINITIALIZED_DELAY_VALUE ||  isPhoneStillRegistered()
-                    || isGlobalMode()) {
+            if (mDelay == UNINITIALIZED_DELAY_VALUE ||  isPhoneStillRegistered() || isGlobalMode()
+                    || isRadioOffOrAirplaneMode()) {
                 return false;
             }
             return true;
diff --git a/src/java/com/android/internal/telephony/ClientWakelockTracker.java b/src/java/com/android/internal/telephony/ClientWakelockTracker.java
index 5bec60b..fa71e76 100644
--- a/src/java/com/android/internal/telephony/ClientWakelockTracker.java
+++ b/src/java/com/android/internal/telephony/ClientWakelockTracker.java
@@ -18,10 +18,10 @@
 
 import android.os.SystemClock;
 import android.telephony.ClientRequestStats;
-import android.telephony.Rlog;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -119,13 +119,13 @@
         return false;
     }
 
-    void dumpClientRequestTracker() {
-        Rlog.d(RIL.RILJ_LOG_TAG, "-------mClients---------------");
+    void dumpClientRequestTracker(PrintWriter pw) {
+        pw.println("-------mClients---------------");
         synchronized (mClients) {
             for (String key : mClients.keySet()) {
-                Rlog.d(RIL.RILJ_LOG_TAG, "Client : " + key);
-                Rlog.d(RIL.RILJ_LOG_TAG, mClients.get(key).toString());
+                pw.println("Client : " + key);
+                pw.println(mClients.get(key).toString());
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index c13e540..98c0a32 100644
--- a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -125,6 +125,9 @@
         int subId = sender.getSubId();
         try {
             if (mRegistry != null) {
+                Rlog.d(LOG_TAG, "notifyCallForwardingChanged: subId=" + subId + ", isCFActive="
+                        + sender.getCallForwardingIndicator());
+
                 mRegistry.notifyCallForwardingChangedForSubscriber(subId,
                         sender.getCallForwardingIndicator());
             }
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 35a2d41..5053341 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -1855,6 +1855,8 @@
 
     @Override
     public void setTTYMode(int ttyMode, Message onComplete) {
+        // Send out the TTY Mode change over RIL as well
+        super.setTTYMode(ttyMode, onComplete);
         if (mImsPhone != null) {
             mImsPhone.setTTYMode(ttyMode, onComplete);
         }
@@ -2547,6 +2549,7 @@
     private void processIccRecordEvents(int eventCode) {
         switch (eventCode) {
             case IccRecords.EVENT_CFI:
+                logi("processIccRecordEvents: EVENT_CFI");
                 notifyCallForwardingIndicator();
                 break;
         }
diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java
index 59195f8..391de50 100644
--- a/src/java/com/android/internal/telephony/InboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -158,6 +158,17 @@
     /** New SMS received as an AsyncResult. */
     public static final int EVENT_INJECT_SMS = 8;
 
+    /** Update tracker object; used only in waiting state */
+    private static final int EVENT_UPDATE_TRACKER = 9;
+
+    /** Timeout in case state machine is stuck in a state for too long; used only in waiting
+     * state */
+    private static final int EVENT_STATE_TIMEOUT = 10;
+
+    /** Timeout duration for EVENT_STATE_TIMEOUT */
+    @VisibleForTesting
+    public static final int STATE_TIMEOUT = 30000;
+
     /** Wakelock release delay when returning to idle state. */
     private static final int WAKELOCK_TIMEOUT = 3000;
 
@@ -450,6 +461,7 @@
                     // if any broadcasts were sent, transition to waiting state
                     InboundSmsTracker inboundSmsTracker = (InboundSmsTracker) msg.obj;
                     if (processMessagePart(inboundSmsTracker)) {
+                        sendMessage(EVENT_UPDATE_TRACKER, inboundSmsTracker);
                         transitionTo(mWaitingState);
                     } else {
                         // if event is sent from SmsBroadcastUndelivered.broadcastSms(), and
@@ -493,18 +505,41 @@
      * {@link IdleState} after any deferred {@link #EVENT_BROADCAST_SMS} messages are handled.
      */
     private class WaitingState extends State {
+        private InboundSmsTracker mTracker;
+
+        @Override
+        public void enter() {
+            if (DBG) log("entering Waiting state");
+            mTracker = null;
+            sendMessageDelayed(EVENT_STATE_TIMEOUT, STATE_TIMEOUT);
+        }
+
         @Override
         public void exit() {
             if (DBG) log("exiting Waiting state");
             // Before moving to idle state, set wakelock timeout to WAKE_LOCK_TIMEOUT milliseconds
             // to give any receivers time to take their own wake locks
             setWakeLockTimeout(WAKELOCK_TIMEOUT);
+            if (VDBG) {
+                if (hasMessages(EVENT_STATE_TIMEOUT)) {
+                    log("exiting Waiting state: removing EVENT_STATE_TIMEOUT from message queue");
+                }
+                if (hasMessages(EVENT_UPDATE_TRACKER)) {
+                    log("exiting Waiting state: removing EVENT_UPDATE_TRACKER from message queue");
+                }
+            }
+            removeMessages(EVENT_STATE_TIMEOUT);
+            removeMessages(EVENT_UPDATE_TRACKER);
         }
 
         @Override
         public boolean processMessage(Message msg) {
             log("WaitingState.processMessage:" + msg.what);
             switch (msg.what) {
+                case EVENT_UPDATE_TRACKER:
+                    mTracker = (InboundSmsTracker) msg.obj;
+                    return HANDLED;
+
                 case EVENT_BROADCAST_SMS:
                     // defer until the current broadcast completes
                     deferMessage(msg);
@@ -520,6 +555,18 @@
                     // not ready to return to idle; ignore
                     return HANDLED;
 
+                case EVENT_STATE_TIMEOUT:
+                    // stuck in WaitingState for too long; drop the message and exit this state
+                    if (mTracker != null) {
+                        log("WaitingState.processMessage: EVENT_STATE_TIMEOUT; dropping message");
+                        dropSms(new SmsBroadcastReceiver(mTracker));
+                    } else {
+                        log("WaitingState.processMessage: EVENT_STATE_TIMEOUT; mTracker is null "
+                                + "- sending EVENT_BROADCAST_COMPLETE");
+                        sendMessage(EVENT_BROADCAST_COMPLETE);
+                    }
+                    return HANDLED;
+
                 default:
                     // parent state handles the other message types
                     return NOT_HANDLED;
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 28e4556..8b0c677 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -56,6 +56,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.VoLteServiceState;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.ims.ImsCall;
 import com.android.ims.ImsConfig;
@@ -1789,7 +1790,7 @@
         int status = enable ? IccRecords.CALL_FORWARDING_STATUS_ENABLED :
                 IccRecords.CALL_FORWARDING_STATUS_DISABLED;
         int subId = getSubId();
-        Rlog.d(LOG_TAG, "setCallForwardingIndicatorInSharedPref: Storing status = " + status +
+        Rlog.i(LOG_TAG, "setCallForwardingIndicatorInSharedPref: Storing status = " + status +
                 " in pref " + CF_STATUS + subId);
 
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
@@ -1831,6 +1832,9 @@
         if (callForwardingIndicator == IccRecords.CALL_FORWARDING_STATUS_UNKNOWN) {
             callForwardingIndicator = getCallForwardingIndicatorFromSharedPref();
         }
+        Rlog.v(LOG_TAG, "getCallForwardingIndicator: iccForwardingFlag=" + (r != null
+                    ? r.getVoiceCallForwardingFlag() : "null") + ", sharedPrefFlag="
+                    + getCallForwardingIndicatorFromSharedPref());
         return (callForwardingIndicator == IccRecords.CALL_FORWARDING_STATUS_ENABLED);
     }
 
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index 3ed591f..226ee8e 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -4910,7 +4910,7 @@
         }
         pw.println(" mLastNITZTimeInfo=" + Arrays.toString(mLastNITZTimeInfo));
         pw.println(" mTestingEmergencyCall=" + mTestingEmergencyCall.get());
-        mClientWakelockTracker.dumpClientRequestTracker();
+        mClientWakelockTracker.dumpClientRequestTracker(pw);
     }
 
     public List<ClientRequestStats> getClientRequestStats() {
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index e9d6759..d3c17dd 100644
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -210,6 +210,7 @@
     protected static final int EVENT_ALL_DATA_DISCONNECTED             = 49;
     protected static final int EVENT_PHONE_TYPE_SWITCHED               = 50;
     protected static final int EVENT_RADIO_POWER_FROM_CARRIER          = 51;
+    protected static final int EVENT_SIM_NOT_INSERTED                  = 52;
 
     protected static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
@@ -354,6 +355,14 @@
                 }
                 // update voicemail count and notify message waiting changed
                 mPhone.updateVoiceMail();
+
+                // cancel notifications if we see SIM_NOT_INSERTED (This happens on bootup before
+                // the SIM is first detected and then subsequently on SIM removals)
+                if (mSubscriptionController.getSlotIndex(subId)
+                        == SubscriptionManager.SIM_NOT_INSERTED) {
+                    // this is handled on the main thread to mitigate racing with setNotification().
+                    sendMessage(obtainMessage(EVENT_SIM_NOT_INSERTED));
+                }
             }
         }
     };
@@ -446,7 +455,6 @@
     public static final int CS_NORMAL_ENABLED = 1005;     // Access Control blocks normal voice/sms service
     public static final int CS_EMERGENCY_ENABLED = 1006;  // Access Control blocks emergency call service
     public static final int CS_REJECT_CAUSE_ENABLED = 2001;     // Notify MM rejection cause
-    public static final int CS_REJECT_CAUSE_DISABLED = 2002;    // Cancel MM rejection cause
     /** Notification id. */
     public static final int PS_NOTIFICATION = 888;  // Id to update and cancel PS restricted
     public static final int CS_NOTIFICATION = 999;  // Id to update and cancel CS restricted
@@ -1297,6 +1305,14 @@
                 }
                 break;
 
+            case EVENT_SIM_NOT_INSERTED:
+                if (DBG) log("EVENT_SIM_NOT_INSERTED");
+                cancelAllNotifications();
+                mMdn = null;
+                mMin = null;
+                mIsMinInfoReady = false;
+                break;
+
             case EVENT_ALL_DATA_DISCONNECTED:
                 int dds = SubscriptionManager.getDefaultDataSubscriptionId();
                 ProxyController.getInstance().unregisterForAllDataDisconnected(dds, this);
@@ -2820,7 +2836,7 @@
         }
 
         if (hasRejectCauseChanged) {
-            setNotification(mRejectCode == 0 ? CS_REJECT_CAUSE_DISABLED : CS_REJECT_CAUSE_ENABLED);
+            setNotification(CS_REJECT_CAUSE_ENABLED);
         }
 
         if (hasChanged) {
@@ -3846,6 +3862,18 @@
     }
 
     /**
+     * Cancels all notifications posted to NotificationManager. These notifications for restricted
+     * state and rejection cause for cs registration are no longer valid after the SIM has been
+     * removed.
+     */
+    private void cancelAllNotifications() {
+        if (DBG) log("setNotification: cancelAllNotifications");
+        NotificationManager notificationManager = (NotificationManager)
+                mPhone.getContext().getSystemService(Context.NOTIFICATION_SERVICE);
+        notificationManager.cancelAll();
+    }
+
+    /**
      * Post a notification to NotificationManager for restricted state and
      * rejection cause for cs registration
      *
@@ -3920,17 +3948,14 @@
                 notificationId = CS_REJECT_CAUSE_NOTIFICATION;
                 int resId = selectResourceForRejectCode(mRejectCode);
                 if (0 == resId) {
-                    // cancel notification because current reject code is not handled.
-                    notifyType = CS_REJECT_CAUSE_DISABLED;
+                    loge("setNotification: mRejectCode=" + mRejectCode + " is not handled.");
+                    return;
                 } else {
                     icon = com.android.internal.R.drawable.stat_notify_mmcc_indication_icn;
                     title = Resources.getSystem().getString(resId);
                     details = null;
                 }
                 break;
-            case CS_REJECT_CAUSE_DISABLED:
-                notificationId = CS_REJECT_CAUSE_NOTIFICATION;
-                break;
         }
 
         if (DBG) {
@@ -3954,8 +3979,7 @@
         NotificationManager notificationManager = (NotificationManager)
                 context.getSystemService(Context.NOTIFICATION_SERVICE);
 
-        if (notifyType == PS_DISABLED || notifyType == CS_DISABLED
-                || notifyType == CS_REJECT_CAUSE_DISABLED) {
+        if (notifyType == PS_DISABLED || notifyType == CS_DISABLED) {
             // cancel previous post notification
             notificationManager.cancel(notificationId);
         } else {
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index 3c13a40..45dc0b27 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -713,7 +713,6 @@
 
     @Override
     public void setTTYMode(int ttyMode, Message onComplete) {
-        super.setTTYMode(ttyMode, onComplete);
         mCT.setTtyMode(ttyMode);
     }
 
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
index 87b96d8..c165b03 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
@@ -496,9 +496,6 @@
         return false;
     }
 
-    public void saveClirSetting(int commandInterfaceCLIRMode) {
-    }
-
     @Override
     public IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager(){
         return null;
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index c19c28b..072da68 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -726,6 +726,8 @@
             return NetworkStats.UID_ALL;
         }
 
+        // Initialize to UID_ALL so at least it can be counted to overall data usage if
+        // the dialer's package uid is not available.
         int uid = NetworkStats.UID_ALL;
         try {
             uid = context.getPackageManager().getPackageUid(pkg, 0);
@@ -2549,7 +2551,7 @@
                                 && targetAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN
                                 && targetAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
                 if (isHandoverFromWifi && imsCall.isVideoCall()) {
-                    if (mNotifyHandoverVideoFromWifiToLTE) {
+                    if (mNotifyHandoverVideoFromWifiToLTE && mIsDataEnabled) {
                         log("onCallHandover :: notifying of WIFI to LTE handover.");
                         conn.onConnectionEvent(
                                 TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE, null);
@@ -2558,7 +2560,7 @@
                     if (!mIsDataEnabled && mIsViLteDataMetered) {
                         // Call was downgraded from WIFI to LTE and data is metered; downgrade the
                         // call now.
-                        downgradeVideoCall(ImsReasonInfo.CODE_DATA_DISABLED, conn);
+                        downgradeVideoCall(ImsReasonInfo.CODE_WIFI_LOST, conn);
                     }
                 }
             } else {
@@ -2999,6 +3001,17 @@
         // a separate entry if uid is different from the previous snapshot.
         NetworkStats vtDataUsageUidSnapshot = new NetworkStats(currentTime, 1);
         vtDataUsageUidSnapshot.combineAllValues(mVtDataUsageUidSnapshot);
+
+        // The dialer uid might not be initialized correctly during boot up due to telecom service
+        // not ready or its default dialer cache not ready. So we double check again here to see if
+        // default dialer uid is really not available.
+        if (mDefaultDialerUid.get() == NetworkStats.UID_ALL) {
+            final TelecomManager telecomManager =
+                    (TelecomManager) mPhone.getContext().getSystemService(Context.TELECOM_SERVICE);
+            mDefaultDialerUid.set(
+                    getPackageUid(mPhone.getContext(), telecomManager.getDefaultDialerPackage()));
+        }
+
         // Since the modem only reports the total vt data usage rather than rx/tx separately,
         // the only thing we can do here is splitting the usage into half rx and half tx.
         vtDataUsageUidSnapshot.combineValues(new NetworkStats.Entry(
@@ -3507,8 +3520,9 @@
                 // If the carrier supports downgrading to voice, then we can simply issue a
                 // downgrade to voice instead of terminating the call.
                 modifyVideoCall(imsCall, VideoProfile.STATE_AUDIO_ONLY);
-            } else if (mSupportPauseVideo) {
-                // The carrier supports video pause signalling, so pause the video.
+            } else if (mSupportPauseVideo && reasonCode != ImsReasonInfo.CODE_WIFI_LOST) {
+                // The carrier supports video pause signalling, so pause the video if we didn't just
+                // lose wifi; in that case just disconnect.
                 mShouldUpdateImsConfigOnDisconnect = true;
                 conn.pauseVideo(VideoPauseTracker.SOURCE_DATA_ENABLED);
             } else {
diff --git a/src/java/com/android/internal/telephony/util/NotificationChannelController.java b/src/java/com/android/internal/telephony/util/NotificationChannelController.java
index 9967b05..54d7d1a 100644
--- a/src/java/com/android/internal/telephony/util/NotificationChannelController.java
+++ b/src/java/com/android/internal/telephony/util/NotificationChannelController.java
@@ -58,21 +58,26 @@
         alertChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
                 new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build());
 
+        final NotificationChannel mobileDataStatusChannel = new NotificationChannel(
+                CHANNEL_ID_MOBILE_DATA_STATUS,
+                context.getText(R.string.notification_channel_mobile_data_status),
+                NotificationManager.IMPORTANCE_LOW);
+        // allow users to block notifications from system
+        mobileDataStatusChannel.setBlockableSystem(true);
+
         context.getSystemService(NotificationManager.class)
                 .createNotificationChannels(Arrays.asList(
                 new NotificationChannel(CHANNEL_ID_CALL_FORWARD,
                         context.getText(R.string.notification_channel_call_forward),
                         NotificationManager.IMPORTANCE_LOW),
-                new NotificationChannel(CHANNEL_ID_MOBILE_DATA_STATUS,
-                        context.getText(R.string.notification_channel_mobile_data_status),
-                        NotificationManager.IMPORTANCE_LOW),
                 new NotificationChannel(CHANNEL_ID_SMS,
                         context.getText(R.string.notification_channel_sms),
                         NotificationManager.IMPORTANCE_HIGH),
                 new NotificationChannel(CHANNEL_ID_WFC,
                         context.getText(R.string.notification_channel_wfc),
                         NotificationManager.IMPORTANCE_LOW),
-                alertChannel));
+                alertChannel,
+                mobileDataStatusChannel));
         // only for update
         if (getChannel(CHANNEL_ID_VOICE_MAIL, context) != null) {
             migrateVoicemailNotificationSettings(context);
diff --git a/tests/telephonytests/Android.mk b/tests/telephonytests/Android.mk
index fd2cbb2..b777fac 100644
--- a/tests/telephonytests/Android.mk
+++ b/tests/telephonytests/Android.mk
@@ -7,7 +7,7 @@
 
 #LOCAL_STATIC_JAVA_LIBRARIES := librilproto-java
 
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common ims-common services.core
+LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common ims-common services.core bouncycastle
 LOCAL_STATIC_JAVA_LIBRARIES := guava \
                                frameworks-base-testutils \
                                mockito-target-minus-junit4 \
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
new file mode 100644
index 0000000..4b99cbe
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.app.DownloadManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.HandlerThread;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.ImsiEncryptionInfo;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
+
+import com.android.org.bouncycastle.util.io.pem.PemReader;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.mockito.MockitoAnnotations;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.security.PublicKey;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import static android.preference.PreferenceManager.getDefaultSharedPreferences;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class CarrierKeyDownloadMgrTest extends TelephonyTest {
+
+    private static final String LOG_TAG = "CarrierKeyDownloadManager";
+
+    private CarrierKeyDownloadManager mCarrierKeyDM;
+    private CarrierActionAgentHandler mCarrierActionAgentHandler;
+
+    private String mURL = "http://www.google.com";
+
+    private static final String CERT = "-----BEGIN CERTIFICATE-----\r\nMIIFjzCCBHegAwIBAgIUPxj3SLif82Ky1RlUy8p2EWJCh8MwDQYJKoZIhvcNAQELBQAwgY0xCzAJBgNVBAYTAk5MMRIwEAYDVQQHEwlBbXN0ZXJkYW0xJTAjBgNVBAoTHFZlcml6b24gRW50ZXJwcmlzZSBTb2x1dGlvbnMxEzARBgNVBAsTCkN5YmVydHJ1c3QxLjAsBgNVBAMTJVZlcml6b24gUHVibGljIFN1cmVTZXJ2ZXIgQ0EgRzE0LVNIQTIwHhcNMTcwODE0MTc0MzM4WhcNMTkwODE0MTc0MzM4WjCBmTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFjAUBgNVBAcTDUJhc2tpbmcgUmlkZ2UxIjAgBgNVBAoTGVZlcml6b24gRGF0YSBTZXJ2aWNlcyBMTEMxHzAdBgNVBAsTFk5ldHdvcmsgU3lzdGVtIFN1cHBvcnQxGDAWBgNVBAMTD3ZpMWx2Lmltc3ZtLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALUQKWTHi4Hjpd1LQwJ87RXa0Rs3rVonvVevliqdUH5BikjhAzvIqwPSXeRQqkaRTFIyp0NKcNqGdjAaHRo43gdHeWSH331sS6CMZDg988gZznskzCqJJo6ii5FuLC8qe2YDsHxT+CefXev2rn6Bj1ei2X74uZsy5KlkBRZfFHtPdK6/EK5TpzrvcXfDyOK1rn8FTno1bQOTAhL39GPcLhdrXV7AN+lu+EBpdCqlTdcoDxsqavi/91MwUIVEzxJmycKloT6OWfU44r7+L5SYYgc88NTaGL/BvCFwHRIa1ZgYSGeAPes45792MGG7tfr/ttAGp9UEwTv2zWTxzWnRP/UCAwEAAaOCAdcwggHTMAwGA1UdEwEB/wQCMAAwTAYDVR0gBEUwQzBBBgkrBgEEAbE+ATIwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly9zZWN1cmUub21uaXJvb3QuY29tL3JlcG9zaXRvcnkwgakGCCsGAQUFBwEBBIGcMIGZMC0GCCsGAQUFBzABhiFodHRwOi8vdnBzc2cxNDIub2NzcC5vbW5pcm9vdC5jb20wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9jYWNlcnQub21uaXJvb3QuY29tL3Zwc3NnMTQyLmNydDAzBggrBgEFBQcwAoYnaHR0cDovL2NhY2VydC5vbW5pcm9vdC5jb20vdnBzc2cxNDIuZGVyMBoGA1UdEQQTMBGCD3ZpMWx2Lmltc3ZtLmNvbTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFOQtu5EBZSYftHo/oxUlpM6MRDM7MD4GA1UdHwQ3MDUwM6AxoC+GLWh0dHA6Ly92cHNzZzE0Mi5jcmwub21uaXJvb3QuY29tL3Zwc3NnMTQyLmNybDAdBgNVHQ4EFgQUv5SaSyNM/yXw1v0N9TNpjsFCaPcwDQYJKoZIhvcNAQELBQADggEBACNJusTULj1KyV4RwiskKfp4wI9Hsz3ESbZS/ijF9D57BQ0UwkELU9r6rEAhsYLUvMq4sDhDbYIdupgP4MBzFnjkKult7VQm5W3nCcuHgXYFAJ9Y1a4OZAo/4hrHj70W9TsQ1ioSMjUT4F8bDUYZI0kcyH8e/+2DaTsLUpHw3L+Keu8PsJVBLnvcKJjWrZD/Bgd6JuaTX2G84i0rY0GJuO9CxLNJa6n61Mz5cqLYIuwKgiVgTA2n71YITyFICOFPFX1vSx35AWvD6aVYblxtC8mpCdF2h4s1iyrpXeji2GCJLwsNVtTtNQ4zWX3Gnq683wzkYZeyOHUyftIgAQZ+HsY=\r\n-----END CERTIFICATE-----";
+
+
+    private String mJsonStr = "{ \"carrier-keys\": [ { \"certificate\": \"" + CERT + "\", \"key-type\": \"WLAN\", \"key-identifier\": \"key1=value\", \"expiration-date\": 1502577746000 }, { \"certificate\": \"" + CERT + "\", \"key-type\": \"WLAN\", \"key-identifier\": \"key1=value\", \"expiration-date\": 1502577746000 }]}";
+
+    private String mJsonStr1 = "{ \"carrier-keys\": [ { \"public-key\": \"" + CERT + "\", \"key-type\": \"WLAN\", \"key-identifier\": \"key1=value\", \"expiration-date\": 1502577746000 }, { \"public-key\": \"" + CERT + "\", \"key-type\": \"WLAN\", \"key-identifier\": \"key1=value\", \"expiration-date\": 1502577746000 }]}";
+
+    private class CarrierActionAgentHandler extends HandlerThread {
+
+        private CarrierActionAgentHandler(String name) {
+            super(name);
+        }
+
+        @Override
+        public void onLooperPrepared() {
+            mCarrierKeyDM = new CarrierKeyDownloadManager(mPhone);
+            setReady(true);
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        logd("CarrierActionAgentTest +Setup!");
+        MockitoAnnotations.initMocks(this);
+        super.setUp(getClass().getSimpleName());
+        mCarrierActionAgentHandler = new CarrierActionAgentHandler(getClass().getSimpleName());
+        mCarrierActionAgentHandler.start();
+        waitUntilReady();
+        logd("CarrierActionAgentTest -Setup!");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mCarrierActionAgentHandler.quit();
+        super.tearDown();
+    }
+
+    /* Checks if the expiration date is calculated correctly
+     * In this case the expiration date should be the next day.
+     */
+    @Test
+    @SmallTest
+    public void testExpirationDate1Day() {
+        java.security.PublicKey publicKey = null;
+        mCarrierKeyDM.mKeyAvailability = 3;
+        SimpleDateFormat dt = new SimpleDateFormat("yyyy-mm-dd");
+        Calendar cal = new GregorianCalendar();
+        cal.add(Calendar.DATE, 6);
+        Date date = cal.getTime();
+        Calendar expectedCal = new GregorianCalendar();
+        expectedCal.add(Calendar.DATE, 1);
+        String dateExpected = dt.format(expectedCal.getTime());
+        ImsiEncryptionInfo imsiEncryptionInfo = new ImsiEncryptionInfo("mcc", "mnc", 1,
+                "keyIdentifier", publicKey, date);
+        when(mPhone.getCarrierInfoForImsiEncryption(anyInt())).thenReturn(imsiEncryptionInfo);
+        Date expirationDate = new Date(mCarrierKeyDM.getExpirationDate());
+        assertTrue(dt.format(expirationDate).equals(dateExpected));
+    }
+
+    /**
+     * Checks if the expiration date is calculated correctly
+     * In this case the expiration date should be the expiration date of the key.
+     **/
+    @Test
+    @SmallTest
+    public void testExpirationDate7Day() {
+        java.security.PublicKey publicKey = null;
+        mCarrierKeyDM.mKeyAvailability = 3;
+        SimpleDateFormat dt = new SimpleDateFormat("yyyy-mm-dd");
+        Calendar cal = new GregorianCalendar();
+        cal.add(Calendar.DATE, 10);
+        Date date = cal.getTime();
+        Calendar expectedCal = new GregorianCalendar();
+        expectedCal.add(Calendar.DATE, 3);
+        String dateExpected = dt.format(expectedCal.getTime());
+        ImsiEncryptionInfo imsiEncryptionInfo = new ImsiEncryptionInfo("mcc", "mnc", 1,
+                "keyIdentifier", publicKey, date);
+        when(mPhone.getCarrierInfoForImsiEncryption(anyInt())).thenReturn(imsiEncryptionInfo);
+        Date expirationDate = new Date(mCarrierKeyDM.getExpirationDate());
+        assertTrue(dt.format(expirationDate).equals(dateExpected));
+    }
+
+    /**
+     * Checks if the json is parse correctly.
+     * Verify that setCarrierInfoForImsiEncryption is called with the right params
+     **/
+    @Test
+    @SmallTest
+    public void testParseJson() {
+        ByteArrayInputStream certBytes = new ByteArrayInputStream(CERT.getBytes());
+        Reader fRd = new BufferedReader(new InputStreamReader(certBytes));
+        PemReader reader = new PemReader(fRd);
+        Pair<PublicKey, Long> keyInfo = null;
+        try {
+            keyInfo = mCarrierKeyDM.getKeyInformation(reader.readPemObject().getContent());
+        } catch (Exception e) {
+            fail(LOG_TAG + "exception creating public key");
+        }
+        ImsiEncryptionInfo imsiEncryptionInfo = new ImsiEncryptionInfo("310", "270", 2,
+                "key1=value", keyInfo.first, new Date(keyInfo.second));
+        String mccMnc = "310:270";
+        mCarrierKeyDM.parseJsonAndPersistKey(mJsonStr, mccMnc);
+        verify(mPhone, times(2)).setCarrierInfoForImsiEncryption(
+                (Matchers.refEq(imsiEncryptionInfo)));
+    }
+
+    /**
+     * Checks if the json is parse correctly.
+     * Same as testParseJason, except that the test looks for the "public-key" field.
+     **/
+    @Test
+    @SmallTest
+    public void testParseJsonPublicKey() {
+        ByteArrayInputStream certBytes = new ByteArrayInputStream(CERT.getBytes());
+        Reader fRd = new BufferedReader(new InputStreamReader(certBytes));
+        PemReader reader = new PemReader(fRd);
+        Pair<PublicKey, Long> keyInfo = null;
+        try {
+            keyInfo = mCarrierKeyDM.getKeyInformation(reader.readPemObject().getContent());
+        } catch (Exception e) {
+            fail(LOG_TAG + "exception creating public key");
+        }
+        ImsiEncryptionInfo imsiEncryptionInfo = new ImsiEncryptionInfo("310", "270", 2,
+                "key1=value", keyInfo.first, new Date(keyInfo.second));
+        String mccMnc = "310:270";
+        mCarrierKeyDM.parseJsonAndPersistKey(mJsonStr1, mccMnc);
+        verify(mPhone, times(2)).setCarrierInfoForImsiEncryption(
+                (Matchers.refEq(imsiEncryptionInfo)));
+    }
+
+    /**
+     * Checks if the json is parse correctly.
+     * Since the json is bad, we want to verify that savePublicKey is not called.
+     **/
+    @Test
+    @SmallTest
+    public void testParseBadJsonFail() {
+        String mccMnc = "310:290";
+        String badJsonStr = "{badJsonString}";
+        mCarrierKeyDM.parseJsonAndPersistKey(badJsonStr, mccMnc);
+        verify(mPhone, times(0)).setCarrierInfoForImsiEncryption(any());
+    }
+
+    /**
+     * Checks if the download is valid.
+     * returns true since the mnc/mcc is valid.
+     **/
+    @Test
+    @SmallTest
+    public void testIsValidDownload() {
+        String mccMnc = "310:260";
+        when(mTelephonyManager.getSimOperator(anyInt())).thenReturn("310260");
+        assertTrue(mCarrierKeyDM.isValidDownload(mccMnc));
+    }
+
+    /**
+     * Checks if the download is valid.
+     * returns false since the mnc/mcc is in-valid.
+     **/
+    @Test
+    @SmallTest
+    public void testIsValidDownloadFail() {
+        String mccMnc = "310:290";
+        when(mTelephonyManager.getSimOperator(anyInt())).thenReturn("310260");
+        assertFalse(mCarrierKeyDM.isValidDownload(mccMnc));
+    }
+
+    /**
+     * Tests if the key is enabled.
+     * tests for all bit-mask value.
+     **/
+    @Test
+    @SmallTest
+    public void testIsKeyEnabled() {
+        mCarrierKeyDM.mKeyAvailability = 3;
+        assertTrue(mCarrierKeyDM.isKeyEnabled(1));
+        assertTrue(mCarrierKeyDM.isKeyEnabled(2));
+        mCarrierKeyDM.mKeyAvailability = 2;
+        assertFalse(mCarrierKeyDM.isKeyEnabled(1));
+        assertTrue(mCarrierKeyDM.isKeyEnabled(2));
+        mCarrierKeyDM.mKeyAvailability = 1;
+        assertTrue(mCarrierKeyDM.isKeyEnabled(1));
+        assertFalse(mCarrierKeyDM.isKeyEnabled(2));
+    }
+
+    /**
+     * Tests sending the ACTION_DOWNLOAD_COMPLETE intent.
+     * Verify that the alarm will kick-off the next day.
+     **/
+    @Test
+    @SmallTest
+    public void testDownloadComplete() {
+        SharedPreferences.Editor editor = getDefaultSharedPreferences(mContext).edit();
+        String mccMnc = "310:260";
+        int slotId = mPhone.getPhoneId();
+        editor.putString("CARRIER_KEY_DM_MCC_MNC" + slotId, mccMnc);
+        editor.commit();
+
+        SimpleDateFormat dt = new SimpleDateFormat("yyyy-mm-dd");
+        Calendar expectedCal = new GregorianCalendar();
+        expectedCal.add(Calendar.DATE, 1);
+        String dateExpected = dt.format(expectedCal.getTime());
+
+        when(mTelephonyManager.getSimOperator(anyInt())).thenReturn("310260");
+        Intent mIntent = new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
+        mContext.sendBroadcast(mIntent);
+        Date expirationDate = new Date(mCarrierKeyDM.getExpirationDate());
+        assertTrue(dt.format(expirationDate).equals(dateExpected));
+    }
+
+    /**
+     * Test sending the ACTION_CARRIER_CONFIG_CHANGED intent.
+     * Verify that the right mnc/mcc gets stored in the preferences.
+     **/
+    @Test
+    @SmallTest
+    public void testCarrierConfigChanged() {
+        CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
+                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        int slotId = mPhone.getPhoneId();
+        PersistableBundle bundle = carrierConfigManager.getConfigForSubId(slotId);
+        bundle.putInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT, 3);
+        bundle.putString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING, mURL);
+
+        when(mTelephonyManager.getSimOperator(anyInt())).thenReturn("310260");
+        Intent mIntent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        mIntent.putExtra(PhoneConstants.PHONE_KEY, 0);
+        mContext.sendBroadcast(mIntent);
+        SharedPreferences preferences = getDefaultSharedPreferences(mContext);
+        String mccMnc = preferences.getString("CARRIER_KEY_DM_MCC_MNC" + slotId, null);
+        assertTrue(mccMnc.equals("310:260"));
+    }
+
+    /**
+     * Tests sending the INTENT_KEY_RENEWAL_ALARM_PREFIX intent.
+     * Verify that the right mnc/mcc gets stored in the preferences.
+     **/
+    @Test
+    @SmallTest
+    public void testAlarmRenewal() {
+        CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
+                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        int slotId = mPhone.getPhoneId();
+        PersistableBundle bundle = carrierConfigManager.getConfigForSubId(slotId);
+        bundle.putInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT, 3);
+        bundle.putString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING, mURL);
+
+        when(mTelephonyManager.getSimOperator(anyInt())).thenReturn("310260");
+        Intent mIntent = new Intent("com.android.internal.telephony.carrier_key_download_alarm"
+                + slotId);
+        mContext.sendBroadcast(mIntent);
+        SharedPreferences preferences = getDefaultSharedPreferences(mContext);
+        String mccMnc = preferences.getString("CARRIER_KEY_DM_MCC_MNC" + slotId, null);
+        assertTrue(mccMnc.equals("310:260"));
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index 7e8a439..2afdcf9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
@@ -27,6 +27,7 @@
 
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
+import android.app.DownloadManager;
 import android.app.NotificationManager;
 import android.app.usage.UsageStatsManager;
 import android.content.BroadcastReceiver;
@@ -240,6 +241,8 @@
                     return mEuiccManager;
                 case Context.TELECOM_SERVICE:
                     return mTelecomManager;
+                case Context.DOWNLOAD_SERVICE:
+                    return mDownloadManager;
                 case Context.DISPLAY_SERVICE:
                 case Context.POWER_SERVICE:
                     // PowerManager and DisplayManager are final classes so cannot be mocked,
@@ -496,6 +499,7 @@
     private final Resources mResources = mock(Resources.class);
     private final PackageManager mPackageManager = mock(PackageManager.class);
     private final TelephonyManager mTelephonyManager = mock(TelephonyManager.class);
+    private final DownloadManager mDownloadManager = mock(DownloadManager.class);
     private final AppOpsManager mAppOpsManager = mock(AppOpsManager.class);
     private final NotificationManager mNotificationManager = mock(NotificationManager.class);
     private final UserManager mUserManager = mock(UserManager.class);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
index 260b235..5e3f948 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
@@ -26,6 +26,7 @@
 import android.telephony.PhoneNumberUtils;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.text.SpannableStringBuilder;
+import android.text.style.TtsSpan;
 
 import org.junit.Ignore;
 import org.junit.Test;
@@ -751,4 +752,21 @@
         assertEquals("tim_123", PhoneNumberUtils.getUsernameFromUriNumber("tim_123@zzz.org"));
         assertEquals("5103331245", PhoneNumberUtils.getUsernameFromUriNumber("5103331245"));
     }
+
+    @SmallTest
+    @Test
+    public void testCreateTtsSpan() {
+        checkTtsNumber("650 555 1212", "650-555-1212");
+        checkTtsNumber("6505551212", "+1-650-555-1212");
+        checkTtsNumber("232", "232");
+        checkTtsNumber("*232", "*232");
+        checkTtsNumber("*232#", "*232#");
+        checkTtsNumber("*650 555 1212#", "*650-555-1212#");
+    }
+
+    private void checkTtsNumber(String expected, String sourceNumber) {
+        TtsSpan ttsSpan = PhoneNumberUtils.createTtsSpan(sourceNumber);
+        assertEquals(TtsSpan.TYPE_TELEPHONE, ttsSpan.getType());
+        assertEquals(expected, ttsSpan.getArgs().getString(TtsSpan.ARG_NUMBER_PARTS));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
index bb09014..02758ac 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
@@ -47,8 +47,8 @@
 import android.os.UserManager;
 import android.provider.Telephony;
 import android.support.test.filters.FlakyTest;
+import android.support.test.filters.MediumTest;
 import android.test.mock.MockContentResolver;
-import android.test.suitebuilder.annotation.MediumTest;
 
 import com.android.internal.telephony.FakeSmsContentProvider;
 import com.android.internal.telephony.InboundSmsHandler;
@@ -781,4 +781,28 @@
 
         verifySmsIntentBroadcasts(0);
     }
+
+    @FlakyTest
+    @Ignore
+    @Test
+    @MediumTest
+    public void testWaitingStateTimeout() throws Exception {
+        transitionFromStartupToIdle();
+
+        // send new SMS to state machine and verify that triggers SMS_DELIVER_ACTION
+        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS,
+                new AsyncResult(null, mSmsMessage, null));
+        waitForMs(100);
+
+        ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(1)).sendBroadcast(
+                intentArgumentCaptor.capture());
+        assertEquals(Telephony.Sms.Intents.SMS_DELIVER_ACTION,
+                intentArgumentCaptor.getAllValues().get(0).getAction());
+        assertEquals("WaitingState", getCurrentState().getName());
+
+        waitForMs(InboundSmsHandler.STATE_TIMEOUT + 300);
+
+        assertEquals("IdleState", getCurrentState().getName());
+    }
 }