[automerger skipped] [automerger] Fixed Invalid Pdu Issue am: 4b938358de am: fc2a2d071a am: ca00d5d151 am: 23de93c197 am: 909c1606d3 am: bfb0076f03 skipped: c8ec5c3e94 am: 63d433cd2b am: 2c39575764  -s ours
am: 6e89e1ecdf  -s ours

Change-Id: I0c46301fd221b50b88c204280009d4067e2655e5
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/OWNERS b/OWNERS
new file mode 100644
index 0000000..b215563
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,7 @@
+amitmahajan@google.com
+breadley@google.com
+fionaxu@google.com
+jackyu@google.com
+jsh@google.com
+rgreenwalt@google.com
+tgunn@google.com
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/CarrierInfoManager.java b/src/java/com/android/internal/telephony/CarrierInfoManager.java
index 96dd8c4..ebf04e8 100644
--- a/src/java/com/android/internal/telephony/CarrierInfoManager.java
+++ b/src/java/com/android/internal/telephony/CarrierInfoManager.java
@@ -16,26 +16,133 @@
 
 package com.android.internal.telephony;
 
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteConstraintException;
+import android.provider.Telephony;
 import android.telephony.ImsiEncryptionInfo;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
 
- /**
+import java.util.Date;
+
+/**
  * This class provides methods to retreive information from the CarrierKeyProvider.
  */
 public class CarrierInfoManager {
-    private static final String TAG = "CarrierInfoManager";
+    private static final String LOG_TAG = "CarrierInfoManager";
 
     /**
      * Returns Carrier specific information that will be used to encrypt the IMSI and IMPI.
      * @param keyType whether the key is being used for WLAN or ePDG.
-     * @return ImsiEncryptionInfo which contains the information including the public key to be
+     * @param mContext
+     * @return ImsiEncryptionInfo which contains the information, including the public key, to be
      *         used for encryption.
      */
-    public static ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType) {
-        //TODO implementation will be done in subsequent CL.
+    public static ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType,
+                                                                     Context mContext) {
+        String mcc = "";
+        String mnc = "";
+        final TelephonyManager telephonyManager =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        String networkOperator = telephonyManager.getNetworkOperator();
+        if (!TextUtils.isEmpty(networkOperator)) {
+            mcc = networkOperator.substring(0, 3);
+            mnc = networkOperator.substring(3);
+            Log.i(LOG_TAG, "using values for mnc, mcc: " + mnc + "," + mcc);
+        } else {
+            Log.e(LOG_TAG, "Invalid networkOperator: " + networkOperator);
+            return null;
+        }
+        Cursor findCursor = null;
+        try {
+            // In the current design, MVNOs are not supported. If we decide to support them,
+            // we'll need to add to this CL.
+            ContentResolver mContentResolver = mContext.getContentResolver();
+            String[] columns = {Telephony.CarrierColumns.PUBLIC_KEY,
+                    Telephony.CarrierColumns.EXPIRATION_TIME,
+                    Telephony.CarrierColumns.KEY_IDENTIFIER};
+            findCursor = mContentResolver.query(Telephony.CarrierColumns.CONTENT_URI, columns,
+                    "mcc=? and mnc=? and key_type=?",
+                    new String[]{mcc, mnc, String.valueOf(keyType)}, null);
+            if (findCursor == null || !findCursor.moveToFirst()) {
+                Log.d(LOG_TAG, "No rows found for keyType: " + keyType);
+                return null;
+            }
+            if (findCursor.getCount() > 1) {
+                Log.e(LOG_TAG, "More than 1 row found for the keyType: " + keyType);
+            }
+            byte[] carrier_key = findCursor.getBlob(0);
+            Date expirationTime = new Date(findCursor.getLong(1));
+            String keyIdentifier = findCursor.getString(2);
+            return new ImsiEncryptionInfo(mcc, mnc, keyType, keyIdentifier, carrier_key,
+                    expirationTime);
+        } catch (IllegalArgumentException e) {
+            Log.e(LOG_TAG, "Bad arguments:" + e);
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Query failed:" + e);
+        } finally {
+            if (findCursor != null) {
+                findCursor.close();
+            }
+        }
         return null;
     }
 
     /**
+     * Inserts or update the Carrier Key in the database
+     * @param imsiEncryptionInfo ImsiEncryptionInfo object.
+     * @param mContext Context.
+     */
+    public static void updateOrInsertCarrierKey(ImsiEncryptionInfo imsiEncryptionInfo,
+                                                Context mContext) {
+        byte[] keyBytes = imsiEncryptionInfo.getPublicKey().getEncoded();
+        ContentResolver mContentResolver = mContext.getContentResolver();
+        // In the current design, MVNOs are not supported. If we decide to support them,
+        // we'll need to add to this CL.
+        ContentValues contentValues = new ContentValues();
+        contentValues.put(Telephony.CarrierColumns.MCC, imsiEncryptionInfo.getMcc());
+        contentValues.put(Telephony.CarrierColumns.MNC, imsiEncryptionInfo.getMnc());
+        contentValues.put(Telephony.CarrierColumns.KEY_TYPE,
+                imsiEncryptionInfo.getKeyType());
+        contentValues.put(Telephony.CarrierColumns.KEY_IDENTIFIER,
+                imsiEncryptionInfo.getKeyIdentifier());
+        contentValues.put(Telephony.CarrierColumns.PUBLIC_KEY, keyBytes);
+        contentValues.put(Telephony.CarrierColumns.EXPIRATION_TIME,
+                imsiEncryptionInfo.getExpirationTime().getTime());
+        try {
+            Log.i(LOG_TAG, "Inserting imsiEncryptionInfo into db");
+            mContentResolver.insert(Telephony.CarrierColumns.CONTENT_URI, contentValues);
+        } catch (SQLiteConstraintException e) {
+            Log.i(LOG_TAG, "Insert failed, updating imsiEncryptionInfo into db");
+            ContentValues updatedValues = new ContentValues();
+            updatedValues.put(Telephony.CarrierColumns.PUBLIC_KEY, keyBytes);
+            updatedValues.put(Telephony.CarrierColumns.EXPIRATION_TIME,
+                    imsiEncryptionInfo.getExpirationTime().getTime());
+            updatedValues.put(Telephony.CarrierColumns.KEY_IDENTIFIER,
+                    imsiEncryptionInfo.getKeyIdentifier());
+            try {
+                int nRows = mContentResolver.update(Telephony.CarrierColumns.CONTENT_URI,
+                        updatedValues,
+                        "mcc=? and mnc=? and key_type=?", new String[]{
+                                imsiEncryptionInfo.getMcc(),
+                                imsiEncryptionInfo.getMnc(),
+                                String.valueOf(imsiEncryptionInfo.getKeyType())});
+                if (nRows == 0) {
+                    Log.d(LOG_TAG, "Error updating values:" + imsiEncryptionInfo);
+                }
+            } catch (Exception ex) {
+                Log.d(LOG_TAG, "Error updating values:" + imsiEncryptionInfo + ex);
+            }
+        }  catch (Exception e) {
+            Log.d(LOG_TAG, "Error inserting/updating values:" + imsiEncryptionInfo + e);
+        }
+    }
+
+    /**
      * Sets the Carrier specific information that will be used to encrypt the IMSI and IMPI.
      * This includes the public key and the key identifier. This information will be stored in the
      * device keystore.
@@ -43,10 +150,12 @@
      *        {@link java.security.PublicKey} and the Key Identifier.
      *        The keyIdentifier Attribute value pair that helps a server locate
      *        the private key to decrypt the permanent identity.
+     * @param mContext Context.
      */
-    public static void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo) {
-        //TODO implementation will be done in subsequent CL.
-        return;
+    public static void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo,
+                                                       Context mContext) {
+        Log.i(LOG_TAG, "inserting carrier key: " + imsiEncryptionInfo);
+        updateOrInsertCarrierKey(imsiEncryptionInfo, mContext);
+        //todo send key to modem. Will be done in a subsequent CL.
     }
-}
-
+}
\ No newline at end of file
diff --git a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
new file mode 100644
index 0000000..66bc529
--- /dev/null
+++ b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
@@ -0,0 +1,551 @@
+/*
+ * 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 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;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.ImsiEncryptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+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.
+ * The class will be instantiated by various Phone implementations.
+ */
+public class CarrierKeyDownloadManager {
+    private static final String LOG_TAG = "CarrierKeyDownloadManager";
+
+    private static final String MCC_MNC_PREF_TAG = "CARRIER_KEY_DM_MCC_MNC";
+
+    private static final int DAY_IN_MILLIS = 24 * 3600 * 1000;
+
+    // Start trying to renew the cert X days before it expires.
+    private static final int DEFAULT_RENEWAL_WINDOW_DAYS = 7;
+
+    /* Intent for downloading the public key */
+    private static final String INTENT_KEY_RENEWAL_ALARM_PREFIX =
+            "com.android.internal.telephony.carrier_key_download_alarm";
+
+    @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_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";
+
+
+    private static final int[] CARRIER_KEY_TYPES = {TelephonyManager.KEY_TYPE_EPDG,
+            TelephonyManager.KEY_TYPE_WLAN};
+    private static final int UNINITIALIZED_KEY_TYPE = -1;
+
+    private final Phone mPhone;
+    private final Context mContext;
+    public final DownloadManager mDownloadManager;
+    private String mURL;
+
+    public CarrierKeyDownloadManager(Phone phone) {
+        mPhone = phone;
+        mContext = phone.getContext();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        filter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
+        filter.addAction(INTENT_KEY_RENEWAL_ALARM_PREFIX + mPhone.getPhoneId());
+        mContext.registerReceiver(mBroadcastReceiver, filter, null, phone);
+        mDownloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
+    }
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            int slotId = mPhone.getPhoneId();
+            if (action.equals(INTENT_KEY_RENEWAL_ALARM_PREFIX + slotId)) {
+                Log.d(LOG_TAG, "Handling key renewal alarm: " + action);
+                handleAlarmOrConfigChange();
+            } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
+                if (slotId == intent.getIntExtra(PhoneConstants.PHONE_KEY,
+                        SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
+                    Log.d(LOG_TAG, "Carrier Config changed: " + action);
+                    handleAlarmOrConfigChange();
+                }
+            } else if (action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
+                Log.d(LOG_TAG, "Download Complete");
+                long carrierKeyDownloadIdentifier =
+                        intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0);
+                String mccMnc = getMccMncSetFromPref();
+                if (isValidDownload(mccMnc)) {
+                    onDownloadComplete(carrierKeyDownloadIdentifier, mccMnc);
+                    onPostDownloadProcessing(carrierKeyDownloadIdentifier);
+                }
+            }
+        }
+    };
+
+    private void onPostDownloadProcessing(long carrierKeyDownloadIdentifier) {
+        resetRenewalAlarm();
+        cleanupDownloadPreferences(carrierKeyDownloadIdentifier);
+    }
+
+    private void handleAlarmOrConfigChange() {
+        if (carrierUsesKeys()) {
+            if (areCarrierKeysAbsentOrExpiring()) {
+                boolean downloadStartedSuccessfully = downloadKey();
+                // if the download was attemped, but not started successfully, and if carriers uses
+                // keys, we'll still want to renew the alarms, and try downloading the key a day
+                // later.
+                if (!downloadStartedSuccessfully) {
+                    resetRenewalAlarm();
+                }
+            } else {
+                return;
+            }
+        } else {
+            // delete any existing alarms.
+            cleanupRenewalAlarms();
+        }
+    }
+
+    private void cleanupDownloadPreferences(long carrierKeyDownloadIdentifier) {
+        Log.d(LOG_TAG, "Cleaning up download preferences: " + carrierKeyDownloadIdentifier);
+        SharedPreferences.Editor editor = getDefaultSharedPreferences(mContext).edit();
+        editor.remove(String.valueOf(carrierKeyDownloadIdentifier));
+        editor.commit();
+    }
+
+    private void cleanupRenewalAlarms() {
+        Log.d(LOG_TAG, "Cleaning up existing renewal alarms");
+        int slotId = mPhone.getPhoneId();
+        Intent intent = new Intent(INTENT_KEY_RENEWAL_ALARM_PREFIX + slotId);
+        PendingIntent carrierKeyDownloadIntent = PendingIntent.getBroadcast(mContext, 0, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+        AlarmManager alarmManager =
+                (AlarmManager) mContext.getSystemService(mContext.ALARM_SERVICE);
+        alarmManager.cancel(carrierKeyDownloadIntent);
+    }
+
+    /**
+     * this method returns the date to be used to decide on when to start downloading the key.
+     * from the carrier.
+     **/
+    @VisibleForTesting
+    public long getExpirationDate()  {
+        long minExpirationDate = Long.MAX_VALUE;
+        for (int key_type : CARRIER_KEY_TYPES) {
+            if (!isKeyEnabled(key_type)) {
+                continue;
+            }
+            ImsiEncryptionInfo imsiEncryptionInfo =
+                    mPhone.getCarrierInfoForImsiEncryption(key_type);
+            if (imsiEncryptionInfo != null && imsiEncryptionInfo.getExpirationTime() != null) {
+                if (minExpirationDate > imsiEncryptionInfo.getExpirationTime().getTime()) {
+                    minExpirationDate = imsiEncryptionInfo.getExpirationTime().getTime();
+                }
+            }
+        }
+
+        // if there are no keys, or expiration date is in the past, or within 7 days, then we
+        // set the alarm to run in a day. Else, we'll set the alarm to run 7 days prior to
+        // expiration.
+        if (minExpirationDate == Long.MAX_VALUE || (minExpirationDate
+                < System.currentTimeMillis() + DEFAULT_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS)) {
+            minExpirationDate = System.currentTimeMillis() + DAY_IN_MILLIS;
+        } 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);
+        Intent intent = new Intent(INTENT_KEY_RENEWAL_ALARM_PREFIX + slotId);
+        PendingIntent carrierKeyDownloadIntent = PendingIntent.getBroadcast(mContext, 0, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+        alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, minExpirationDate,
+                carrierKeyDownloadIntent);
+        Log.d(LOG_TAG, "setRenewelAlarm: action=" + intent.getAction() + " time="
+                + new Date(minExpirationDate));
+    }
+
+    private String getMccMncSetFromPref() {
+        // check if this is a download that we had created. We do this by checking if the
+        // downloadId is stored in the shared prefs.
+        int slotId = mPhone.getPhoneId();
+        SharedPreferences preferences = getDefaultSharedPreferences(mContext);
+        return preferences.getString(MCC_MNC_PREF_TAG + slotId, null);
+    }
+
+    /**
+     * 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.
+     **/
+    @VisibleForTesting
+    public boolean isValidDownload(String mccMnc) {
+        String mccCurrent = "";
+        String mncCurrent = "";
+        String mccSource = "";
+        String mncSource = "";
+
+        String simOperator = getSimOperator();
+        if (TextUtils.isEmpty(simOperator) || TextUtils.isEmpty(mccMnc)) {
+            Log.e(LOG_TAG, "simOperator or mcc/mnc is empty");
+            return false;
+        }
+
+        String[] splitValue = mccMnc.split(SEPARATOR);
+        mccSource = splitValue[0];
+        mncSource = splitValue[1];
+        Log.d(LOG_TAG, "values from sharedPrefs mcc, mnc: " + mccSource + "," + mncSource);
+
+        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)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * This method will try to parse the downloaded information, and persist it in the database.
+     **/
+    private void onDownloadComplete(long carrierKeyDownloadIdentifier, String mccMnc) {
+        Log.d(LOG_TAG, "onDownloadComplete: " + carrierKeyDownloadIdentifier);
+        String jsonStr;
+        DownloadManager.Query query = new DownloadManager.Query();
+        query.setFilterById(carrierKeyDownloadIdentifier);
+        Cursor cursor = mDownloadManager.query(query);
+        InputStream source = null;
+
+        if (cursor == null) {
+            return;
+        }
+        if (cursor.moveToFirst()) {
+            int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
+            if (DownloadManager.STATUS_SUCCESSFUL == cursor.getInt(columnIndex)) {
+                try {
+                    source = new FileInputStream(
+                            mDownloadManager.openDownloadedFile(carrierKeyDownloadIdentifier)
+                                    .getFileDescriptor());
+                    jsonStr = convertToString(source);
+                    parseJsonAndPersistKey(jsonStr, mccMnc);
+                } catch (Exception e) {
+                    Log.e(LOG_TAG, "Error in download:" + carrierKeyDownloadIdentifier
+                            + ". " + e);
+                } finally {
+                    mDownloadManager.remove(carrierKeyDownloadIdentifier);
+                    try {
+                        source.close();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+            Log.d(LOG_TAG, "Completed downloading keys");
+        }
+        cursor.close();
+        return;
+    }
+
+    /**
+     * This method checks if the carrier requires key. We'll read the carrier config to make that
+     * determination.
+     * @return boolean returns true if carrier requires keys, else false.
+     **/
+    private boolean carrierUsesKeys() {
+        CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
+                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (carrierConfigManager == null) {
+            return false;
+        }
+        int subId = mPhone.getSubId();
+        PersistableBundle b = carrierConfigManager.getConfigForSubId(subId);
+        if (b == null) {
+            return false;
+        }
+        mKeyAvailability = b.getInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT);
+        mURL = b.getString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING);
+        if (TextUtils.isEmpty(mURL) || mKeyAvailability == 0) {
+            Log.d(LOG_TAG, "Carrier not enabled or invalid values");
+            return false;
+        }
+        for (int key_type : CARRIER_KEY_TYPES) {
+            if (isKeyEnabled(key_type)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static String convertToString(InputStream is) {
+        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();
+        }
+        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": [ { "certificate": "",
+     *                         "key-type": "WLAN",
+     *                         "key-identifier": ""
+     *                        } ]
+     * @param jsonStr the json string.
+     * @param mccMnc contains the mcc, mnc.
+     */
+    @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 = "";
+            String[] splitValue = mccMnc.split(SEPARATOR);
+            mcc = splitValue[0];
+            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);
+                // 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)) {
+                    type = TelephonyManager.KEY_TYPE_WLAN;
+                } else if (typeString.equals(JSON_TYPE_VALUE_EPDG)) {
+                    type = TelephonyManager.KEY_TYPE_EPDG;
+                }
+                String identifier = key.getString(JSON_IDENTIFIER);
+                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);
+            }
+        }
+    }
+
+    /**
+     * introspects the mKeyAvailability bitmask
+     * @return true if the digit at position k is 1, else false.
+     */
+    @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;
+    }
+
+    /**
+     * Checks whether is the keys are absent or close to expiration. Returns true, if either of
+     * those conditions are true.
+     * @return boolean returns true when keys are absent or close to expiration, else false.
+     */
+    @VisibleForTesting
+    public boolean areCarrierKeysAbsentOrExpiring() {
+        for (int key_type : CARRIER_KEY_TYPES) {
+            if (!isKeyEnabled(key_type)) {
+                continue;
+            }
+            ImsiEncryptionInfo imsiEncryptionInfo =
+                    mPhone.getCarrierInfoForImsiEncryption(key_type);
+            if (imsiEncryptionInfo == null) {
+                Log.d(LOG_TAG, "Key not found for: " + key_type);
+                return true;
+            }
+            Date imsiDate = imsiEncryptionInfo.getExpirationTime();
+            long timeToExpire = imsiDate.getTime() - System.currentTimeMillis();
+            return (timeToExpire < DEFAULT_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS) ? true : false;
+        }
+        return false;
+    }
+
+    private boolean downloadKey() {
+        Log.d(LOG_TAG, "starting download from: " + mURL);
+        String mcc = "";
+        String mnc = "";
+        String simOperator = getSimOperator();
+
+        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");
+            return false;
+        }
+        try {
+            DownloadManager.Request request = new DownloadManager.Request(Uri.parse(mURL));
+            request.setAllowedOverMetered(false);
+            request.setVisibleInDownloadsUi(false);
+            Long carrierKeyDownloadRequestId = mDownloadManager.enqueue(request);
+            SharedPreferences.Editor editor = getDefaultSharedPreferences(mContext).edit();
+
+            String mccMnc = mcc + SEPARATOR + mnc;
+            int slotId = mPhone.getPhoneId();
+            Log.d(LOG_TAG, "storing values in sharedpref mcc, mnc, days: " + mcc + "," + mnc
+                    + "," + carrierKeyDownloadRequestId);
+            editor.putString(MCC_MNC_PREF_TAG + slotId, mccMnc);
+            editor.commit();
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "exception trying to dowload key from url: " + mURL);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 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) {
+        ImsiEncryptionInfo imsiEncryptionInfo = new ImsiEncryptionInfo(mcc, mnc, type, identifier,
+                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 4acb6e3..77a39eb 100644
--- a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
@@ -16,9 +16,9 @@
 
 package com.android.internal.telephony;
 
-import android.app.PendingIntent;
 import android.app.Notification;
 import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -31,8 +31,13 @@
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.util.NotificationChannelController;
 
+import java.util.HashMap;
+import java.util.Map;
+
+
 /**
  * This contains Carrier specific logic based on the states/events
  * managed in ServiceStateTracker.
@@ -45,19 +50,28 @@
     protected static final int CARRIER_EVENT_VOICE_DEREGISTRATION = CARRIER_EVENT_BASE + 2;
     protected static final int CARRIER_EVENT_DATA_REGISTRATION = CARRIER_EVENT_BASE + 3;
     protected static final int CARRIER_EVENT_DATA_DEREGISTRATION = CARRIER_EVENT_BASE + 4;
-    private static final int SHOW_NOTIFICATION = 200;
-    private static final int NOTIFICATION_ID = 1000;
     private static final int UNINITIALIZED_DELAY_VALUE = -1;
-    private int mDelay = UNINITIALIZED_DELAY_VALUE;
     private Phone mPhone;
-    private boolean mIsPhoneRegistered = false;
     private ServiceStateTracker mSST;
 
+    public static final int NOTIFICATION_PREF_NETWORK = 1000;
+    public static final int NOTIFICATION_EMERGENCY_NETWORK = 1001;
+
+    private final Map<Integer, NotificationType> mNotificationTypeMap = new HashMap<>();
+
     public CarrierServiceStateTracker(Phone phone, ServiceStateTracker sst) {
         this.mPhone = phone;
         this.mSST = sst;
         phone.getContext().registerReceiver(mBroadcastReceiver, new IntentFilter(
                 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+        registerNotificationTypes();
+    }
+
+    private void registerNotificationTypes() {
+        mNotificationTypeMap.put(NOTIFICATION_PREF_NETWORK,
+                new PrefNetworkNotification(NOTIFICATION_PREF_NETWORK));
+        mNotificationTypeMap.put(NOTIFICATION_EMERGENCY_NETWORK,
+                new EmergencyNetworkNotification(NOTIFICATION_EMERGENCY_NETWORK));
     }
 
     @Override
@@ -65,19 +79,17 @@
         switch (msg.what) {
             case CARRIER_EVENT_VOICE_REGISTRATION:
             case CARRIER_EVENT_DATA_REGISTRATION:
-                mIsPhoneRegistered = true;
-                handleConfigChanges();
-                break;
             case CARRIER_EVENT_VOICE_DEREGISTRATION:
             case CARRIER_EVENT_DATA_DEREGISTRATION:
-                if (isGlobalModeOrRadioOffOrAirplaneMode() || isPhoneStillRegistered()) {
-                    break;
-                }
-                mIsPhoneRegistered = false;
                 handleConfigChanges();
                 break;
-            case SHOW_NOTIFICATION:
-                sendNotification();
+            case NOTIFICATION_EMERGENCY_NETWORK:
+            case NOTIFICATION_PREF_NETWORK:
+                Rlog.d(LOG_TAG, "sending notification after delay: " + msg.what);
+                NotificationType notificationType = mNotificationTypeMap.get(msg.what);
+                if (notificationType != null) {
+                    sendNotification(notificationType);
+                }
                 break;
         }
     }
@@ -90,48 +102,97 @@
                 || mSST.mSS.getDataRegState() == ServiceState.STATE_IN_SERVICE);
     }
 
+    private boolean isPhoneVoiceRegistered() {
+        if (mSST.mSS == null) {
+            return true; //something has gone wrong, return true and not show the notification.
+        }
+        return (mSST.mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE);
+    }
+
+    private boolean isPhoneRegisteredForWifiCalling() {
+        Rlog.d(LOG_TAG, "isPhoneRegisteredForWifiCalling: " + mPhone.isWifiCallingEnabled());
+        return mPhone.isWifiCallingEnabled();
+    }
+
     /**
-     * Returns true if the preferred network is set to 'Global' or the radio is off or in
-     * Airplane Mode else returns false.
+     * Returns true if the radio is off or in Airplane Mode else returns false.
      */
-    private boolean isGlobalModeOrRadioOffOrAirplaneMode() {
+    @VisibleForTesting
+    public boolean isRadioOffOrAirplaneMode() {
+        Context context = mPhone.getContext();
+        int airplaneMode = -1;
+        try {
+            airplaneMode = Settings.Global.getInt(context.getContentResolver(),
+                    Settings.Global.AIRPLANE_MODE_ON, 0);
+        } catch (Exception e) {
+            Rlog.e(LOG_TAG, "Unable to get AIRPLACE_MODE_ON.");
+            return true;
+        }
+        return (!mSST.isRadioOn() || (airplaneMode != 0));
+    }
+
+    /**
+     * Returns true if the preferred network is set to 'Global'.
+     */
+    private boolean isGlobalMode() {
         Context context = mPhone.getContext();
         int preferredNetworkSetting = -1;
-        int airplaneMode = -1;
-        int subId = mPhone.getSubId();
         try {
             preferredNetworkSetting =
                     android.provider.Settings.Global.getInt(context.getContentResolver(),
-                            android.provider.Settings.Global.PREFERRED_NETWORK_MODE + subId,
-                            Phone.PREFERRED_NT_MODE);
-            airplaneMode = Settings.Global.getInt(context.getContentResolver(),
-                    Settings.Global.AIRPLANE_MODE_ON, 0);
+                            android.provider.Settings.Global.PREFERRED_NETWORK_MODE
+                                    + mPhone.getSubId(), Phone.PREFERRED_NT_MODE);
         } catch (Exception e) {
             Rlog.e(LOG_TAG, "Unable to get PREFERRED_NETWORK_MODE.");
             return true;
         }
-        return ((preferredNetworkSetting == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA) ||
-                !mSST.isRadioOn() || (airplaneMode != 0));
+        return (preferredNetworkSetting == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
+    }
+
+    private void handleConfigChanges() {
+        for (Map.Entry<Integer, NotificationType> entry : mNotificationTypeMap.entrySet()) {
+            NotificationType notificationType = entry.getValue();
+            if (evaluateSendingMessage(notificationType)) {
+                Message notificationMsg = obtainMessage(notificationType.getTypeId(), null);
+                Rlog.i(LOG_TAG, "starting timer for notifications." + notificationType.getTypeId());
+                sendMessageDelayed(notificationMsg, getDelay(notificationType));
+            } else {
+                cancelNotification(notificationType.getTypeId());
+                Rlog.i(LOG_TAG, "canceling notifications: " + notificationType.getTypeId());
+            }
+        }
     }
 
     /**
-     * Contains logic to decide when to create/cancel notifications.
-     */
-    private void handleConfigChanges() {
-        if (mDelay == UNINITIALIZED_DELAY_VALUE) {
-            cancelNotification();
-            return;
-        }
-        // send a notification if the device is registerd to a network.
-        if (mIsPhoneRegistered) {
-            cancelNotification();
-            Rlog.i(LOG_TAG, "canceling all notifications. ");
-        } else {
-            Message notificationMsg;
-            notificationMsg = obtainMessage(SHOW_NOTIFICATION, null);
-            Rlog.i(LOG_TAG, "starting timer for notifications. ");
-            sendMessageDelayed(notificationMsg, mDelay);
-        }
+     * This method adds a level of indirection, and was created so we can unit the class.
+     **/
+    @VisibleForTesting
+    public boolean evaluateSendingMessage(NotificationType notificationType) {
+        return notificationType.sendMessage();
+    }
+
+    /**
+     * This method adds a level of indirection, and was created so we can unit the class.
+     **/
+    @VisibleForTesting
+    public int getDelay(NotificationType notificationType) {
+        return notificationType.getDelay();
+    }
+
+    /**
+     * This method adds a level of indirection, and was created so we can unit the class.
+     **/
+    @VisibleForTesting
+    public Notification.Builder getNotificationBuilder(NotificationType notificationType) {
+        return notificationType.getNotificationBuilder();
+    }
+
+    /**
+     * This method adds a level of indirection, and was created so we can unit the class.
+     **/
+    @VisibleForTesting
+    public NotificationManager getNotificationManager(Context context) {
+        return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
     }
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -139,9 +200,12 @@
         public void onReceive(Context context, Intent intent) {
             CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
                     context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-            PersistableBundle b = carrierConfigManager.getConfig();
-            mDelay = b.getInt(CarrierConfigManager.KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT);
-            Rlog.i(LOG_TAG, "reading time to delay notification: " + mDelay);
+            PersistableBundle b = carrierConfigManager.getConfigForSubId(mPhone.getSubId());
+
+            for (Map.Entry<Integer, NotificationType> entry : mNotificationTypeMap.entrySet()) {
+                NotificationType notificationType = entry.getValue();
+                notificationType.setDelay(b);
+            }
             handleConfigChanges();
         }
     };
@@ -149,56 +213,195 @@
     /**
      * Post a notification to the NotificationManager for changing network type.
      */
-    private void sendNotification() {
-        Context context = mPhone.getContext();
-
-        Rlog.i(LOG_TAG, "w/values: " + "," + mIsPhoneRegistered + "," + mDelay
-                + "," + isGlobalModeOrRadioOffOrAirplaneMode() + "," + mSST.isRadioOn());
-
-        // exit if the network preference is set to Global or if the phone is registered.
-        if (isGlobalModeOrRadioOffOrAirplaneMode() || mIsPhoneRegistered) {
+    @VisibleForTesting
+    public void sendNotification(NotificationType notificationType) {
+        if (!evaluateSendingMessage(notificationType)) {
             return;
         }
 
-        NotificationManager notificationManager = (NotificationManager)
-                context.getSystemService(Context.NOTIFICATION_SERVICE);
-
-
-        Intent notificationIntent = new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS);
-        PendingIntent settingsIntent = PendingIntent.getActivity(context, 0, notificationIntent,
-                PendingIntent.FLAG_ONE_SHOT);
-
-        CharSequence title =
-                context.getText(com.android.internal.R.string.NetworkPreferenceSwitchTitle);
-        CharSequence details =
-                context.getText(com.android.internal.R.string.NetworkPreferenceSwitchSummary);
-
-
-        Notification mNotification = new Notification.Builder(context)
-                .setWhen(System.currentTimeMillis())
+        Context context = mPhone.getContext();
+        Notification.Builder builder = getNotificationBuilder(notificationType);
+        // set some common attributes
+        builder.setWhen(System.currentTimeMillis())
                 .setAutoCancel(true)
                 .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
-                .setContentTitle(title)
                 .setColor(context.getResources().getColor(
-                        com.android.internal.R.color.system_notification_accent_color))
-                .setStyle(new Notification.BigTextStyle().bigText(details))
-                .setContentText(details)
-                .setContentIntent(settingsIntent)
-                .setChannel(NotificationChannelController.CHANNEL_ID_ALERT)
-                .build();
+                       com.android.internal.R.color.system_notification_accent_color));
 
-        notificationManager.notify(NOTIFICATION_ID, mNotification);
+        getNotificationManager(context).notify(notificationType.getTypeId(), builder.build());
     }
 
     /**
      * Cancel notifications if a registration is pending or has been sent.
-     */
-    private void cancelNotification() {
+     **/
+    public void cancelNotification(int notificationId) {
         Context context = mPhone.getContext();
-        mIsPhoneRegistered = true;
-        removeMessages(SHOW_NOTIFICATION);
-        NotificationManager notificationManager = (NotificationManager)
-                context.getSystemService(Context.NOTIFICATION_SERVICE);
-        notificationManager.cancel(NOTIFICATION_ID);
+        removeMessages(notificationId);
+        getNotificationManager(context).cancel(notificationId);
+    }
+
+    /**
+     * Class that defines the different types of notifications.
+     */
+    public interface NotificationType {
+
+        /**
+         * decides if the message should be sent, Returns boolean
+         **/
+        boolean sendMessage();
+
+        /**
+         * returns the interval by which the message is delayed.
+         **/
+        int getDelay();
+
+        /** sets the interval by which the message is delayed.
+         * @param bundle PersistableBundle
+        **/
+        void setDelay(PersistableBundle bundle);
+
+        /**
+         * returns notification type id.
+         **/
+        int getTypeId();
+
+        /**
+         * returns the notification builder, for the notification to be displayed.
+         **/
+        Notification.Builder getNotificationBuilder();
+    }
+
+    /**
+     * Class that defines the network notification, which is shown when the phone cannot camp on
+     * a network, and has 'preferred mode' set to global.
+     */
+    public class PrefNetworkNotification implements NotificationType {
+
+        private final int mTypeId;
+        private int mDelay = UNINITIALIZED_DELAY_VALUE;
+
+        PrefNetworkNotification(int typeId) {
+            this.mTypeId = typeId;
+        }
+
+        /** sets the interval by which the message is delayed.
+         * @param bundle PersistableBundle
+         **/
+        public void setDelay(PersistableBundle bundle) {
+            if (bundle == null) {
+                Rlog.e(LOG_TAG, "bundle is null");
+                return;
+            }
+            this.mDelay = bundle.getInt(
+                    CarrierConfigManager.KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT);
+            Rlog.i(LOG_TAG, "reading time to delay notification emergency: " + mDelay);
+        }
+
+        public int getDelay() {
+            return mDelay;
+        }
+
+        public int getTypeId() {
+            return mTypeId;
+        }
+
+        /**
+         * Contains logic on sending notifications.
+         */
+        public boolean sendMessage() {
+            Rlog.i(LOG_TAG, "PrefNetworkNotification: sendMessage() w/values: "
+                    + "," + isPhoneStillRegistered() + "," + mDelay + "," + isGlobalMode()
+                    + "," + mSST.isRadioOn());
+            if (mDelay == UNINITIALIZED_DELAY_VALUE ||  isPhoneStillRegistered() || isGlobalMode()
+                    || isRadioOffOrAirplaneMode()) {
+                return false;
+            }
+            return true;
+        }
+
+        /**
+         * Builds a partial notificaiton builder, and returns it.
+         */
+        public Notification.Builder getNotificationBuilder() {
+            Context context = mPhone.getContext();
+            Intent notificationIntent = new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS);
+            PendingIntent settingsIntent = PendingIntent.getActivity(context, 0, notificationIntent,
+                    PendingIntent.FLAG_ONE_SHOT);
+            CharSequence title = context.getText(
+                    com.android.internal.R.string.NetworkPreferenceSwitchTitle);
+            CharSequence details = context.getText(
+                    com.android.internal.R.string.NetworkPreferenceSwitchSummary);
+            return new Notification.Builder(context)
+                    .setContentTitle(title)
+                    .setStyle(new Notification.BigTextStyle().bigText(details))
+                    .setContentText(details)
+                    .setChannel(NotificationChannelController.CHANNEL_ID_ALERT)
+                    .setContentIntent(settingsIntent);
+        }
+    }
+
+    /**
+     * Class that defines the emergency notification, which is shown when the user is out of cell
+     * connectivity, but has wifi enabled.
+     */
+    public class EmergencyNetworkNotification implements NotificationType {
+
+        private final int mTypeId;
+        private int mDelay = UNINITIALIZED_DELAY_VALUE;
+
+        EmergencyNetworkNotification(int typeId) {
+            this.mTypeId = typeId;
+        }
+
+        /** sets the interval by which the message is delayed.
+         * @param bundle PersistableBundle
+         **/
+        public void setDelay(PersistableBundle bundle) {
+            if (bundle == null) {
+                Rlog.e(LOG_TAG, "bundle is null");
+                return;
+            }
+            this.mDelay = bundle.getInt(
+                    CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT);
+            Rlog.i(LOG_TAG, "reading time to delay notification emergency: " + mDelay);
+        }
+
+        public int getDelay() {
+            return mDelay;
+        }
+
+        public int getTypeId() {
+            return mTypeId;
+        }
+
+        /**
+         * Contains logic on sending notifications,
+         */
+        public boolean sendMessage() {
+            Rlog.i(LOG_TAG, "EmergencyNetworkNotification: sendMessage() w/values: "
+                    + "," + isPhoneVoiceRegistered() + "," + mDelay + ","
+                    + isPhoneRegisteredForWifiCalling() + "," + mSST.isRadioOn());
+            if (mDelay == UNINITIALIZED_DELAY_VALUE || isPhoneVoiceRegistered()
+                    || !isPhoneRegisteredForWifiCalling()) {
+                return false;
+            }
+            return true;
+        }
+
+        /**
+         * Builds a partial notificaiton builder, and returns it.
+         */
+        public Notification.Builder getNotificationBuilder() {
+            Context context = mPhone.getContext();
+            CharSequence title = context.getText(
+                    com.android.internal.R.string.EmergencyCallWarningTitle);
+            CharSequence details = context.getText(
+                    com.android.internal.R.string.EmergencyCallWarningSummary);
+            return new Notification.Builder(context)
+                    .setContentTitle(title)
+                    .setStyle(new Notification.BigTextStyle().bigText(details))
+                    .setContentText(details)
+                    .setChannel(NotificationChannelController.CHANNEL_ID_WFC);
+        }
     }
 }
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/Connection.java b/src/java/com/android/internal/telephony/Connection.java
index bb92692..245f76c 100644
--- a/src/java/com/android/internal/telephony/Connection.java
+++ b/src/java/com/android/internal/telephony/Connection.java
@@ -311,6 +311,15 @@
     }
 
     /**
+     * Sets the Connection connect time in {@link SystemClock#elapsedRealtime()} format.
+     *
+     * @param connectTimeReal the new connect time.
+     */
+    public void setConnectTimeReal(long connectTimeReal) {
+        mConnectTimeReal = connectTimeReal;
+    }
+
+    /**
      * Connection connect time in elapsedRealtime() format.
      * For outgoing calls: Begins at (DIALING|ALERTING) -> ACTIVE transition.
      * For incoming calls: Begins at (INCOMING|WAITING) -> ACTIVE transition.
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/GsmCdmaCallTracker.java b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
index b91f452..e4e7f35 100755
--- a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
@@ -40,6 +40,7 @@
 import android.text.TextUtils;
 import android.util.EventLog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 
@@ -68,7 +69,8 @@
     private static final int MAX_CONNECTIONS_PER_CALL_CDMA = 1; //only 1 connection allowed per call
 
     //***** Instance Variables
-    private GsmCdmaConnection mConnections[];
+    @VisibleForTesting
+    public GsmCdmaConnection[] mConnections;
     private RegistrantList mVoiceCallEndedRegistrants = new RegistrantList();
     private RegistrantList mVoiceCallStartedRegistrants = new RegistrantList();
 
@@ -168,6 +170,11 @@
         if (mPhone.isPhoneTypeGsm()) {
             mConnections = new GsmCdmaConnection[MAX_CONNECTIONS_GSM];
             mCi.unregisterForCallWaitingInfo(this);
+            // Prior to phone switch to GSM, if CDMA has any emergency call
+            // data will be in disabled state, after switching to GSM enable data.
+            if (mIsInEmergencyCall) {
+                mPhone.mDcTracker.setInternalDataEnabled(true);
+            }
         } else {
             mConnections = new GsmCdmaConnection[MAX_CONNECTIONS_CDMA];
             mPendingCallInEcm = false;
@@ -182,10 +189,9 @@
     private void reset() {
         Rlog.d(LOG_TAG, "reset");
 
-        clearDisconnected();
-
         for (GsmCdmaConnection gsmCdmaConnection : mConnections) {
             if (gsmCdmaConnection != null) {
+                gsmCdmaConnection.onDisconnect(DisconnectCause.ERROR_UNSPECIFIED);
                 gsmCdmaConnection.dispose();
             }
         }
@@ -196,7 +202,7 @@
 
         mConnections = null;
         mPendingMO = null;
-        mState = PhoneConstants.State.IDLE;
+        clearDisconnected();
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 4251567..5053341 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -187,7 +187,7 @@
 
     private int mRilVersion;
     private boolean mBroadcastEmergencyCallStateChanges = false;
-
+    private CarrierKeyDownloadManager mCDM;
     // Constructors
 
     public GsmCdmaPhone(Context context, CommandsInterface ci, PhoneNotifier notifier, int phoneId,
@@ -269,6 +269,7 @@
         mCi.registerForVoiceRadioTechChanged(this, EVENT_VOICE_RADIO_TECH_CHANGED, null);
         mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(
                 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+        mCDM = new CarrierKeyDownloadManager(this);
     }
 
     private void initRatSpecific(int precisePhoneType) {
@@ -1524,12 +1525,12 @@
 
     @Override
     public ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType) {
-        return CarrierInfoManager.getCarrierInfoForImsiEncryption(keyType);
+        return CarrierInfoManager.getCarrierInfoForImsiEncryption(keyType, mContext);
     }
 
     @Override
     public void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo) {
-        CarrierInfoManager.setCarrierInfoForImsiEncryption(imsiEncryptionInfo);
+        CarrierInfoManager.setCarrierInfoForImsiEncryption(imsiEncryptionInfo, mContext);
     }
 
     @Override
@@ -1853,6 +1854,15 @@
     }
 
     @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);
+        }
+    }
+
+    @Override
     public void setUiTTYMode(int uiTtyMode, Message onComplete) {
        if (mImsPhone != null) {
            mImsPhone.setUiTTYMode(uiTtyMode, onComplete);
@@ -2539,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 934ecff..99fd965 100644
--- a/src/java/com/android/internal/telephony/InboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -159,6 +159,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 (5 minutes) */
+    @VisibleForTesting
+    public static final int STATE_TIMEOUT = 5 * 60 * 1000;
+
     /** Wakelock release delay when returning to idle state. */
     private static final int WAKELOCK_TIMEOUT = 3000;
 
@@ -451,6 +462,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
@@ -494,18 +506,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);
@@ -521,6 +556,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/MccTable.java b/src/java/com/android/internal/telephony/MccTable.java
index 51ae81e..5714b29 100644
--- a/src/java/com/android/internal/telephony/MccTable.java
+++ b/src/java/com/android/internal/telephony/MccTable.java
@@ -376,7 +376,14 @@
      */
     private static void setTimezoneFromMccIfNeeded(Context context, int mcc) {
         String timezone = SystemProperties.get(ServiceStateTracker.TIMEZONE_PROPERTY);
-        if (timezone == null || timezone.length() == 0) {
+        // timezone.equals("GMT") will be true and only true if the timezone was
+        // set to a default value by the system server (when starting, system server.
+        // sets the persist.sys.timezone to "GMT" if it's not set)."GMT" is not used by
+        // any code that sets it explicitly (in case where something sets GMT explicitly,
+        // "Etc/GMT" Olsen ID would be used).
+        // TODO(b/64056758): Remove "timezone.equals("GMT")" hack when there's a
+        // better way of telling if the value has been defaulted.
+        if (timezone == null || timezone.length() == 0 || timezone.equals("GMT")) {
             String zoneId = defaultTimeZoneForMcc(mcc);
             if (zoneId != null && zoneId.length() > 0) {
                 // Set time zone based on MCC
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 0238793..8b0c677 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -56,11 +56,13 @@
 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;
 import com.android.ims.ImsManager;
 import com.android.internal.R;
+import com.android.internal.telephony.dataconnection.DataConnectionReasons;
 import com.android.internal.telephony.dataconnection.DcTracker;
 import com.android.internal.telephony.imsphone.ImsPhoneCall;
 import com.android.internal.telephony.test.SimulatedRadioControl;
@@ -1788,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);
@@ -1830,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);
     }
 
@@ -2790,12 +2795,25 @@
 
     /**
      * Report on whether data connectivity is allowed.
+     *
+     * @return True if data is allowed to be established.
      */
     public boolean isDataAllowed() {
         return ((mDcTracker != null) && (mDcTracker.isDataAllowed(null)));
     }
 
     /**
+     * Report on whether data connectivity is allowed.
+     *
+     * @param reasons The reasons that data can/can't be established. This is an output param.
+     * @return True if data is allowed to be established
+     */
+    public boolean isDataAllowed(DataConnectionReasons reasons) {
+        return ((mDcTracker != null) && (mDcTracker.isDataAllowed(reasons)));
+    }
+
+
+    /**
      * Action set from carrier signalling broadcast receivers to enable/disable metered apns.
      */
     public void carrierActionSetMeteredApnsEnabled(boolean 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/RetryManager.java b/src/java/com/android/internal/telephony/RetryManager.java
index 684de9a..23c3498 100644
--- a/src/java/com/android/internal/telephony/RetryManager.java
+++ b/src/java/com/android/internal/telephony/RetryManager.java
@@ -27,8 +27,6 @@
 
 import com.android.internal.telephony.dataconnection.ApnSetting;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Random;
 
@@ -112,6 +110,11 @@
     private static final long DEFAULT_INTER_APN_DELAY_FOR_PROVISIONING = 3000;
 
     /**
+     * The default value (in milliseconds) for retrying APN after disconnect
+     */
+    private static final long DEFAULT_APN_RETRY_AFTER_DISCONNECT_DELAY = 10000;
+
+    /**
      * The value indicating no retry is needed
      */
     public static final long NO_RETRY = -1;
@@ -141,6 +144,12 @@
     private long mFailFastInterApnDelay;
 
     /**
+     * The delay (in milliseconds) for APN retrying after disconnect (e.g. Modem suddenly reports
+     * data call lost)
+     */
+    private long mApnRetryAfterDisconnectDelay;
+
+    /**
      * Modem suggested delay for retrying the current APN
      */
     private long mModemSuggestedDelay = NO_SUGGESTED_RETRY_DELAY;
@@ -337,6 +346,9 @@
             mFailFastInterApnDelay = b.getLong(
                     CarrierConfigManager.KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG,
                     DEFAULT_INTER_APN_DELAY_FOR_PROVISIONING);
+            mApnRetryAfterDisconnectDelay = b.getLong(
+                    CarrierConfigManager.KEY_CARRIER_DATA_CALL_APN_RETRY_AFTER_DISCONNECT_LONG,
+                    DEFAULT_APN_RETRY_AFTER_DISCONNECT_DELAY);
 
             // Load all retry patterns for all different APNs.
             String[] allConfigStrings = b.getStringArray(
@@ -645,44 +657,21 @@
     }
 
     /**
-     * Get the delay between APN setting trying. This is the fixed delay used for APN setting trying
-     * within the same round, comparing to the exponential delay used for different rounds.
-     * @param failFastEnabled True if fail fast mode enabled, which a shorter delay will be used
+     * Get the delay in milliseconds for APN retry after disconnect
      * @return The delay in milliseconds
      */
-    public long getInterApnDelay(boolean failFastEnabled) {
-        return (failFastEnabled) ? mFailFastInterApnDelay : mInterApnDelay;
+    public long getRetryAfterDisconnectDelay() {
+        return mApnRetryAfterDisconnectDelay;
     }
 
     public String toString() {
-        return "mApnType=" + mApnType + " mRetryCount=" + mRetryCount +
-                " mMaxRetryCount=" + mMaxRetryCount + " mCurrentApnIndex=" + mCurrentApnIndex +
-                " mSameApnRtryCount=" + mSameApnRetryCount + " mModemSuggestedDelay=" +
-                mModemSuggestedDelay + " mRetryForever=" + mRetryForever +
-                " mConfig={" + mConfig + "}";
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("  RetryManager");
-        pw.println("***************************************");
-
-        pw.println("    config = " + mConfig);
-        pw.println("    mApnType = " + mApnType);
-        pw.println("    mCurrentApnIndex = " + mCurrentApnIndex);
-        pw.println("    mRetryCount = " + mRetryCount);
-        pw.println("    mMaxRetryCount = " + mMaxRetryCount);
-        pw.println("    mSameApnRetryCount = " + mSameApnRetryCount);
-        pw.println("    mModemSuggestedDelay = " + mModemSuggestedDelay);
-
-        if (mWaitingApns != null) {
-            pw.println("    APN list: ");
-            for (int i = 0; i < mWaitingApns.size(); i++) {
-                pw.println("      [" + i + "]=" + mWaitingApns.get(i));
-            }
-        }
-
-        pw.println("***************************************");
-        pw.flush();
+        if (mConfig == null) return "";
+        return "RetryManager: mApnType=" + mApnType + " mRetryCount=" + mRetryCount
+                + " mMaxRetryCount=" + mMaxRetryCount + " mCurrentApnIndex=" + mCurrentApnIndex
+                + " mSameApnRtryCount=" + mSameApnRetryCount + " mModemSuggestedDelay="
+                + mModemSuggestedDelay + " mRetryForever=" + mRetryForever + " mInterApnDelay="
+                + mInterApnDelay + " mApnRetryAfterDisconnectDelay=" + mApnRetryAfterDisconnectDelay
+                + " mConfig={" + mConfig + "}";
     }
 
     private void log(String s) {
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index d1e8a0c..2ec5101 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -23,6 +23,8 @@
 import static android.telephony.SmsManager.RESULT_ERROR_NO_SERVICE;
 import static android.telephony.SmsManager.RESULT_ERROR_NULL_PDU;
 import static android.telephony.SmsManager.RESULT_ERROR_RADIO_OFF;
+import static android.telephony.SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED;
+import static android.telephony.SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED;
 
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -74,6 +76,7 @@
 import android.widget.TextView;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccController;
@@ -313,7 +316,25 @@
         case EVENT_STOP_SENDING:
         {
             SmsTracker tracker = (SmsTracker) msg.obj;
-            tracker.onFailed(mContext, RESULT_ERROR_LIMIT_EXCEEDED, 0/*errorCode*/);
+            if (msg.arg1 == ConfirmDialogListener.SHORT_CODE_MSG) {
+                if (msg.arg2 == ConfirmDialogListener.NEVER_ALLOW) {
+                    tracker.onFailed(mContext,
+                            RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED, 0/*errorCode*/);
+                    Rlog.d(TAG, "SMSDispatcher: EVENT_STOP_SENDING - "
+                            + "sending SHORT_CODE_NEVER_ALLOWED error code.");
+                } else {
+                    tracker.onFailed(mContext,
+                            RESULT_ERROR_SHORT_CODE_NOT_ALLOWED, 0/*errorCode*/);
+                    Rlog.d(TAG, "SMSDispatcher: EVENT_STOP_SENDING - "
+                            + "sending SHORT_CODE_NOT_ALLOWED error code.");
+                }
+            } else if (msg.arg1 == ConfirmDialogListener.RATE_LIMIT) {
+                tracker.onFailed(mContext, RESULT_ERROR_LIMIT_EXCEEDED, 0/*errorCode*/);
+                Rlog.d(TAG, "SMSDispatcher: EVENT_STOP_SENDING - "
+                        + "sending LIMIT_EXCEEDED error code.");
+            } else {
+                Rlog.e(TAG, "SMSDispatcher: EVENT_STOP_SENDING - unexpected cases.");
+            }
             mPendingTrackerCount--;
             break;
         }
@@ -950,7 +971,8 @@
      *  raw pdu of the status report is in the extended data ("pdu").
      * -param destAddr the destination phone number (for short code confirmation)
      */
-    protected void sendRawPdu(SmsTracker tracker) {
+    @VisibleForTesting
+    public void sendRawPdu(SmsTracker tracker) {
         HashMap map = tracker.getData();
         byte pdu[] = (byte[]) map.get("pdu");
 
@@ -1055,7 +1077,7 @@
 
             // Wait for user confirmation unless the user has set permission to always allow/deny
             int premiumSmsPermission = mUsageMonitor.getPremiumSmsPermission(
-                    tracker.mAppInfo.packageName);
+                    tracker.getAppPackageName());
             if (premiumSmsPermission == SmsUsageMonitor.PREMIUM_SMS_PERMISSION_UNKNOWN) {
                 // First time trying to send to premium SMS.
                 premiumSmsPermission = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER;
@@ -1068,7 +1090,10 @@
 
                 case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW:
                     Rlog.w(TAG, "User denied this app from sending to premium SMS");
-                    sendMessage(obtainMessage(EVENT_STOP_SENDING, tracker));
+                    Message msg = obtainMessage(EVENT_STOP_SENDING, tracker);
+                    msg.arg1 = ConfirmDialogListener.SHORT_CODE_MSG;
+                    msg.arg2 = ConfirmDialogListener.NEVER_ALLOW;
+                    sendMessage(msg);
                     return false;   // reject this message
 
                 case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER:
@@ -1127,11 +1152,13 @@
             return;     // queue limit reached; error was returned to caller
         }
 
-        CharSequence appLabel = getAppLabel(tracker.mAppInfo.packageName, tracker.mUserId);
+        CharSequence appLabel = getAppLabel(tracker.getAppPackageName(), tracker.mUserId);
         Resources r = Resources.getSystem();
         Spanned messageText = Html.fromHtml(r.getString(R.string.sms_control_message, appLabel));
 
-        ConfirmDialogListener listener = new ConfirmDialogListener(tracker, null);
+        // Construct ConfirmDialogListenter for Rate Limit handling
+        ConfirmDialogListener listener = new ConfirmDialogListener(tracker, null,
+                ConfirmDialogListener.RATE_LIMIT);
 
         AlertDialog d = new AlertDialog.Builder(mContext)
                 .setTitle(R.string.sms_control_title)
@@ -1163,7 +1190,7 @@
             detailsId = R.string.sms_short_code_details;
         }
 
-        CharSequence appLabel = getAppLabel(tracker.mAppInfo.packageName, tracker.mUserId);
+        CharSequence appLabel = getAppLabel(tracker.getAppPackageName(), tracker.mUserId);
         Resources r = Resources.getSystem();
         Spanned messageText = Html.fromHtml(r.getString(R.string.sms_short_code_confirm_message,
                 appLabel, tracker.mDestAddress));
@@ -1172,8 +1199,10 @@
                 Context.LAYOUT_INFLATER_SERVICE);
         View layout = inflater.inflate(R.layout.sms_short_code_confirmation_dialog, null);
 
+        // Construct ConfirmDialogListenter for short code message sending
         ConfirmDialogListener listener = new ConfirmDialogListener(tracker,
-                (TextView)layout.findViewById(R.id.sms_short_code_remember_undo_instruction));
+                (TextView) layout.findViewById(R.id.sms_short_code_remember_undo_instruction),
+                ConfirmDialogListener.SHORT_CODE_MSG);
 
 
         TextView messageView = (TextView) layout.findViewById(R.id.sms_short_code_confirm_message);
@@ -1374,6 +1403,14 @@
         }
 
         /**
+         * Get the App package name
+         * @return App package name info
+         */
+        public String getAppPackageName() {
+            return mAppInfo != null ? mAppInfo.packageName : null;
+        }
+
+        /**
          * Update the status of this message if we persisted it
          */
         public void updateSentMessageStatus(Context context, int status) {
@@ -1634,10 +1671,15 @@
         private Button mNegativeButton;
         private boolean mRememberChoice;    // default is unchecked
         private final TextView mRememberUndoInstruction;
+        private int mConfirmationType;  // 0 - Short Code Msg Sending; 1 - Rate Limit Exceeded
+        private static final int SHORT_CODE_MSG = 0; // Short Code Msg
+        private static final int RATE_LIMIT = 1; // Rate Limit Exceeded
+        private static final int NEVER_ALLOW = 1; // Never Allow
 
-        ConfirmDialogListener(SmsTracker tracker, TextView textView) {
+        ConfirmDialogListener(SmsTracker tracker, TextView textView, int confirmationType) {
             mTracker = tracker;
             mRememberUndoInstruction = textView;
+            mConfirmationType = confirmationType;
         }
 
         void setPositiveButton(Button button) {
@@ -1670,18 +1712,23 @@
                 EventLog.writeEvent(EventLogTags.EXP_DET_SMS_DENIED_BY_USER,
                                     mTracker.mAppInfo.applicationInfo == null ?
                                     -1 :  mTracker.mAppInfo.applicationInfo.uid);
-                sendMessage(obtainMessage(EVENT_STOP_SENDING, mTracker));
+                Message msg = obtainMessage(EVENT_STOP_SENDING, mTracker);
+                msg.arg1 = mConfirmationType;
                 if (mRememberChoice) {
                     newSmsPermission = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW;
+                    msg.arg2 = ConfirmDialogListener.NEVER_ALLOW;
                 }
+                sendMessage(msg);
             }
-            setPremiumSmsPermission(mTracker.mAppInfo.packageName, newSmsPermission);
+            setPremiumSmsPermission(mTracker.getAppPackageName(), newSmsPermission);
         }
 
         @Override
         public void onCancel(DialogInterface dialog) {
             Rlog.d(TAG, "dialog dismissed: don't send SMS");
-            sendMessage(obtainMessage(EVENT_STOP_SENDING, mTracker));
+            Message msg = obtainMessage(EVENT_STOP_SENDING, mTracker);
+            msg.arg1 = mConfirmationType;
+            sendMessage(msg);
         }
 
         @Override
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index c4d048d..d3c17dd 100644
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -87,6 +87,7 @@
 import com.android.internal.telephony.uicc.UiccCardApplication;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.util.NotificationChannelController;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.FileDescriptor;
@@ -209,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";
 
@@ -353,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));
+                }
             }
         }
     };
@@ -445,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
@@ -1296,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);
@@ -2819,7 +2836,7 @@
         }
 
         if (hasRejectCauseChanged) {
-            setNotification(mRejectCode == 0 ? CS_REJECT_CAUSE_DISABLED : CS_REJECT_CAUSE_ENABLED);
+            setNotification(CS_REJECT_CAUSE_ENABLED);
         }
 
         if (hasChanged) {
@@ -3181,8 +3198,6 @@
                 + mNeedFixZoneAfterNitz + " zone=" + (zone != null ? zone.getID() : "NULL");
         mTimeZoneLog.log(tmpLog);
 
-        mNeedFixZoneAfterNitz = false;
-
         if (zone != null) {
             log("fixTimeZone: zone != null zone.getID=" + zone.getID());
             if (getAutoTimeZone()) {
@@ -3190,10 +3205,13 @@
             } else {
                 log("fixTimeZone: skip changing zone as getAutoTimeZone was false");
             }
-            saveNitzTimeZone(zone.getID());
+            if (mNeedFixZoneAfterNitz) {
+                saveNitzTimeZone(zone.getID());
+            }
         } else {
             log("fixTimeZone: zone == null, do nothing for zone");
         }
+        mNeedFixZoneAfterNitz = false;
     }
 
     /**
@@ -3312,7 +3330,8 @@
     /**
      * Do not set roaming state in case of oprators considered non-roaming.
      *
-     * Can use mcc or mcc+mnc as item of config_operatorConsideredNonRoaming.
+     * Can use mcc or mcc+mnc as item of
+     * {@link CarrierConfigManager#KEY_NON_ROAMING_OPERATOR_STRING_ARRAY}.
      * For example, 302 or 21407. If mcc or mcc+mnc match with operator,
      * don't set roaming state.
      *
@@ -3321,10 +3340,17 @@
      */
     private boolean isOperatorConsideredNonRoaming(ServiceState s) {
         String operatorNumeric = s.getOperatorNumeric();
-        String[] numericArray = mPhone.getContext().getResources().getStringArray(
-                com.android.internal.R.array.config_operatorConsideredNonRoaming);
-
-        if (numericArray.length == 0 || operatorNumeric == null) {
+        final CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        String[] numericArray = null;
+        if (configManager != null) {
+            PersistableBundle config = configManager.getConfigForSubId(mPhone.getSubId());
+            if (config != null) {
+                numericArray = config.getStringArray(
+                        CarrierConfigManager.KEY_NON_ROAMING_OPERATOR_STRING_ARRAY);
+            }
+        }
+        if (ArrayUtils.isEmpty(numericArray) || operatorNumeric == null) {
             return false;
         }
 
@@ -3338,10 +3364,17 @@
 
     private boolean isOperatorConsideredRoaming(ServiceState s) {
         String operatorNumeric = s.getOperatorNumeric();
-        String[] numericArray = mPhone.getContext().getResources().getStringArray(
-                com.android.internal.R.array.config_sameNamedOperatorConsideredRoaming);
-
-        if (numericArray.length == 0 || operatorNumeric == null) {
+        final CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        String[] numericArray = null;
+        if (configManager != null) {
+            PersistableBundle config = configManager.getConfigForSubId(mPhone.getSubId());
+            if (config != null) {
+                numericArray = config.getStringArray(
+                        CarrierConfigManager.KEY_ROAMING_OPERATOR_STRING_ARRAY);
+            }
+        }
+        if (ArrayUtils.isEmpty(numericArray) || operatorNumeric == null) {
             return false;
         }
 
@@ -3829,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
      *
@@ -3903,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) {
@@ -3929,6 +3971,7 @@
                 .setColor(context.getResources().getColor(
                         com.android.internal.R.color.system_notification_accent_color))
                 .setContentTitle(title)
+                .setStyle(new Notification.BigTextStyle().bigText(details))
                 .setContentText(details)
                 .setChannel(NotificationChannelController.CHANNEL_ID_ALERT)
                 .build();
@@ -3936,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/SmsUsageMonitor.java b/src/java/com/android/internal/telephony/SmsUsageMonitor.java
index 73e9a42..402a5ef 100644
--- a/src/java/com/android/internal/telephony/SmsUsageMonitor.java
+++ b/src/java/com/android/internal/telephony/SmsUsageMonitor.java
@@ -29,8 +29,8 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.telephony.PhoneNumberUtils;
-import android.util.AtomicFile;
 import android.telephony.Rlog;
+import android.util.AtomicFile;
 import android.util.Xml;
 
 import com.android.internal.util.FastXmlSerializer;
@@ -48,10 +48,10 @@
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.regex.Pattern;
 
 /**
@@ -85,7 +85,7 @@
     static final int CATEGORY_STANDARD_SHORT_CODE = 2;
 
     /** Return value from {@link #checkDestination} for possible premium short codes. */
-    static final int CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE = 3;
+    public static final int CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE = 3;
 
     /** Return value from {@link #checkDestination} for premium short codes. */
     static final int CATEGORY_PREMIUM_SHORT_CODE = 4;
diff --git a/src/java/com/android/internal/telephony/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java
index 4a41926..f122cc0 100644
--- a/src/java/com/android/internal/telephony/SubscriptionController.java
+++ b/src/java/com/android/internal/telephony/SubscriptionController.java
@@ -61,6 +61,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
 
 /**
@@ -84,9 +85,13 @@
     static final String LOG_TAG = "SubscriptionController";
     static final boolean DBG = true;
     static final boolean VDBG = false;
+    static final boolean DBG_CACHE = false;
     static final int MAX_LOCAL_LOG_LINES = 500; // TODO: Reduce to 100 when 17678050 is fixed
     private ScLocalLog mLocalLog = new ScLocalLog(MAX_LOCAL_LOG_LINES);
 
+    /* The Cache of Active SubInfoRecord(s) list of currently in use SubInfoRecord(s) */
+    private AtomicReference<List<SubscriptionInfo>> mCacheActiveSubInfoList = new AtomicReference();
+
     /**
      * Copied from android.util.LocalLog with flush() adding flush and line number
      * TODO: Update LocalLog
@@ -590,6 +595,43 @@
                 return null;
             }
 
+            // Get the active subscription info list from the cache if the cache is not null
+            List<SubscriptionInfo> tmpCachedSubList = mCacheActiveSubInfoList.get();
+            if (tmpCachedSubList != null) {
+                if (DBG_CACHE) {
+                    for (SubscriptionInfo si : tmpCachedSubList) {
+                        logd("[getActiveSubscriptionInfoList] Getting Cached subInfo=" + si);
+                    }
+                }
+                return new ArrayList<SubscriptionInfo>(tmpCachedSubList);
+            } else {
+                if (DBG_CACHE) {
+                    logd("[getActiveSubscriptionInfoList] Cached subInfo is null");
+                }
+                return null;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Refresh the cache of SubInfoRecord(s) of the currently inserted SIM(s)
+     */
+    @VisibleForTesting
+    protected void refreshCachedActiveSubscriptionInfoList() {
+
+        // Now that all security checks passes, perform the operation as ourselves.
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            if (!isSubInfoReady()) {
+                if (DBG_CACHE) {
+                    logdl("[refreshCachedActiveSubscriptionInfoList] "
+                            + "Sub Controller not ready ");
+                }
+                return;
+            }
+
             List<SubscriptionInfo> subList = getSubInfo(
                     SubscriptionManager.SIM_SLOT_INDEX + ">=0", null);
 
@@ -597,12 +639,21 @@
                 // FIXME: Unnecessary when an insertion sort is used!
                 subList.sort(SUBSCRIPTION_INFO_COMPARATOR);
 
-                if (VDBG) logdl("[getActiveSubInfoList]- " + subList.size() + " infos return");
+                if (DBG_CACHE) {
+                    logdl("[refreshCachedActiveSubscriptionInfoList]- " + subList.size()
+                            + " infos return");
+                }
             } else {
-                if (DBG) logdl("[getActiveSubInfoList]- no info return");
+                if (DBG_CACHE) logdl("[refreshCachedActiveSubscriptionInfoList]- no info return");
             }
 
-            return subList;
+            if (DBG_CACHE) {
+                for (SubscriptionInfo si : subList) {
+                    logd("[refreshCachedActiveSubscriptionInfoList] Setting Cached subInfo=" + si);
+                }
+            }
+            mCacheActiveSubInfoList.set(subList);
+
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -615,8 +666,6 @@
      */
     @Override
     public int getActiveSubInfoCount(String callingPackage) {
-        if (DBG) logd("[getActiveSubInfoCount]+");
-
         if (!canReadPhoneState(callingPackage, "getActiveSubInfoCount")) {
             return 0;
         }
@@ -627,10 +676,10 @@
             List<SubscriptionInfo> records = getActiveSubscriptionInfoList(
                     mContext.getOpPackageName());
             if (records == null) {
-                if (DBG) logd("[getActiveSubInfoCount] records null");
+                if (VDBG) logd("[getActiveSubInfoCount] records null");
                 return 0;
             }
-            if (DBG) logd("[getActiveSubInfoCount]- count: " + records.size());
+            if (VDBG) logd("[getActiveSubInfoCount]- count: " + records.size());
             return records.size();
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -883,6 +932,9 @@
                         resolver.update(SubscriptionManager.CONTENT_URI, value,
                                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
                                         "=" + Long.toString(subId), null);
+
+                        // Refresh the Cache of Active Subscription Info List
+                        refreshCachedActiveSubscriptionInfoList();
                     }
 
                     if (DBG) logdl("[addSubInfoRecord] Record already exists");
@@ -976,6 +1028,9 @@
                         SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
                                 "=" + Long.toString(subId), null);
 
+                // Refresh the Cache of Active Subscription Info List
+                refreshCachedActiveSubscriptionInfoList();
+
                 if (DBG) logdl("[addSubInfoRecord] sim name = " + nameToSet);
             }
 
@@ -1008,7 +1063,13 @@
         value.put(SubscriptionManager.COLOR, color);
         value.put(SubscriptionManager.SIM_SLOT_INDEX, slotIndex);
         value.put(SubscriptionManager.CARRIER_NAME, "");
-        return resolver.insert(SubscriptionManager.CONTENT_URI, value);
+
+        Uri uri = resolver.insert(SubscriptionManager.CONTENT_URI, value);
+
+        // Refresh the Cache of Active Subscription Info List
+        refreshCachedActiveSubscriptionInfoList();
+
+        return uri;
     }
 
     /**
@@ -1074,6 +1135,10 @@
             int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
                     value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
                     Long.toString(subId), null);
+
+            // Refresh the Cache of Active Subscription Info List
+            refreshCachedActiveSubscriptionInfoList();
+
             notifySubscriptionInfoChanged();
 
             return result;
@@ -1105,6 +1170,10 @@
             int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
                     value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
                             Long.toString(subId), null);
+
+            // Refresh the Cache of Active Subscription Info List
+            refreshCachedActiveSubscriptionInfoList();
+
             notifySubscriptionInfoChanged();
 
             return result;
@@ -1165,6 +1234,10 @@
             int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
                     value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
                     Long.toString(subId), null);
+
+            // Refresh the Cache of Active Subscription Info List
+            refreshCachedActiveSubscriptionInfoList();
+
             notifySubscriptionInfoChanged();
 
             return result;
@@ -1207,6 +1280,10 @@
             result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value,
                     SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
                             + "=" + Long.toString(subId), null);
+
+            // Refresh the Cache of Active Subscription Info List
+            refreshCachedActiveSubscriptionInfoList();
+
             if (DBG) logd("[setDisplayNumber]- update result :" + result);
             notifySubscriptionInfoChanged();
 
@@ -1243,6 +1320,10 @@
             int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
                     value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
                     Long.toString(subId), null);
+
+            // Refresh the Cache of Active Subscription Info List
+            refreshCachedActiveSubscriptionInfoList();
+
             notifySubscriptionInfoChanged();
 
             return result;
@@ -1273,6 +1354,10 @@
 
         int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value,
                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + Long.toString(subId), null);
+
+        // Refresh the Cache of Active Subscription Info List
+        refreshCachedActiveSubscriptionInfoList();
+
         notifySubscriptionInfoChanged();
 
         return result;
@@ -1919,6 +2004,10 @@
         resolver.update(SubscriptionManager.CONTENT_URI, value,
                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
                         "=" + Integer.toString(subId), null);
+
+        // Refresh the Cache of Active Subscription Info List
+        refreshCachedActiveSubscriptionInfoList();
+
         Binder.restoreCallingIdentity(token);
     }
 
diff --git a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
index 519a57b..7e8f51e 100644
--- a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
@@ -388,18 +388,18 @@
     }
 
     private void handleSimLoaded(int slotId) {
-        logd("handleSimStateLoadedInternal: slotId: " + slotId);
+        logd("handleSimLoaded: slotId: " + slotId);
 
         // The SIM should be loaded at this state, but it is possible in cases such as SIM being
         // removed or a refresh RESET that the IccRecords could be null. The right behavior is to
         // not broadcast the SIM loaded.
         IccRecords records = mPhone[slotId].getIccCard().getIccRecords();
         if (records == null) {  // Possibly a race condition.
-            logd("onRecieve: IccRecords null");
+            logd("handleSimLoaded: IccRecords null");
             return;
         }
         if (records.getIccId() == null) {
-            logd("onRecieve: IccID null");
+            logd("handleSimLoaded: IccID null");
             return;
         }
         mIccId[slotId] = records.getIccId();
@@ -431,6 +431,9 @@
                     contentResolver.update(SubscriptionManager.CONTENT_URI, number,
                             SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "="
                                     + Long.toString(subId), null);
+
+                    // refresh Cached Active Subscription Info List
+                    SubscriptionController.getInstance().refreshCachedActiveSubscriptionInfoList();
                 }
 
                 SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(subId);
@@ -450,6 +453,9 @@
                     contentResolver.update(SubscriptionManager.CONTENT_URI, name,
                             SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
                                     + "=" + Long.toString(subId), null);
+
+                    // refresh Cached Active Subscription Info List
+                    SubscriptionController.getInstance().refreshCachedActiveSubscriptionInfoList();
                 }
 
                 /* Update preferred network type and network selection mode on SIM change.
@@ -581,6 +587,9 @@
                     contentResolver.update(SubscriptionManager.CONTENT_URI, value,
                             SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "="
                             + Integer.toString(oldSubInfo.get(0).getSubscriptionId()), null);
+
+                    // refresh Cached Active Subscription Info List
+                    SubscriptionController.getInstance().refreshCachedActiveSubscriptionInfoList();
                 }
             } else {
                 if (mInsertSimState[i] == SIM_NOT_CHANGE) {
@@ -658,6 +667,9 @@
                 contentResolver.update(SubscriptionManager.CONTENT_URI, value,
                         SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "="
                         + Integer.toString(temp.getSubscriptionId()), null);
+
+                // refresh Cached Active Subscription Info List
+                SubscriptionController.getInstance().refreshCachedActiveSubscriptionInfoList();
             }
         }
 
@@ -745,6 +757,9 @@
             hasChanges = true;
             contentResolver.update(SubscriptionManager.CONTENT_URI, values,
                     SubscriptionManager.ICC_ID + "=\"" + embeddedProfile.iccid + "\"", null);
+
+            // refresh Cached Active Subscription Info List
+            SubscriptionController.getInstance().refreshCachedActiveSubscriptionInfoList();
         }
 
         // Remove all remaining subscriptions which have embedded = true. We set embedded to false
@@ -765,6 +780,9 @@
             values.put(SubscriptionManager.IS_EMBEDDED, 0);
             hasChanges = true;
             contentResolver.update(SubscriptionManager.CONTENT_URI, values, whereClause, null);
+
+            // refresh Cached Active Subscription Info List
+            SubscriptionController.getInstance().refreshCachedActiveSubscriptionInfoList();
         }
 
         return hasChanges;
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java b/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
index 7865bc4..3cd804d 100644
--- a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
+++ b/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
@@ -17,7 +17,6 @@
 package com.android.internal.telephony.dataconnection;
 
 import android.app.PendingIntent;
-import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
 import android.net.NetworkConfig;
@@ -536,8 +535,8 @@
         return mConnectionGeneration.get();
     }
 
-    public long getInterApnDelay(boolean failFastEnabled) {
-        return mRetryManager.getInterApnDelay(failFastEnabled || isFastRetryReason());
+    long getRetryAfterDisconnectDelay() {
+        return mRetryManager.getRetryAfterDisconnectDelay();
     }
 
     public static int apnIdForType(int networkType) {
@@ -726,7 +725,7 @@
                 l.dump(fd, pw, args);
             }
             pw.decreaseIndent();
-            pw.println("mRetryManager={" + mRetryManager.toString() + "}");
+            pw.println(mRetryManager);
         }
     }
 }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
index 7c5f503..2898b24 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -411,9 +411,6 @@
                 networkType, NETWORK_TYPE, TelephonyManager.getNetworkTypeName(networkType));
         mNetworkInfo.setRoaming(ss.getDataRoaming());
         mNetworkInfo.setIsAvailable(true);
-        // The network should be by default metered until we find it has NET_CAPABILITY_NOT_METERED
-        // capability.
-        mNetworkInfo.setMetered(true);
 
         addState(mDefaultState);
             addState(mInactiveState, mDefaultState);
@@ -865,6 +862,7 @@
         result.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
 
         if (mApnSetting != null) {
+            ApnSetting securedDunApn = mDct.fetchDunApn();
             for (String type : mApnSetting.types) {
                 if (!mRestrictedNetworkOverride
                         && (mConnectionParams != null && mConnectionParams.mUnmeteredUseOnly)
@@ -881,6 +879,11 @@
                         result.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
                         result.addCapability(NetworkCapabilities.NET_CAPABILITY_CBS);
                         result.addCapability(NetworkCapabilities.NET_CAPABILITY_IA);
+                        // check if this is the DUN apn as well as returned by fetchDunApn().
+                        // If yes, add DUN capability too.
+                        if (mApnSetting.equals(securedDunApn)) {
+                            result.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
+                        }
                         break;
                     }
                     case PhoneConstants.APN_TYPE_DEFAULT: {
@@ -896,7 +899,6 @@
                         break;
                     }
                     case PhoneConstants.APN_TYPE_DUN: {
-                        ApnSetting securedDunApn = mDct.fetchDunApn();
                         if (securedDunApn == null || securedDunApn.equals(mApnSetting)) {
                             result.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
                         }
@@ -933,10 +935,8 @@
                     && !mRestrictedNetworkOverride)
                     || !mApnSetting.isMetered(mPhone)) {
                 result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-                mNetworkInfo.setMetered(false);
             } else {
                 result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-                mNetworkInfo.setMetered(true);
             }
 
             result.maybeMarkCapabilitiesRestricted();
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnectionReasons.java b/src/java/com/android/internal/telephony/dataconnection/DataConnectionReasons.java
index e949acf..e7afdff 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnectionReasons.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnectionReasons.java
@@ -74,6 +74,16 @@
         return mDataDisallowedReasonSet.contains(reason);
     }
 
+    /**
+     * Check if only one disallowed reason prevent data connection.
+     *
+     * @param reason The given reason to check
+     * @return True if the given reason is the only one that prevents data connection
+     */
+    public boolean containsOnly(DataDisallowedReasonType reason) {
+        return mDataDisallowedReasonSet.size() == 1 && contains(reason);
+    }
+
     boolean contains(DataAllowedReasonType reason) {
         return reason == mDataAllowedReason;
     }
@@ -88,7 +98,7 @@
     }
 
     // Disallowed reasons. There could be multiple reasons if data connection is not allowed.
-    enum DataDisallowedReasonType {
+    public enum DataDisallowedReasonType {
         // Soft failure reasons. Normally the reasons from users or policy settings.
         DATA_DISABLED(false),                   // Data is disabled by the user or policy.
         ROAMING_DISABLED(false),                // Data roaming is disabled by the user.
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java b/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java
index 67d91d3..8273dee 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java
@@ -80,6 +80,8 @@
         sCmdToString[RSP_RESET - BASE] = "RSP_RESET";
     }
 
+    ConnectionParams mLastConnectionParams;
+
     // Convert cmd to string or null if unknown
     protected static String cmdToString(int cmd) {
         cmd -= BASE;
@@ -377,9 +379,9 @@
             log("bringUp: apnContext=" + apnContext + "unmeteredUseOnly=" + unmeteredUseOnly
                     + " onCompletedMsg=" + onCompletedMsg);
         }
-        sendMessage(DataConnection.EVENT_CONNECT,
-                new ConnectionParams(apnContext, profileId, rilRadioTechnology, unmeteredUseOnly,
-                        onCompletedMsg, connectionGeneration));
+        mLastConnectionParams = new ConnectionParams(apnContext, profileId, rilRadioTechnology,
+                unmeteredUseOnly, onCompletedMsg, connectionGeneration);
+        sendMessage(DataConnection.EVENT_CONNECT, mLastConnectionParams);
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
index 6daff83..850ccaa 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -84,6 +84,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.SettingsObserver;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataAllowedReasonType;
 import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataDisallowedReasonType;
@@ -216,6 +217,8 @@
 
     private AsyncChannel mReplyAc = new AsyncChannel();
 
+    private final LocalLog mDataRoamingLeakageLog = new LocalLog(50);
+
     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver () {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -316,46 +319,6 @@
                 }
             };
 
-    private static class SettingsObserver extends ContentObserver {
-        final private HashMap<Uri, Integer> mUriEventMap;
-        final private Context mContext;
-        final private Handler mHandler;
-        final private static String TAG = "DcTracker.SettingsObserver";
-
-        SettingsObserver(Context context, Handler handler) {
-            super(null);
-            mUriEventMap = new HashMap<Uri, Integer>();
-            mContext = context;
-            mHandler = handler;
-        }
-
-        void observe(Uri uri, int what) {
-            mUriEventMap.put(uri, what);
-            final ContentResolver resolver = mContext.getContentResolver();
-            resolver.registerContentObserver(uri, false, this);
-        }
-
-        void unobserve() {
-            final ContentResolver resolver = mContext.getContentResolver();
-            resolver.unregisterContentObserver(this);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            Rlog.e(TAG, "Should never be reached.");
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            final Integer what = mUriEventMap.get(uri);
-            if (what != null) {
-                mHandler.obtainMessage(what.intValue()).sendToTarget();
-            } else {
-                Rlog.e(TAG, "No matching event to send for URI=" + uri);
-            }
-        }
-    }
-
     private final SettingsObserver mSettingsObserver;
 
     private void registerSettingsObserver() {
@@ -367,7 +330,7 @@
 
         mSettingsObserver.observe(
                 Settings.Global.getUriFor(Settings.Global.DATA_ROAMING + simSuffix),
-                DctConstants.EVENT_ROAMING_ON);
+                DctConstants.EVENT_ROAMING_SETTING_CHANGE);
         mSettingsObserver.observe(
                 Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
                 DctConstants.EVENT_DEVICE_PROVISIONED_CHANGE);
@@ -2813,8 +2776,10 @@
     // This method is called
     // 1. When the data roaming status changes from non-roaming to roaming.
     // 2. When allowed data roaming settings is changed by the user.
-    private void onDataRoamingOnOrSettingsChanged() {
+    private void onDataRoamingOnOrSettingsChanged(int messageType) {
         if (DBG) log("onDataRoamingOnOrSettingsChanged");
+        // Used to differentiate data roaming turned on vs settings changed.
+        boolean settingChanged = (messageType == DctConstants.EVENT_ROAMING_SETTING_CHANGE);
 
         // Check if the device is actually data roaming
         if (!mPhone.getServiceState().getDataRoaming()) {
@@ -2822,6 +2787,8 @@
             return;
         }
 
+        checkDataRoamingStatus(settingChanged);
+
         if (getDataRoamingEnabled()) {
             if (DBG) log("onDataRoamingOnOrSettingsChanged: setup data on roaming");
 
@@ -2837,6 +2804,22 @@
         }
     }
 
+    // We want to track possible roaming data leakage. Which is, if roaming setting
+    // is disabled, yet we still setup a roaming data connection or have a connected ApnContext
+    // switched to roaming. When this happens, we log it in a local log.
+    private void checkDataRoamingStatus(boolean settingChanged) {
+        if (!settingChanged && !getDataRoamingEnabled()
+                && mPhone.getServiceState().getDataRoaming()) {
+            for (ApnContext apnContext : mApnContexts.values()) {
+                if (apnContext.getState() == DctConstants.State.CONNECTED) {
+                    mDataRoamingLeakageLog.log("PossibleRoamingLeakage "
+                            + " connection params: " + (apnContext.getDcAc() != null
+                            ? apnContext.getDcAc().mLastConnectionParams : ""));
+                }
+            }
+        }
+    }
+
     private void onRadioAvailable() {
         if (DBG) log("onRadioAvailable");
         if (mPhone.getSimulatedRadioControl() != null) {
@@ -2966,7 +2949,7 @@
                 }
 
                 // everything is setup
-                if(TextUtils.equals(apnContext.getApnType(),PhoneConstants.APN_TYPE_DEFAULT)) {
+                if (TextUtils.equals(apnContext.getApnType(), PhoneConstants.APN_TYPE_DEFAULT)) {
                     try {
                         SystemProperties.set(PUPPET_MASTER_RADIO_STRESS_TEST, "true");
                     } catch (RuntimeException ex) {
@@ -2990,6 +2973,8 @@
                 // A connection is setup
                 apnContext.setState(DctConstants.State.CONNECTED);
 
+                checkDataRoamingStatus(false);
+
                 boolean isProvApn = apnContext.isProvisioningApn();
                 final ConnectivityManager cm = ConnectivityManager.from(mPhone.getContext());
                 if (mProvisionBroadcastReceiver != null) {
@@ -3217,7 +3202,7 @@
             // we're not tying up the RIL command channel.
             // This also helps in any external dependency to turn off the context.
             if (DBG) log("onDisconnectDone: attached, ready and retry after disconnect");
-            long delay = apnContext.getInterApnDelay(mFailFast);
+            long delay = apnContext.getRetryAfterDisconnectDelay();
             if (delay > 0) {
                 // Data connection is in IDLE state, so when we reconnect later, we'll rebuild
                 // the waiting APN list, which will also reset/reconfigure the retry manager.
@@ -3801,7 +3786,8 @@
                 break;
 
             case DctConstants.EVENT_ROAMING_ON:
-                onDataRoamingOnOrSettingsChanged();
+            case DctConstants.EVENT_ROAMING_SETTING_CHANGE:
+                onDataRoamingOnOrSettingsChanged(msg.what);
                 break;
 
             case DctConstants.EVENT_DEVICE_PROVISIONED_CHANGE:
@@ -4215,6 +4201,8 @@
         pw.println(" mAutoAttachOnCreation=" + mAutoAttachOnCreation.get());
         pw.println(" mIsScreenOn=" + mIsScreenOn);
         pw.println(" mUniqueIdGenerator=" + mUniqueIdGenerator);
+        pw.println(" mDataRoamingLeakageLog= ");
+        mDataRoamingLeakageLog.dump(fd, pw, args);
         pw.flush();
         pw.println(" ***************************************");
         DcController dcc = mDcc;
diff --git a/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java b/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
index 00ab0f4..6489014 100755
--- a/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
+++ b/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
@@ -523,6 +523,7 @@
 
         switch(msg.what) {
         case EVENT_PBR_LOAD_DONE:
+            log("Loading PBR records done");
             ar = (AsyncResult) msg.obj;
             if (ar.exception == null) {
                 createPbrFile((ArrayList<byte[]>)ar.result);
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index 734a155..45dc0b27 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -712,6 +712,11 @@
     }
 
     @Override
+    public void setTTYMode(int ttyMode, Message onComplete) {
+        mCT.setTtyMode(ttyMode);
+    }
+
+    @Override
     public void setUiTTYMode(int uiTtyMode, Message onComplete) {
         mCT.setUiTTYMode(uiTtyMode, onComplete);
     }
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/ImsPhoneCall.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java
index a011f757..52636f5 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java
@@ -267,6 +267,7 @@
             long conferenceConnectTime = imsPhoneConnection.getConferenceConnectTime();
             if (conferenceConnectTime > 0) {
                 imsPhoneConnection.setConnectTime(conferenceConnectTime);
+                imsPhoneConnection.setConnectTimeReal(imsPhoneConnection.getConnectTimeReal());
             } else {
                 if (DBG) {
                     Rlog.d(LOG_TAG, "merge: conference connect time is 0");
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index 7cf720d..bdb1e11 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -24,7 +24,10 @@
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
+import android.net.NetworkRequest;
 import android.net.NetworkStats;
 import android.net.Uri;
 import android.os.AsyncResult;
@@ -227,6 +230,24 @@
         }
     };
 
+    /**
+     * Tracks whether we are currently monitoring network connectivity for the purpose of warning
+     * the user of an inability to handover from LTE to WIFI for video calls.
+     */
+    private boolean mIsMonitoringConnectivity = false;
+
+    /**
+     * Network callback used to schedule the handover check when a wireless network connects.
+     */
+    private ConnectivityManager.NetworkCallback mNetworkCallback =
+            new ConnectivityManager.NetworkCallback() {
+                @Override
+                public void onAvailable(Network network) {
+                    Rlog.i(LOG_TAG, "Network available: " + network);
+                    scheduleHandoverCheck();
+                }
+            };
+
     //***** Constants
 
     static final int MAX_CONNECTIONS = 7;
@@ -306,6 +327,7 @@
     private ImsCall mCallExpectedToResume = null;
     private boolean mAllowEmergencyVideoCalls = false;
     private boolean mIgnoreDataEnabledChangedForVideoCalls = false;
+    private boolean mIsViLteDataMetered = false;
 
     /**
      * Listeners to changes in the phone state.  Intended for use by other interested IMS components
@@ -579,6 +601,22 @@
     private boolean mNotifyHandoverVideoFromWifiToLTE = false;
 
     /**
+     * Carrier configuration option which determines whether the carrier wants to inform the user
+     * when a video call is handed over from LTE to WIFI.
+     * See {@link CarrierConfigManager#KEY_NOTIFY_HANDOVER_VIDEO_FROM_LTE_TO_WIFI_BOOL} for more
+     * information.
+     */
+    private boolean mNotifyHandoverVideoFromLTEToWifi = false;
+
+    /**
+     * When {@code} false, indicates that no handover from LTE to WIFI has occurred during the start
+     * of the call.
+     * When {@code true}, indicates that the start of call handover from LTE to WIFI has been
+     * attempted (it may have suceeded or failed).
+     */
+    private boolean mHasPerformedStartOfCallHandover = false;
+
+    /**
      * Carrier configuration option which determines whether the carrier supports the
      * {@link VideoProfile#STATE_PAUSED} signalling.
      * See {@link CarrierConfigManager#KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL} for more information.
@@ -985,6 +1023,16 @@
             return;
         }
 
+        updateCarrierConfigCache(carrierConfig);
+    }
+
+    /**
+     * Updates the local carrier config cache from a bundle obtained from the carrier config
+     * manager.  Also supports unit testing by injecting configuration at test time.
+     * @param carrierConfig The config bundle.
+     */
+    @VisibleForTesting
+    public void updateCarrierConfigCache(PersistableBundle carrierConfig) {
         mAllowEmergencyVideoCalls =
                 carrierConfig.getBoolean(CarrierConfigManager.KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL);
         mTreatDowngradedVideoCallsAsVideoCalls =
@@ -1001,9 +1049,13 @@
         mSupportDowngradeVtToAudio = carrierConfig.getBoolean(
                 CarrierConfigManager.KEY_SUPPORT_DOWNGRADE_VT_TO_AUDIO_BOOL);
         mNotifyHandoverVideoFromWifiToLTE = carrierConfig.getBoolean(
-                CarrierConfigManager.KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL);
+                CarrierConfigManager.KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL);
+        mNotifyHandoverVideoFromLTEToWifi = carrierConfig.getBoolean(
+                CarrierConfigManager.KEY_NOTIFY_HANDOVER_VIDEO_FROM_LTE_TO_WIFI_BOOL);
         mIgnoreDataEnabledChangedForVideoCalls = carrierConfig.getBoolean(
                 CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS);
+        mIsViLteDataMetered = carrierConfig.getBoolean(
+                CarrierConfigManager.KEY_VILTE_DATA_IS_METERED_BOOL);
         mSupportPauseVideo = carrierConfig.getBoolean(
                 CarrierConfigManager.KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL);
 
@@ -1449,7 +1501,27 @@
     }
 
     //***** Called from ImsPhone
+    /**
+     * Set the TTY mode. This is the actual tty mode (varies depending on peripheral status)
+     */
+    public void setTtyMode(int ttyMode) {
+        if (mImsManager == null) {
+            Log.w(LOG_TAG, "ImsManager is null when setting TTY mode");
+            return;
+        }
 
+        try {
+            mImsManager.setTtyMode(ttyMode);
+        } catch (ImsException e) {
+            loge("setTtyMode : " + e);
+            retryGetImsService();
+        }
+    }
+
+    /**
+     * Sets the UI TTY mode. This is the preferred TTY mode that the user sets in the call
+     * settings screen.
+     */
     public void setUiTTYMode(int uiTtyMode, Message onComplete) {
         if (mImsManager == null) {
             mPhone.sendErrorResponse(onComplete, getImsManagerIsNullException());
@@ -1459,7 +1531,7 @@
         try {
             mImsManager.setUiTTYMode(mPhone.getContext(), uiTtyMode, onComplete);
         } catch (ImsException e) {
-            loge("setTTYMode : " + e);
+            loge("setUITTYMode : " + e);
             mPhone.sendErrorResponse(onComplete, e);
             retryGetImsService();
         }
@@ -1825,10 +1897,17 @@
         return code;
     }
 
-    private int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) {
+    /**
+     * Maps an {@link ImsReasonInfo} reason code to a {@link DisconnectCause} cause code.
+     * The {@link Call.State} provided is the state of the call prior to disconnection.
+     * @param reasonInfo the {@link ImsReasonInfo} for the disconnection.
+     * @param callState The {@link Call.State} prior to disconnection.
+     * @return The {@link DisconnectCause} code.
+     */
+    @VisibleForTesting
+    public int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo, Call.State callState) {
         int cause = DisconnectCause.ERROR_UNSPECIFIED;
 
-        //int type = reasonInfo.getReasonType();
         int code = maybeRemapReasonCode(reasonInfo);
         switch (code) {
             case ImsReasonInfo.CODE_SIP_BAD_ADDRESS:
@@ -1884,10 +1963,18 @@
             case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE:
                 return DisconnectCause.TIMED_OUT;
 
-            case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY:
             case ImsReasonInfo.CODE_LOCAL_POWER_OFF:
                 return DisconnectCause.POWER_OFF;
 
+            case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY:
+            case ImsReasonInfo.CODE_LOW_BATTERY: {
+                if (callState == Call.State.DIALING) {
+                    return DisconnectCause.DIAL_LOW_BATTERY;
+                } else {
+                    return DisconnectCause.LOW_BATTERY;
+                }
+            }
+
             case ImsReasonInfo.CODE_FDN_BLOCKED:
                 return DisconnectCause.FDN_BLOCKED;
 
@@ -1978,13 +2065,18 @@
             processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
                     DisconnectCause.NOT_DISCONNECTED);
 
-            if (mNotifyVtHandoverToWifiFail &&
-                    !imsCall.isWifiCall() && imsCall.isVideoCall() && isWifiConnected()) {
-                // Schedule check to see if handover succeeded.
-                sendMessageDelayed(obtainMessage(EVENT_CHECK_FOR_WIFI_HANDOVER, imsCall),
-                        HANDOVER_TO_WIFI_TIMEOUT_MS);
+            if (mNotifyVtHandoverToWifiFail && imsCall.isVideoCall() && !imsCall.isWifiCall()) {
+                if (isWifiConnected()) {
+                    // Schedule check to see if handover succeeded.
+                    sendMessageDelayed(obtainMessage(EVENT_CHECK_FOR_WIFI_HANDOVER, imsCall),
+                            HANDOVER_TO_WIFI_TIMEOUT_MS);
+                } else {
+                    // No wifi connectivity, so keep track of network availability for potential
+                    // handover.
+                    registerForConnectivityChanges();
+                }
             }
-
+            mHasPerformedStartOfCallHandover = false;
             mMetrics.writeOnImsCallStarted(mPhone.getPhoneId(), imsCall.getCallSession());
         }
 
@@ -2035,8 +2127,18 @@
                     return;
                 } else {
                     mPendingMO = null;
-                    int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
                     ImsPhoneConnection conn = findConnection(imsCall);
+                    Call.State callState;
+                    if (conn != null) {
+                        callState = conn.getState();
+                    } else {
+                        // Need to fall back in case connection is null; it shouldn't be, but a sane
+                        // fallback is to assume we're dialing.  This state is only used to
+                        // determine which disconnect string to show in the case of a low battery
+                        // disconnect.
+                        callState = Call.State.DIALING;
+                    }
+                    int cause = getDisconnectCauseFromReasonInfo(reasonInfo, callState);
 
                     if(conn != null) {
                         conn.setPreciseDisconnectCause(
@@ -2054,8 +2156,18 @@
         public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
             if (DBG) log("onCallTerminated reasonCode=" + reasonInfo.getCode());
 
-            int cause = getDisconnectCauseFromReasonInfo(reasonInfo);
             ImsPhoneConnection conn = findConnection(imsCall);
+            Call.State callState;
+            if (conn != null) {
+                callState = conn.getState();
+            } else {
+                // Connection shouldn't be null, but if it is, we can assume the call was active.
+                // This call state is only used for determining which disconnect message to show in
+                // the case of the device's battery being low resulting in a call drop.
+                callState = Call.State.ACTIVE;
+            }
+            int cause = getDisconnectCauseFromReasonInfo(reasonInfo, callState);
+
             if (DBG) log("cause = " + cause + " conn = " + conn);
 
             if (conn != null) {
@@ -2473,33 +2585,70 @@
             ImsReasonInfo reasonInfo) {
             if (DBG) {
                 log("onCallHandover ::  srcAccessTech=" + srcAccessTech + ", targetAccessTech=" +
-                    targetAccessTech + ", reasonInfo=" + reasonInfo);
+                        targetAccessTech + ", reasonInfo=" + reasonInfo);
             }
 
             // Only consider it a valid handover to WIFI if the source radio tech is known.
             boolean isHandoverToWifi = srcAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN
                     && srcAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
                     && targetAccessTech == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
-            if (isHandoverToWifi) {
-                // If we handed over to wifi successfully, don't check for failure in the future.
-                removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);
-            }
-
             // Only consider it a handover from WIFI if the source and target radio tech is known.
-            boolean isHandoverFromWifi = srcAccessTech == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
-                    && targetAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN
-                    && targetAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
-            if (mNotifyHandoverVideoFromWifiToLTE && isHandoverFromWifi && imsCall.isVideoCall()) {
-                log("onCallHandover :: notifying of WIFI to LTE handover.");
-                ImsPhoneConnection conn = findConnection(imsCall);
-                if (conn != null) {
-                    conn.onConnectionEvent(
-                            TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE, null);
-                } else {
-                    loge("onCallHandover :: failed to notify of handover; connection is null.");
+            boolean isHandoverFromWifi =
+                    srcAccessTech == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
+                            && targetAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN
+                            && targetAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
+
+            ImsPhoneConnection conn = findConnection(imsCall);
+            if (conn != null) {
+                if (conn.getDisconnectCause() == DisconnectCause.NOT_DISCONNECTED) {
+                    if (isHandoverToWifi) {
+                        removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);
+
+                        if (mNotifyHandoverVideoFromLTEToWifi && mHasPerformedStartOfCallHandover) {
+                            // This is a handover which happened mid-call (ie not the start of call
+                            // handover from LTE to WIFI), so we'll notify the InCall UI.
+                            conn.onConnectionEvent(
+                                    TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_LTE_TO_WIFI, null);
+                        }
+
+                        // We are on WIFI now so no need to get notified of network availability.
+                        unregisterForConnectivityChanges();
+                    } else if (isHandoverFromWifi && imsCall.isVideoCall()) {
+                        // A video call just dropped from WIFI to LTE; we want to be informed if a
+                        // new WIFI
+                        // network comes into range.
+                        registerForConnectivityChanges();
+                    }
                 }
+
+                if (isHandoverFromWifi && imsCall.isVideoCall()) {
+                    if (mNotifyHandoverVideoFromWifiToLTE && mIsDataEnabled) {
+                        if (conn.getDisconnectCause() == DisconnectCause.NOT_DISCONNECTED) {
+                            log("onCallHandover :: notifying of WIFI to LTE handover.");
+                            conn.onConnectionEvent(
+                                    TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE, null);
+                        } else {
+                            // Call has already had a disconnect request issued by the user or is
+                            // in the process of disconnecting; do not inform the UI of this as it
+                            // is not relevant.
+                            log("onCallHandover :: skip notify of WIFI to LTE handover for "
+                                    + "disconnected call.");
+                        }
+                    }
+
+                    if (!mIsDataEnabled && mIsViLteDataMetered) {
+                        // Call was downgraded from WIFI to LTE and data is metered; downgrade the
+                        // call now.
+                        downgradeVideoCall(ImsReasonInfo.CODE_WIFI_LOST, conn);
+                    }
+                }
+            } else {
+                loge("onCallHandover :: connection null.");
             }
 
+            if (!mHasPerformedStartOfCallHandover) {
+                mHasPerformedStartOfCallHandover = true;
+            }
             mMetrics.writeOnImsCallHandoverEvent(mPhone.getPhoneId(),
                     TelephonyCallSession.Event.Type.IMS_CALL_HANDOVER, imsCall.getCallSession(),
                     srcAccessTech, targetAccessTech, reasonInfo);
@@ -2525,11 +2674,20 @@
                 // If we know we failed to handover, don't check for failure in the future.
                 removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);
 
+                if (imsCall.isVideoCall()
+                        && conn.getDisconnectCause() == DisconnectCause.NOT_DISCONNECTED) {
+                    // Start listening for a WIFI network to come into range for potential handover.
+                    registerForConnectivityChanges();
+                }
+
                 if (mNotifyVtHandoverToWifiFail) {
                     // Only notify others if carrier config indicates to do so.
                     conn.onHandoverToWifiFailed();
                 }
             }
+            if (!mHasPerformedStartOfCallHandover) {
+                mHasPerformedStartOfCallHandover = true;
+            }
         }
 
         @Override
@@ -2607,6 +2765,8 @@
         public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
             if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode());
             removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);
+            mHasPerformedStartOfCallHandover = false;
+            unregisterForConnectivityChanges();
 
             if (imsCall == mUssdSession) {
                 mUssdSession = null;
@@ -2875,12 +3035,24 @@
             case EVENT_CHECK_FOR_WIFI_HANDOVER:
                 if (msg.obj instanceof ImsCall) {
                     ImsCall imsCall = (ImsCall) msg.obj;
+                    if (imsCall != mForegroundCall.getImsCall()) {
+                        Rlog.i(LOG_TAG, "handoverCheck: no longer FG; check skipped.");
+                        unregisterForConnectivityChanges();
+                        // Handover check and its not the foreground call any more.
+                        return;
+                    }
                     if (!imsCall.isWifiCall()) {
                         // Call did not handover to wifi, notify of handover failure.
                         ImsPhoneConnection conn = findConnection(imsCall);
                         if (conn != null) {
+                            Rlog.i(LOG_TAG, "handoverCheck: handover failed.");
                             conn.onHandoverToWifiFailed();
                         }
+
+                        if (imsCall.isVideoCall()
+                                && conn.getDisconnectCause() == DisconnectCause.NOT_DISCONNECTED) {
+                            registerForConnectivityChanges();
+                        }
                     }
                 }
                 break;
@@ -3332,27 +3504,46 @@
         ImsManager.getInstance(mPhone.getContext(), mPhone.getPhoneId()).setDataEnabled(enabled);
         mIsDataEnabled = enabled;
 
-        if (mIgnoreDataEnabledChangedForVideoCalls) {
-            log("Ignore data " + ((enabled) ? "enabled" : "disabled") + " due to carrier policy.");
+        if (!mIsViLteDataMetered) {
+            log("Ignore data " + ((enabled) ? "enabled" : "disabled") + " - carrier policy "
+                    + "indicates that data is not metered for ViLTE calls.");
             return;
         }
 
-        if (mIgnoreDataEnabledChangedForVideoCalls) {
-            log("Ignore data " + ((enabled) ? "enabled" : "disabled") + " due to carrier policy.");
-            return;
+        // Inform connections that data has been disabled to ensure we turn off video capability
+        // if this is an LTE call.
+        for (ImsPhoneConnection conn : mConnections) {
+            conn.handleDataEnabledChange(enabled);
         }
 
+        int reasonCode;
+        if (reason == DataEnabledSettings.REASON_POLICY_DATA_ENABLED) {
+            reasonCode = ImsReasonInfo.CODE_DATA_LIMIT_REACHED;
+        } else if (reason == DataEnabledSettings.REASON_USER_DATA_ENABLED) {
+            reasonCode = ImsReasonInfo.CODE_DATA_DISABLED;
+        } else {
+            // Unexpected code, default to data disabled.
+            reasonCode = ImsReasonInfo.CODE_DATA_DISABLED;
+        }
+
+        // Potentially send connection events so the InCall UI knows that video calls are being
+        // downgraded due to data being enabled/disabled.
+        maybeNotifyDataDisabled(enabled, reasonCode);
+        // Handle video state changes required as a result of data being enabled/disabled.
+        handleDataEnabledChange(enabled, reasonCode);
+
+        // We do not want to update the ImsConfig for REASON_REGISTERED, since it can happen before
+        // the carrier config has loaded and will deregister IMS.
+        if (!mShouldUpdateImsConfigOnDisconnect
+                && reason != DataEnabledSettings.REASON_REGISTERED) {
+            // This will call into updateVideoCallFeatureValue and eventually all clients will be
+            // asynchronously notified that the availability of VT over LTE has changed.
+            ImsManager.updateImsServiceConfig(mPhone.getContext(), mPhone.getPhoneId(), true);
+        }
+    }
+
+    private void maybeNotifyDataDisabled(boolean enabled, int reasonCode) {
         if (!enabled) {
-            int reasonCode;
-            if (reason == DataEnabledSettings.REASON_POLICY_DATA_ENABLED) {
-                reasonCode = ImsReasonInfo.CODE_DATA_LIMIT_REACHED;
-            } else if (reason == DataEnabledSettings.REASON_USER_DATA_ENABLED) {
-                reasonCode = ImsReasonInfo.CODE_DATA_DISABLED;
-            } else {
-                // Unexpected code, default to data disabled.
-                reasonCode = ImsReasonInfo.CODE_DATA_DISABLED;
-            }
-
             // If data is disabled while there are ongoing VT calls which are not taking place over
             // wifi, then they should be disconnected to prevent the user from incurring further
             // data charges.
@@ -3372,27 +3563,38 @@
                             conn.onConnectionEvent(
                                     TelephonyManager.EVENT_DOWNGRADE_DATA_LIMIT_REACHED, null);
                         }
-                        modifyVideoCall(imsCall, VideoProfile.STATE_AUDIO_ONLY);
-                    } else if (mSupportPauseVideo) {
-                        // The carrier supports video pause signalling, so pause the video.
-                        mShouldUpdateImsConfigOnDisconnect = true;
-                        conn.pauseVideo(VideoPauseTracker.SOURCE_DATA_ENABLED);
-                    } else {
-                        // At this point the only choice we have is to terminate the call.
-                        try {
-                            imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED, reasonCode);
-                        } catch (ImsException ie) {
-                            loge("Couldn't terminate call " + imsCall);
-                        }
                     }
                 }
             }
+        }
+    }
+
+    /**
+     * Handles changes to the enabled state of mobile data.
+     * When data is disabled, handles auto-downgrade of video calls over LTE.
+     * When data is enabled, handled resuming of video calls paused when data was disabled.
+     * @param enabled {@code true} if mobile data is enabled, {@code false} if mobile data is
+     *                            disabled.
+     * @param reasonCode The {@link ImsReasonInfo} code for the data enabled state change.
+     */
+    private void handleDataEnabledChange(boolean enabled, int reasonCode) {
+        if (!enabled) {
+            // If data is disabled while there are ongoing VT calls which are not taking place over
+            // wifi, then they should be disconnected to prevent the user from incurring further
+            // data charges.
+            for (ImsPhoneConnection conn : mConnections) {
+                ImsCall imsCall = conn.getImsCall();
+                if (imsCall != null && imsCall.isVideoCall() && !imsCall.isWifiCall()) {
+                    log("handleDataEnabledChange - downgrading " + conn);
+                    downgradeVideoCall(reasonCode, conn);
+                }
+            }
         } else if (mSupportPauseVideo) {
             // Data was re-enabled, so un-pause previously paused video calls.
             for (ImsPhoneConnection conn : mConnections) {
                 // If video is paused, check to see if there are any pending pauses due to enabled
                 // state of data changing.
-                log("onDataEnabledChanged - resuming " + conn);
+                log("handleDataEnabledChange - resuming " + conn);
                 if (VideoProfile.isPaused(conn.getVideoState()) &&
                         conn.wasVideoPausedFromSource(VideoPauseTracker.SOURCE_DATA_ENABLED)) {
                     // The data enabled state was a cause of a pending pause, so potentially
@@ -3402,15 +3604,40 @@
             }
             mShouldUpdateImsConfigOnDisconnect = false;
         }
+    }
 
+    /**
+     * Handles downgrading a video call.  The behavior depends on carrier capabilities; we will
+     * attempt to take one of the following actions (in order of precedence):
+     * 1. If supported by the carrier, the call will be downgraded to an audio-only call.
+     * 2. If the carrier supports video pause signalling, the video will be paused.
+     * 3. The call will be disconnected.
+     * @param reasonCode The {@link ImsReasonInfo} reason code for the downgrade.
+     * @param conn The {@link ImsPhoneConnection} to downgrade.
+     */
+    private void downgradeVideoCall(int reasonCode, ImsPhoneConnection conn) {
+        ImsCall imsCall = conn.getImsCall();
+        if (imsCall != null) {
+            if (conn.hasCapabilities(
+                    Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL |
+                            Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE)) {
 
-        // We do not want to update the ImsConfig for REASON_REGISTERED, since it can happen before
-        // the carrier config has loaded and will deregister IMS.
-        if (!mShouldUpdateImsConfigOnDisconnect
-                && reason != DataEnabledSettings.REASON_REGISTERED) {
-            // This will call into updateVideoCallFeatureValue and eventually all clients will be
-            // asynchronously notified that the availability of VT over LTE has changed.
-            ImsManager.updateImsServiceConfig(mPhone.getContext(), mPhone.getPhoneId(), true);
+                // 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 && 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 {
+                // At this point the only choice we have is to terminate the call.
+                try {
+                    imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED, reasonCode);
+                } catch (ImsException ie) {
+                    loge("Couldn't terminate call " + imsCall);
+                }
+            }
         }
     }
 
@@ -3437,12 +3664,75 @@
     }
 
     /**
+     * Registers for changes to network connectivity.  Specifically requests the availability of new
+     * WIFI networks which an IMS video call could potentially hand over to.
+     */
+    private void registerForConnectivityChanges() {
+        if (mIsMonitoringConnectivity || !mNotifyVtHandoverToWifiFail) {
+            return;
+        }
+        ConnectivityManager cm = (ConnectivityManager) mPhone.getContext()
+                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        if (cm != null) {
+            Rlog.i(LOG_TAG, "registerForConnectivityChanges");
+            NetworkCapabilities capabilities = new NetworkCapabilities();
+            capabilities.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+            NetworkRequest.Builder builder = new NetworkRequest.Builder();
+            builder.setCapabilities(capabilities);
+            cm.registerNetworkCallback(builder.build(), mNetworkCallback);
+            mIsMonitoringConnectivity = true;
+        }
+    }
+
+    /**
+     * Unregister for connectivity changes.  Will be called when a call disconnects or if the call
+     * ends up handing over to WIFI.
+     */
+    private void unregisterForConnectivityChanges() {
+        if (!mIsMonitoringConnectivity || !mNotifyVtHandoverToWifiFail) {
+            return;
+        }
+        ConnectivityManager cm = (ConnectivityManager) mPhone.getContext()
+                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        if (cm != null) {
+            Rlog.i(LOG_TAG, "unregisterForConnectivityChanges");
+            cm.unregisterNetworkCallback(mNetworkCallback);
+            mIsMonitoringConnectivity = false;
+        }
+    }
+
+    /**
+     * If the foreground call is a video call, schedule a handover check if one is not already
+     * scheduled.  This method is intended ONLY for use when scheduling to watch for mid-call
+     * handovers.
+     */
+    private void scheduleHandoverCheck() {
+        ImsCall fgCall = mForegroundCall.getImsCall();
+        ImsPhoneConnection conn = mForegroundCall.getFirstConnection();
+        if (!mNotifyVtHandoverToWifiFail || fgCall == null || !fgCall.isVideoCall() || conn == null
+                || conn.getDisconnectCause() != DisconnectCause.NOT_DISCONNECTED) {
+            return;
+        }
+
+        if (!hasMessages(EVENT_CHECK_FOR_WIFI_HANDOVER)) {
+            Rlog.i(LOG_TAG, "scheduleHandoverCheck: schedule");
+            sendMessageDelayed(obtainMessage(EVENT_CHECK_FOR_WIFI_HANDOVER, fgCall),
+                    HANDOVER_TO_WIFI_TIMEOUT_MS);
+        }
+    }
+
+    /**
      * @return {@code true} if downgrading of a video call to audio is supported.
      */
     public boolean isCarrierDowngradeOfVtCallSupported() {
         return mSupportDowngradeVtToAudio;
     }
 
+    @VisibleForTesting
+    public void setDataEnabled(boolean isDataEnabled) {
+        mIsDataEnabled = isDataEnabled;
+    }
+
     private void handleFeatureCapabilityChanged(int serviceClass,
             int[] enabledFeatures, int[] disabledFeatures) {
         if (serviceClass == ImsServiceClass.MMTEL) {
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
index a180862..9800a44 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
@@ -111,6 +111,14 @@
      */
     private boolean mIsMergeInProcess = false;
 
+    /**
+     * Used as an override to determine whether video is locally available for this call.
+     * This allows video availability to be overridden in the case that the modem says video is
+     * currently available, but mobile data is off and the carrier is metering data for video
+     * calls.
+     */
+    private boolean mIsVideoEnabled = true;
+
     //***** Event Constants
     private static final int EVENT_DTMF_DONE = 1;
     private static final int EVENT_PAUSE_DONE = 2;
@@ -246,11 +254,15 @@
         return (a == null) ? (b == null) : (b != null && a.startsWith (b));
     }
 
-    private static int applyLocalCallCapabilities(ImsCallProfile localProfile, int capabilities) {
-        Rlog.w(LOG_TAG, "applyLocalCallCapabilities - localProfile = "+localProfile);
+    private int applyLocalCallCapabilities(ImsCallProfile localProfile, int capabilities) {
+        Rlog.i(LOG_TAG, "applyLocalCallCapabilities - localProfile = " + localProfile);
         capabilities = removeCapability(capabilities,
                 Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
 
+        if (!mIsVideoEnabled) {
+            Rlog.i(LOG_TAG, "applyLocalCallCapabilities - disabling video (overidden)");
+            return capabilities;
+        }
         switch (localProfile.mCallType) {
             case ImsCallProfile.CALL_TYPE_VT:
                 // Fall-through
@@ -1229,4 +1241,15 @@
         updateVideoState(newVideoState);
         mShouldIgnoreVideoStateChanges = false;
     }
+
+    public void handleDataEnabledChange(boolean isDataEnabled) {
+        mIsVideoEnabled = isDataEnabled;
+        Rlog.i(LOG_TAG, "handleDataEnabledChange: isDataEnabled=" + isDataEnabled
+                + "; updating local video availability.");
+        updateMediaCapabilities(getImsCall());
+        if (mImsVideoCallProviderWrapper != null) {
+            mImsVideoCallProviderWrapper.setIsVideoEnabled(
+                    hasCapabilities(Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
index 1be0ffa..4e3957e 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
@@ -722,7 +722,6 @@
     boolean
     isSupportedOverImsPhone() {
         if (isShortCode()) return true;
-        else if (mDialingNumber != null) return false;
         else if (isServiceCodeCallForwarding(mSc)
                 || isServiceCodeCallBarring(mSc)
                 || (mSc != null && mSc.equals(SC_WAIT))
diff --git a/src/java/com/android/internal/telephony/uicc/IccCardProxy.java b/src/java/com/android/internal/telephony/uicc/IccCardProxy.java
index 9dd4c62..241f211 100644
--- a/src/java/com/android/internal/telephony/uicc/IccCardProxy.java
+++ b/src/java/com/android/internal/telephony/uicc/IccCardProxy.java
@@ -47,7 +47,6 @@
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
 import com.android.internal.telephony.uicc.IccCardStatus.PinState;
-import com.android.internal.telephony.uicc.UiccController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -88,7 +87,7 @@
     private static final int EVENT_ICC_RECORD_EVENTS = 500;
     private static final int EVENT_SUBSCRIPTION_ACTIVATED = 501;
     private static final int EVENT_SUBSCRIPTION_DEACTIVATED = 502;
-    private static final int EVENT_CARRIER_PRIVILIGES_LOADED = 503;
+    private static final int EVENT_CARRIER_PRIVILEGES_LOADED = 503;
 
     private Integer mPhoneId = null;
 
@@ -168,17 +167,31 @@
      */
     private void updateQuietMode() {
         synchronized (mLock) {
+            boolean oldQuietMode = mQuietMode;
             boolean newQuietMode;
             int cdmaSource = Phone.CDMA_SUBSCRIPTION_UNKNOWN;
+            boolean isLteOnCdmaMode = TelephonyManager.getLteOnCdmaModeStatic()
+                    == PhoneConstants.LTE_ON_CDMA_TRUE;
             if (mCurrentAppType == UiccController.APP_FAM_3GPP) {
                 newQuietMode = false;
                 if (DBG) log("updateQuietMode: 3GPP subscription -> newQuietMode=" + newQuietMode);
             } else {
+                if (isLteOnCdmaMode) {
+                    log("updateQuietMode: is cdma/lte device, force IccCardProxy into 3gpp mode");
+                    mCurrentAppType = UiccController.APP_FAM_3GPP;
+                }
                 cdmaSource = mCdmaSSM != null ?
                         mCdmaSSM.getCdmaSubscriptionSource() : Phone.CDMA_SUBSCRIPTION_UNKNOWN;
 
                 newQuietMode = (cdmaSource == Phone.CDMA_SUBSCRIPTION_NV)
-                        && (mCurrentAppType == UiccController.APP_FAM_3GPP2);
+                        && (mCurrentAppType == UiccController.APP_FAM_3GPP2)
+                        && !isLteOnCdmaMode;
+                if (DBG) {
+                    log("updateQuietMode: cdmaSource=" + cdmaSource
+                            + " mCurrentAppType=" + mCurrentAppType
+                            + " isLteOnCdmaMode=" + isLteOnCdmaMode
+                            + " newQuietMode=" + newQuietMode);
+                }
             }
 
             if (mQuietMode == false && newQuietMode == true) {
@@ -199,7 +212,8 @@
             }
             if (DBG) {
                 log("updateQuietMode: QuietMode is " + mQuietMode + " (app_type="
-                    + mCurrentAppType + " cdmaSource=" + cdmaSource + ")");
+                    + mCurrentAppType + " isLteOnCdmaMode=" + isLteOnCdmaMode
+                    + " cdmaSource=" + cdmaSource + ")");
             }
             mInitialized = true;
             sendMessage(obtainMessage(EVENT_ICC_CHANGED));
@@ -260,7 +274,7 @@
                 }
                 if (mUiccCard != null && !mUiccCard.areCarrierPriviligeRulesLoaded()) {
                     mUiccCard.registerForCarrierPrivilegeRulesLoaded(
-                        this, EVENT_CARRIER_PRIVILIGES_LOADED, null);
+                            this, EVENT_CARRIER_PRIVILEGES_LOADED, null);
                 } else {
                     onRecordsLoaded();
                 }
@@ -296,7 +310,7 @@
                 }
                 break;
 
-            case EVENT_CARRIER_PRIVILIGES_LOADED:
+            case EVENT_CARRIER_PRIVILEGES_LOADED:
                 log("EVENT_CARRIER_PRIVILEGES_LOADED");
                 if (mUiccCard != null) {
                     mUiccCard.unregisterForCarrierPrivilegeRulesLoaded(this);
@@ -526,7 +540,8 @@
             intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE, value);
             intent.putExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON, reason);
             intent.putExtra(PhoneConstants.PHONE_KEY, mPhoneId);  // SubId may not be valid.
-            log("Sending intent ACTION_INTERNAL_SIM_STATE_CHANGED" + " for mPhoneId : " + mPhoneId);
+            log("Sending intent ACTION_INTERNAL_SIM_STATE_CHANGED value=" + value
+                    + " for mPhoneId : " + mPhoneId);
             ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
         }
     }
diff --git a/src/java/com/android/internal/telephony/uicc/IccFileHandler.java b/src/java/com/android/internal/telephony/uicc/IccFileHandler.java
index 4750814..33dd381 100644
--- a/src/java/com/android/internal/telephony/uicc/IccFileHandler.java
+++ b/src/java/com/android/internal/telephony/uicc/IccFileHandler.java
@@ -471,6 +471,7 @@
                 response = lc.mOnLoaded;
 
                 if (processException(response, (AsyncResult) msg.obj)) {
+                    loge("exception caught from EVENT_GET_RECORD_SIZE");
                     break;
                 }
 
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCard.java b/src/java/com/android/internal/telephony/uicc/UiccCard.java
index 8cd6696..baad60b 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCard.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCard.java
@@ -94,7 +94,7 @@
     private static final int EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE = 17;
     private static final int EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE = 18;
     private static final int EVENT_SIM_IO_DONE = 19;
-    private static final int EVENT_CARRIER_PRIVILIGES_LOADED = 20;
+    private static final int EVENT_CARRIER_PRIVILEGES_LOADED = 20;
 
     private static final LocalLog mLocalLog = new LocalLog(100);
 
@@ -158,7 +158,7 @@
             log("Before privilege rules: " + mCarrierPrivilegeRules + " : " + mCardState);
             if (mCarrierPrivilegeRules == null && mCardState == CardState.CARDSTATE_PRESENT) {
                 mCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(this,
-                        mHandler.obtainMessage(EVENT_CARRIER_PRIVILIGES_LOADED));
+                        mHandler.obtainMessage(EVENT_CARRIER_PRIVILEGES_LOADED));
             } else if (mCarrierPrivilegeRules != null
                     && mCardState != CardState.CARDSTATE_PRESENT) {
                 mCarrierPrivilegeRules = null;
@@ -384,7 +384,7 @@
                     AsyncResult.forMessage((Message)ar.userObj, ar.result, ar.exception);
                     ((Message)ar.userObj).sendToTarget();
                     break;
-                case EVENT_CARRIER_PRIVILIGES_LOADED:
+                case EVENT_CARRIER_PRIVILEGES_LOADED:
                     onCarrierPriviligesLoadedMessage();
                     break;
                 default:
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java b/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
index 26c8aea..e50f40c 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
@@ -92,6 +92,8 @@
     private static final String TAG_PKG_REF_DO = "CA";
     private static final String TAG_AR_DO = "E3";
     private static final String TAG_PERM_AR_DO = "DB";
+    private static final String TAG_AID_REF_DO = "4F";
+    private static final String CARRIER_PRIVILEGE_AID = "FFFFFFFFFFFF";
 
     private static final int EVENT_OPEN_LOGICAL_CHANNEL_DONE = 1;
     private static final int EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE = 2;
@@ -541,37 +543,52 @@
             if (rule.startsWith(TAG_REF_DO)) {
                 TLV refDo = new TLV(TAG_REF_DO); //E1
                 rule = refDo.parse(rule, false);
-
-                // Skip unrelated rules.
-                if (!refDo.value.startsWith(TAG_DEVICE_APP_ID_REF_DO)) {
+                // Allow 4F tag with a default value "FF FF FF FF FF FF" to be compatible with
+                // devices having GP access control enforcer:
+                //  - If no 4F tag is present, it's a CP rule.
+                //  - If 4F tag has value "FF FF FF FF FF FF", it's a CP rule.
+                //  - If 4F tag has other values, it's not a CP rule and Android should ignore it.
+                TLV deviceDo = new TLV(TAG_DEVICE_APP_ID_REF_DO); //C1
+                if (refDo.value.startsWith(TAG_AID_REF_DO)) {
+                    TLV cpDo = new TLV(TAG_AID_REF_DO); //4F
+                    String remain = cpDo.parse(refDo.value, false);
+                    if (!cpDo.lengthBytes.equals("06") || !cpDo.value.equals(CARRIER_PRIVILEGE_AID)
+                            || remain.isEmpty() || !remain.startsWith(TAG_DEVICE_APP_ID_REF_DO)) {
+                        return null;
+                    }
+                    tmp = deviceDo.parse(remain, false);
+                    certificateHash = deviceDo.value;
+                } else if (refDo.value.startsWith(TAG_DEVICE_APP_ID_REF_DO)) {
+                    tmp = deviceDo.parse(refDo.value, false);
+                    certificateHash = deviceDo.value;
+                } else {
                     return null;
                 }
-
-                TLV deviceDo = new TLV(TAG_DEVICE_APP_ID_REF_DO); //C1
-                tmp = deviceDo.parse(refDo.value, false);
-                certificateHash = deviceDo.value;
-
                 if (!tmp.isEmpty()) {
-                  if (!tmp.startsWith(TAG_PKG_REF_DO)) {
-                      return null;
-                  }
-                  TLV pkgDo = new TLV(TAG_PKG_REF_DO); //CA
-                  pkgDo.parse(tmp, true);
-                  packageName = new String(IccUtils.hexStringToBytes(pkgDo.value));
+                    if (!tmp.startsWith(TAG_PKG_REF_DO)) {
+                        return null;
+                    }
+                    TLV pkgDo = new TLV(TAG_PKG_REF_DO); //CA
+                    pkgDo.parse(tmp, true);
+                    packageName = new String(IccUtils.hexStringToBytes(pkgDo.value));
                 } else {
-                  packageName = null;
+                    packageName = null;
                 }
             } else if (rule.startsWith(TAG_AR_DO)) {
                 TLV arDo = new TLV(TAG_AR_DO); //E3
                 rule = arDo.parse(rule, false);
-
-                // Skip unrelated rules.
-                if (!arDo.value.startsWith(TAG_PERM_AR_DO)) {
+                // Skip all the irrelevant tags (All the optional tags here are two bytes
+                // according to the spec GlobalPlatform Secure Element Access Control).
+                String remain = arDo.value;
+                while (!remain.isEmpty() && !remain.startsWith(TAG_PERM_AR_DO)) {
+                    TLV tmpDo = new TLV(remain.substring(0, 2));
+                    remain = tmpDo.parse(remain, false);
+                }
+                if (remain.isEmpty()) {
                     return null;
                 }
-
                 TLV permDo = new TLV(TAG_PERM_AR_DO); //DB
-                permDo.parse(arDo.value, true);
+                permDo.parse(remain, true);
             } else  {
                 // Spec requires it must be either TAG_REF_DO or TAG_AR_DO.
                 throw new RuntimeException("Invalid Rule type");
diff --git a/src/java/com/android/internal/telephony/uicc/UiccController.java b/src/java/com/android/internal/telephony/uicc/UiccController.java
index 65aacd1..a948b75 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccController.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccController.java
@@ -89,8 +89,6 @@
     private static final int EVENT_RADIO_UNAVAILABLE = 3;
     private static final int EVENT_SIM_REFRESH = 4;
 
-    private static final String DECRYPT_STATE = "trigger_restart_framework";
-
     private CommandsInterface[] mCis;
     private UiccCard[] mUiccCards = new UiccCard[TelephonyManager.getDefault().getPhoneCount()];
 
@@ -125,12 +123,11 @@
             Integer index = new Integer(i);
             mCis[i].registerForIccStatusChanged(this, EVENT_ICC_STATUS_CHANGED, index);
             // TODO remove this once modem correctly notifies the unsols
-            // If the device has been decrypted or FBE is supported, read SIM when radio state is
-            // available.
+            // If the device is unencrypted or has been decrypted or FBE is supported,
+            // i.e. not in cryptkeeper bounce, read SIM when radio state isavailable.
             // Else wait for radio to be on. This is needed for the scenario when SIM is locked --
             // to avoid overlap of CryptKeeper and SIM unlock screen.
-            if (DECRYPT_STATE.equals(SystemProperties.get("vold.decrypt")) ||
-                    StorageManager.isFileEncryptedNativeOrEmulated()) {
+            if (!StorageManager.inCryptKeeperBounce()) {
                 mCis[i].registerForAvailable(this, EVENT_ICC_STATUS_CHANGED, index);
             } else {
                 mCis[i].registerForOn(this, EVENT_ICC_STATUS_CHANGED, index);
@@ -342,7 +339,7 @@
             if (requirePowerOffOnSimRefreshReset) {
                 mCis[index].setRadioPower(false, null);
             } else {
-                mCis[index].getIccCardStatus(obtainMessage(EVENT_GET_ICC_STATUS_DONE));
+                mCis[index].getIccCardStatus(obtainMessage(EVENT_GET_ICC_STATUS_DONE, index));
             }
             mIccChangedRegistrants.notifyRegistrants(new AsyncResult(null, index, null));
         }
diff --git a/src/java/com/android/internal/telephony/util/NotificationChannelController.java b/src/java/com/android/internal/telephony/util/NotificationChannelController.java
index 88ff4cf..54d7d1a 100644
--- a/src/java/com/android/internal/telephony/util/NotificationChannelController.java
+++ b/src/java/com/android/internal/telephony/util/NotificationChannelController.java
@@ -38,12 +38,12 @@
      */
     public static final String CHANNEL_ID_ALERT = "alert";
     public static final String CHANNEL_ID_CALL_FORWARD = "callForward";
-    public static final String CHANNEL_ID_MOBILE_DATA_ALERT = "mobileDataAlertNew";
+    public static final String CHANNEL_ID_MOBILE_DATA_STATUS = "mobileDataAlertNew";
     public static final String CHANNEL_ID_SMS = "sms";
     public static final String CHANNEL_ID_VOICE_MAIL = "voiceMail";
     public static final String CHANNEL_ID_WFC = "wfc";
 
-    /** deprecated channel, replaced with @see #CHANNEL_ID_MOBILE_DATA_ALERT */
+    /** deprecated channel, replaced with @see #CHANNEL_ID_MOBILE_DATA_STATUS */
     private static final String CHANNEL_ID_MOBILE_DATA_ALERT_DEPRECATED = "mobileDataAlert";
 
     /**
@@ -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_ALERT,
-                        context.getText(R.string.notification_channel_mobile_data_alert),
-                        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/CarrierServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
new file mode 100644
index 0000000..8be2f60
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Resources;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isA;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit tests for {@link com.android.internal.telephony.CarrierServiceStateTracker}.
+ */
+@SmallTest
+public class CarrierServiceStateTrackerTest extends TelephonyTest {
+    public static final String LOG_TAG = "CSST";
+    public static final int TEST_TIMEOUT = 5000;
+
+    private CarrierServiceStateTracker mCarrierSST;
+    private CarrierServiceStateTrackerTestHandler mCarrierServiceStateTrackerTestHandler;
+    private  CarrierServiceStateTracker.PrefNetworkNotification mPrefNetworkNotification;
+    private  CarrierServiceStateTracker.EmergencyNetworkNotification mEmergencyNetworkNotification;
+
+    @Mock Context mContext;
+    @Mock ServiceStateTracker mServiceStateTracker;
+    @Mock NotificationManager mNotificationManager;
+    @Mock Resources mResources;
+
+    private class CarrierServiceStateTrackerTestHandler extends HandlerThread {
+
+        private CarrierServiceStateTrackerTestHandler(String name) {
+            super(name);
+        }
+
+        @Override
+        public void onLooperPrepared() {
+            mCarrierSST = spy(new CarrierServiceStateTracker(mPhone, mServiceStateTracker));
+            setReady(true);
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        logd(LOG_TAG + "Setup!");
+        super.setUp(getClass().getSimpleName());
+        mCarrierServiceStateTrackerTestHandler =
+                new CarrierServiceStateTrackerTestHandler(getClass().getSimpleName());
+        mCarrierServiceStateTrackerTestHandler.start();
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+        waitUntilReady();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mCarrierServiceStateTrackerTestHandler.quit();
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void testCancelBothNotifications() {
+        logd(LOG_TAG + ":testCancelBothNotifications()");
+        Message notificationMsg = mCarrierSST.obtainMessage(
+                CarrierServiceStateTracker.CARRIER_EVENT_DATA_REGISTRATION, null);
+        doReturn(false).when(mCarrierSST).evaluateSendingMessage(any());
+        doReturn(mNotificationManager).when(mCarrierSST).getNotificationManager(any());
+        mCarrierSST.handleMessage(notificationMsg);
+        waitForHandlerAction(mCarrierSST, TEST_TIMEOUT);
+        verify(mNotificationManager).cancel(
+                CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK);
+        verify(mNotificationManager).cancel(
+                CarrierServiceStateTracker.NOTIFICATION_PREF_NETWORK);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendBothNotifications() {
+        logd(LOG_TAG + ":testSendBothNotifications()");
+        Notification.Builder mNotificationBuilder = new Notification.Builder(mContext);
+        Message notificationMsg = mCarrierSST.obtainMessage(
+                CarrierServiceStateTracker.CARRIER_EVENT_DATA_DEREGISTRATION, null);
+        doReturn(true).when(mCarrierSST).evaluateSendingMessage(any());
+        doReturn(false).when(mCarrierSST).isRadioOffOrAirplaneMode();
+        doReturn(0).when(mCarrierSST).getDelay(any());
+        doReturn(mNotificationBuilder).when(mCarrierSST).getNotificationBuilder(any());
+        doReturn(mNotificationManager).when(mCarrierSST).getNotificationManager(any());
+        mCarrierSST.handleMessage(notificationMsg);
+        waitForHandlerAction(mCarrierSST, TEST_TIMEOUT);
+        verify(mNotificationManager).notify(
+                eq(CarrierServiceStateTracker.NOTIFICATION_PREF_NETWORK), isA(Notification.class));
+        verify(mNotificationManager).notify(
+                eq(CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK), any());
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ClientWakelockAccountantTest.java b/tests/telephonytests/src/com/android/internal/telephony/ClientWakelockAccountantTest.java
index 2167c13..f7132b8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ClientWakelockAccountantTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ClientWakelockAccountantTest.java
@@ -18,7 +18,6 @@
 
 import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
 
-import android.test.TestRunner;
 import android.os.Build;
 import android.util.Log;
 import android.telephony.Rlog;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index 9a03c44..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,
@@ -442,7 +445,14 @@
 
         @Override
         public int checkCallingOrSelfPermission(String permission) {
-            return PackageManager.PERMISSION_GRANTED;
+            if (mPermissionTable.contains(permission)
+                    || mPermissionTable.contains(PERMISSION_ENABLE_ALL)) {
+                logd("checkCallingOrSelfPermission: " + permission + " return GRANTED");
+                return PackageManager.PERMISSION_GRANTED;
+            } else {
+                logd("checkCallingOrSelfPermission: " + permission + " return DENIED");
+                return PackageManager.PERMISSION_DENIED;
+            }
         }
 
         @Override
@@ -489,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/GsmCdmaCallTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
index 30122f9..f3066f4 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
@@ -53,7 +53,7 @@
     private GsmCdmaCallTracker mCTUT;
     private GsmCdmaCTHandlerThread mGsmCdmaCTHandlerThread;
     @Mock
-    GsmCdmaCall mCall;
+    GsmCdmaConnection mConnection;
     @Mock
     private Handler mHandler;
 
@@ -166,8 +166,8 @@
         assertEquals(PhoneConstants.State.OFFHOOK, mCTUT.getState());
         assertEquals(1, mCTUT.mForegroundCall.getConnections().size());
          /* get the reference of the connection before reject */
-        Connection mConnection = mCTUT.mForegroundCall.getConnections().get(0);
-        assertEquals(DisconnectCause.NOT_DISCONNECTED, mConnection.getDisconnectCause());
+        Connection connection = mCTUT.mForegroundCall.getConnections().get(0);
+        assertEquals(DisconnectCause.NOT_DISCONNECTED, connection.getDisconnectCause());
         logd("hang up MO call after pickup");
         try {
             mCTUT.hangup(mCTUT.mForegroundCall);
@@ -180,11 +180,12 @@
         assertEquals(GsmCdmaCall.State.IDLE, mCTUT.mForegroundCall.getState());
         assertEquals(0, mCTUT.mForegroundCall.getConnections().size());
         assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
-        assertEquals(DisconnectCause.LOCAL, mConnection.getDisconnectCause());
+        assertEquals(DisconnectCause.LOCAL, connection.getDisconnectCause());
 
     }
 
     @FlakyTest
+    @Ignore
     @Test
     @MediumTest
     public void testMOCallPendingHangUp() {
@@ -276,9 +277,9 @@
         testMTCallRinging();
         logd("MT call ringing and rejected ");
         /* get the reference of the connection before reject */
-        Connection mConnection = mCTUT.mRingingCall.getConnections().get(0);
-        assertNotNull(mConnection);
-        assertEquals(DisconnectCause.NOT_DISCONNECTED, mConnection.getDisconnectCause());
+        Connection connection = mCTUT.mRingingCall.getConnections().get(0);
+        assertNotNull(connection);
+        assertEquals(DisconnectCause.NOT_DISCONNECTED, connection.getDisconnectCause());
         try {
             mCTUT.rejectCall();
         } catch(Exception ex) {
@@ -290,11 +291,12 @@
         assertEquals(GsmCdmaCall.State.IDLE, mCTUT.mForegroundCall.getState());
         assertEquals(0, mCTUT.mForegroundCall.getConnections().size());
         /* ? why rejectCall didnt -> hang up locally to set the cause to LOCAL? */
-        assertEquals(DisconnectCause.INCOMING_MISSED, mConnection.getDisconnectCause());
+        assertEquals(DisconnectCause.INCOMING_MISSED, connection.getDisconnectCause());
 
     }
 
     @FlakyTest
+    @Ignore
     @Test
     @MediumTest
     public void testMOCallSwitchHangupForeGround() {
@@ -353,7 +355,8 @@
         testMOCallPickUp();
         ArgumentCaptor<Message> mCaptorMessage = ArgumentCaptor.forClass(Message.class);
         ArgumentCaptor<Long> mCaptorLong = ArgumentCaptor.forClass(Long.class);
-        verify(mHandler,times(1)).sendMessageAtTime(mCaptorMessage.capture(), mCaptorLong.capture());
+        verify(mHandler, times(1))
+                .sendMessageAtTime(mCaptorMessage.capture(), mCaptorLong.capture());
         assertEquals(VOICE_CALL_STARTED_EVENT, mCaptorMessage.getValue().what);
 
     }
@@ -367,7 +370,8 @@
         ArgumentCaptor<Message> mCaptorMessage = ArgumentCaptor.forClass(Message.class);
         ArgumentCaptor<Long> mCaptorLong = ArgumentCaptor.forClass(Long.class);
         testMOCallHangup();
-        verify(mHandler,times(1)).sendMessageAtTime(mCaptorMessage.capture(), mCaptorLong.capture());
+        verify(mHandler, times(1))
+                .sendMessageAtTime(mCaptorMessage.capture(), mCaptorLong.capture());
         assertEquals(VOICE_CALL_ENDED_EVENT, mCaptorMessage.getValue().what);
     }
 
@@ -407,5 +411,28 @@
         assertEquals(GsmCdmaCall.State.IDLE, mCTUT.mBackgroundCall.getState());
         assertEquals(GsmCdmaCall.State.IDLE, mCTUT.mRingingCall.getState());
     }
+
+    @Test
+    @SmallTest
+    public void testUpdatePhoneTypeWithActiveCall() {
+        // verify getCurrentCalls is called on init
+        verify(mSimulatedCommandsVerifier).getCurrentCalls(any(Message.class));
+
+        // fake connection
+        mCTUT.mConnections[0] = mConnection;
+
+        // update phone type (call the function on same thread as the call tracker)
+        Handler updatePhoneTypeHandler = new Handler(mCTUT.getLooper()) {
+            @Override
+            public void handleMessage(Message msg) {
+                mCTUT.updatePhoneType();
+            }
+        };
+        updatePhoneTypeHandler.sendEmptyMessage(0);
+        waitForMs(100);
+
+        // verify that the active call is disconnected
+        verify(mConnection).onDisconnect(DisconnectCause.ERROR_UNSPECIFIED);
+    }
 }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
index 309568a..e1e3cd2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
@@ -513,6 +513,7 @@
      * received when obj is created and that are received on phone type switch
      */
     @FlakyTest
+    @Ignore
     @Test
     @SmallTest
     public void testHandleInitialMessages() {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
index f4caf48..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;
@@ -428,6 +429,7 @@
 
     // To run this test, the device has to be registered with network
     @FlakyTest
+    @Ignore
     public void testCheckAndProcessPlusCode() {
         assertEquals("0118475797000",
                 PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+8475797000"));
@@ -750,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/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
index 2096231..795cb98 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -45,10 +45,12 @@
 import android.os.HandlerThread;
 import android.os.Message;
 import android.os.Parcel;
+import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.support.test.filters.FlakyTest;
+import android.telephony.CarrierConfigManager;
 import android.telephony.CellInfo;
 import android.telephony.CellInfoGsm;
 import android.telephony.ServiceState;
@@ -66,6 +68,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
@@ -87,6 +90,7 @@
 
     private ServiceStateTracker sst;
     private ServiceStateTrackerTestHandler mSSTTestHandler;
+    private PersistableBundle mBundle;
 
     private static final int EVENT_REGISTERED_TO_NETWORK = 1;
     private static final int EVENT_SUBSCRIPTION_INFO_READY = 2;
@@ -123,14 +127,12 @@
         mPhone.mDcTracker = mDct;
 
         replaceInstance(ProxyController.class, "sProxyController", null, mProxyController);
+        mBundle = mContextFixture.getCarrierConfigBundle();
+        mBundle.putStringArray(
+                CarrierConfigManager.KEY_ROAMING_OPERATOR_STRING_ARRAY, new String[]{"123456"});
 
-        mContextFixture.putStringArrayResource(
-                com.android.internal.R.array.config_sameNamedOperatorConsideredRoaming,
-                new String[]{"123456"});
-
-        mContextFixture.putStringArrayResource(
-                com.android.internal.R.array.config_operatorConsideredNonRoaming,
-                new String[]{"123456"});
+        mBundle.putStringArray(
+                CarrierConfigManager.KEY_NON_ROAMING_OPERATOR_STRING_ARRAY, new String[]{"123456"});
 
         mSimulatedCommands.setVoiceRegState(ServiceState.RIL_REG_STATE_HOME);
         mSimulatedCommands.setVoiceRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_HSPA);
@@ -238,6 +240,7 @@
     }
 
     @FlakyTest
+    @Ignore
     @Test
     @MediumTest
     public void testSpnUpdateShowPlmnOnly() {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/Sms7BitEncodingTranslatorTest.java b/tests/telephonytests/src/com/android/internal/telephony/Sms7BitEncodingTranslatorTest.java
index 283b170..cf11d1b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/Sms7BitEncodingTranslatorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/Sms7BitEncodingTranslatorTest.java
@@ -16,21 +16,23 @@
 
 package com.android.internal.telephony;
 
-import android.support.test.filters.FlakyTest;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-
-import java.io.UnsupportedEncodingException;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 
+import android.support.test.filters.FlakyTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.io.UnsupportedEncodingException;
+
+@Ignore
 public class Sms7BitEncodingTranslatorTest extends TelephonyTest {
 
     @Mock
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
index 141ca45..66742dd 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
@@ -243,6 +243,33 @@
     }
 
     @Test @SmallTest
+    public void testSetGetDisplayNameSrc() {
+        testInsertSim();
+
+        /* Get SUB ID */
+        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList();
+        assertTrue(subIds != null && subIds.length != 0);
+        int subID = subIds[0];
+
+        /* Setting */
+        String disName = "TESTING";
+        long nameSource = 1;
+        mSubscriptionControllerUT.setDisplayNameUsingSrc(disName, subID, nameSource);
+        SubscriptionInfo subInfo = mSubscriptionControllerUT
+                .getActiveSubscriptionInfo(subID, mCallingPackage);
+        assertNotNull(subInfo);
+        assertEquals(disName, subInfo.getDisplayName());
+        assertEquals(nameSource, subInfo.getNameSource());
+
+        /* verify broadcast intent */
+        ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, atLeast(1)).sendBroadcast(captorIntent.capture());
+        assertEquals(TelephonyIntents.ACTION_SUBINFO_RECORD_UPDATED,
+                captorIntent.getValue().getAction());
+
+    }
+
+    @Test @SmallTest
     public void testCleanUpSIM() {
         testInsertSim();
         assertFalse(mSubscriptionControllerUT.isActiveSubId(1));
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java
index ab7fd7f..55036ad 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java
@@ -17,27 +17,24 @@
 package com.android.internal.telephony.cdma;
 
 import android.hardware.radio.V1_0.CdmaSmsMessage;
-import android.os.Parcel;
 import android.support.test.filters.FlakyTest;
+import android.telephony.Rlog;
 import android.telephony.SmsCbCmasInfo;
 import android.telephony.SmsCbMessage;
 import android.telephony.cdma.CdmaSmsCbProgramData;
 import android.test.AndroidTestCase;
-import android.telephony.Rlog;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.telephony.GsmAlphabet;
-import com.android.internal.telephony.cdma.SmsMessageConverter;
 import com.android.internal.telephony.cdma.sms.BearerData;
 import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
 import com.android.internal.telephony.cdma.sms.UserData;
-import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.util.BitwiseOutputStream;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Random;
@@ -482,7 +479,9 @@
     // VZW requirement is to discard message with unsupported charset. Verify that we return null
     // for this unsupported character set.
     @FlakyTest
-    @Test @SmallTest
+    @Ignore
+    @Test
+    @SmallTest
     public void testCmasUnsupportedCharSet() throws Exception {
         SmsMessage msg = createCmasSmsMessage(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT,
                 12345, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
index 1e34910..21048ff 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
@@ -296,7 +296,6 @@
 
         assertFalse(getNetworkCapabilities()
                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED));
-        assertTrue(getNetworkInfo().isMetered());
     }
 
     @Test
@@ -312,6 +311,5 @@
 
         assertTrue(getNetworkCapabilities()
                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED));
-        assertFalse(getNetworkInfo().isMetered());
     }
 }
\ No newline at end of file
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
index 1ca9acc..0983c27 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
@@ -849,6 +849,7 @@
 
     // Test for API carrierActionSetMeteredApnsEnabled.
     @FlakyTest
+    @Ignore
     @Test
     @MediumTest
     public void testCarrierActionSetMeteredApnsEnabled() throws Exception {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
index a60b502..6d1cf8b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
@@ -22,7 +22,6 @@
 import android.net.NetworkRequest;
 import android.net.StringNetworkSpecifier;
 import android.os.Binder;
-import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
@@ -39,6 +38,7 @@
 import com.android.internal.telephony.mocks.SubscriptionMonitorMock;
 import com.android.internal.telephony.mocks.TelephonyRegistryMock;
 
+import org.junit.Ignore;
 
 public class TelephonyNetworkFactoryTest extends AndroidTestCase {
     private final static String LOG_TAG = "TelephonyNetworkFactoryTest";
@@ -65,18 +65,29 @@
         final Looper looper;
         DcTrackerMock dcTrackerMock;
         final Context contextMock;
+        private Object mLock = new Object();
+        private static final int MAX_INIT_WAIT_MS = 30000; // 30 seconds
 
         TestSetup(int numPhones) {
-            handlerThread = new HandlerThread("TelephonyNetworkFactoryTest");
-            handlerThread.start();
-            looper = handlerThread.getLooper();
-
-            Handler myHandler = new Handler(looper) {
-                public void handleMessage(Message msg) {
-                    if (dcTrackerMock == null) dcTrackerMock = new DcTrackerMock();
+            handlerThread = new HandlerThread("TelephonyNetworkFactoryTest") {
+                @Override
+                public void onLooperPrepared() {
+                    synchronized (mLock) {
+                        if (dcTrackerMock == null) dcTrackerMock = new DcTrackerMock();
+                        mLock.notifyAll();
+                    }
                 }
             };
-            myHandler.obtainMessage(0).sendToTarget();
+            handlerThread.start();
+            // wait until dct created
+            synchronized (mLock) {
+                try {
+                    mLock.wait(MAX_INIT_WAIT_MS);
+                } catch (InterruptedException ie) {
+                }
+                if (dcTrackerMock == null) fail("failed to initialize dct");
+            }
+            looper = handlerThread.getLooper();
 
             final ContextFixture contextFixture = new ContextFixture();
             String[] networkConfigString = getContext().getResources().getStringArray(
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 61b4b4c..6e14aca 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,9 @@
 import android.os.UserManager;
 import android.provider.Telephony;
 import android.support.test.filters.FlakyTest;
+import android.support.test.filters.LargeTest;
+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;
@@ -64,6 +65,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
@@ -225,6 +227,8 @@
         assertEquals("IdleState", getCurrentState().getName());
     }
 
+    @FlakyTest
+    @Ignore
     @Test
     @MediumTest
     public void testNewSms() {
@@ -297,6 +301,8 @@
         verifyDataSmsIntentBroadcasts(1);
     }
 
+    @FlakyTest
+    @Ignore
     @Test
     @MediumTest
     public void testInjectSms() {
@@ -483,6 +489,7 @@
     }
 
     @FlakyTest
+    @Ignore
     @Test
     @MediumTest
     public void testMultiPartSms() {
@@ -752,6 +759,7 @@
     }
 
     @FlakyTest
+    @Ignore
     @Test
     @MediumTest
     public void testBroadcastUndeliveredMultiPart() throws Exception {
@@ -774,4 +782,28 @@
 
         verifySmsIntentBroadcasts(0);
     }
+
+    @FlakyTest
+    @Ignore
+    @Test
+    @LargeTest
+    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());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
index 8ab749c..0774e8e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
@@ -16,9 +16,15 @@
 
 package com.android.internal.telephony.gsm;
 
+import static android.telephony.SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED;
+
+import static com.android.internal.telephony.SmsUsageMonitor.CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE;
+import static com.android.internal.telephony.SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW;
 import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -35,6 +41,7 @@
 import android.os.HandlerThread;
 import android.os.Message;
 import android.os.SystemProperties;
+import android.provider.Settings;
 import android.provider.Telephony;
 import android.support.test.filters.FlakyTest;
 import android.telephony.SmsManager;
@@ -42,17 +49,23 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Singleton;
 
+import com.android.internal.telephony.ContextFixture;
 import com.android.internal.telephony.ISub;
 import com.android.internal.telephony.ImsSMSDispatcher;
+import com.android.internal.telephony.SMSDispatcher;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.TelephonyTestUtils;
 import com.android.internal.telephony.TestApplication;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
+import java.util.HashMap;
+
 public class GsmSmsDispatcherTest extends TelephonyTest {
     @Mock
     private android.telephony.SmsMessage mSmsMessage;
@@ -65,6 +78,8 @@
     @Mock
     private CountryDetector mCountryDetector;
     @Mock
+    private SMSDispatcher.SmsTracker mSmsTracker;
+    @Mock
     private ISub.Stub mISubStub;
     private Object mLock = new Object();
     private boolean mReceivedTestIntent = false;
@@ -143,6 +158,7 @@
     }
 
     @FlakyTest
+    @Ignore
     @Test @MediumTest
     public void testSendSmsToEmergencyNumber_notifiesBlockedNumberProvider() throws Exception {
         setupMockPackagePermissionChecks();
@@ -170,7 +186,10 @@
         }
     }
 
-    @Test @SmallTest
+    @Test
+    @SmallTest
+    @FlakyTest
+    @Ignore
     public void testSendTextWithInvalidDestAddr() throws Exception {
         // unmock ActivityManager to be able to register receiver, create real PendingIntent and
         // receive TEST_INTENT
@@ -191,4 +210,35 @@
             assertEquals(SmsManager.RESULT_ERROR_NULL_PDU, mTestReceiver.getResultCode());
         }
     }
+
+    @Test
+    public void testSendRawPduWithEventStopSending() throws Exception {
+        setupMockPackagePermissionChecks();
+        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
+
+        // return a fake value to pass getData()
+        HashMap data = new HashMap<String, String>();
+        data.put("pdu", new byte[1]);
+        when(mSmsTracker.getData()).thenReturn(data);
+
+        // Set values to return to simulate EVENT_STOP_SENDING
+        when(mSmsUsageMonitor.checkDestination(any(), any()))
+                .thenReturn(CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE);
+        when(mSmsUsageMonitor.getPremiumSmsPermission(any()))
+                .thenReturn(PREMIUM_SMS_PERMISSION_NEVER_ALLOW);
+        when(mSmsTracker.getAppPackageName()).thenReturn("");
+
+        // Settings.Global.DEVICE_PROVISIONED to 1
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 1);
+
+        mGsmSmsDispatcher.sendRawPdu(mSmsTracker);
+
+        verify(mSmsUsageMonitor, times(1)).checkDestination(any(), any());
+        verify(mSmsUsageMonitor, times(1)).getPremiumSmsPermission(any());
+        ArgumentCaptor<Integer> argumentCaptor = ArgumentCaptor
+                .forClass(Integer.class);
+        verify(mSmsTracker, times(1)).onFailed(any(), argumentCaptor.capture(), anyInt());
+        assertEquals(RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED, (int) argumentCaptor.getValue());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsExternalCallTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsExternalCallTrackerTest.java
index b01ae4a..8baa6cd 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsExternalCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsExternalCallTrackerTest.java
@@ -16,40 +16,32 @@
 
 package com.android.internal.telephony.imsphone;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.verify;
+
+import android.net.Uri;
+import android.support.test.filters.FlakyTest;
+
 import com.android.ims.ImsCallProfile;
 import com.android.ims.ImsExternalCallState;
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.Connection;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import android.net.Uri;
-import android.support.test.filters.FlakyTest;
 
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
 import java.util.List;
-import java.util.ListIterator;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 /**
  * Unit tests for the {@link ImsExternalCallTracker}.
  */
+@Ignore
 public class ImsExternalCallTrackerTest {
     private static final Uri TEST_ADDRESS = Uri.parse("tel:6505551212");
     private static final int CALL_ID = 1;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java
index 4fc9411..5f88bb1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java
@@ -16,6 +16,14 @@
 
 package com.android.internal.telephony.imsphone;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
 import android.support.test.filters.FlakyTest;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -25,17 +33,10 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.mockito.Mock;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
 public class ImsPhoneCallTest extends TelephonyTest {
     @Mock
     ImsPhoneConnection mConnection1;
@@ -61,6 +62,7 @@
     }
 
     @FlakyTest
+    @Ignore
     @Test
     @SmallTest
     public void testAttachDetach() {
@@ -84,6 +86,7 @@
     }
 
     @FlakyTest
+    @Ignore
     @Test
     @SmallTest
     public void testConnectionDisconnected() {
@@ -102,6 +105,7 @@
     }
 
     @FlakyTest
+    @Ignore
     @Test
     @SmallTest
     public void testHangup() {
@@ -114,6 +118,7 @@
     }
 
     @FlakyTest
+    @Ignore
     @Test
     @SmallTest
     public void testUpdateRingBackTone() {
@@ -146,6 +151,7 @@
     }
 
     @FlakyTest
+    @Ignore
     @Test
     @SmallTest
     public void testMultiParty() {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
index e3f265a..71f4cc4 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -42,9 +43,14 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.support.test.filters.FlakyTest;
 import android.telecom.VideoProfile;
+import android.telephony.CarrierConfigManager;
+import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
 import android.telephony.ims.feature.ImsFeature;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -87,6 +93,8 @@
     private ImsCallSession mImsCallSession;
     @Mock
     private SharedPreferences mSharedPreferences;
+    @Mock
+    private ImsPhoneConnection.Listener mImsPhoneConnectionListener;
     private Handler mCTHander;
 
     private class ImsCTHandlerThread extends HandlerThread {
@@ -102,6 +110,7 @@
                     ImsReasonInfo.CODE_ANSWERED_ELSEWHERE);
             mCTUT.addReasonCodeRemapping(510, "Call answered elsewhere.",
                     ImsReasonInfo.CODE_ANSWERED_ELSEWHERE);
+            mCTUT.setDataEnabled(true);
             mCTHander = new Handler(mCTUT.getLooper());
             setReady(true);
         }
@@ -155,7 +164,7 @@
             }
         }).when(mImsCall).hold();
 
-        doReturn(mImsCallSession).when(mImsCall).getCallSession();
+        mImsCall.attachSession(mImsCallSession);
     }
 
     @Before
@@ -166,6 +175,7 @@
         mImsManagerInstances.put(mImsPhone.getPhoneId(), mImsManager);
         mImsCall = spy(new ImsCall(mContext, mImsCallProfile));
         mSecondImsCall = spy(new ImsCall(mContext, mImsCallProfile));
+        mImsPhoneConnectionListener = mock(ImsPhoneConnection.Listener.class);
         imsCallMocking(mImsCall);
         imsCallMocking(mSecondImsCall);
         doReturn(ImsFeature.STATE_READY).when(mImsManager).getImsServiceStatus();
@@ -188,6 +198,7 @@
             public ImsCall answer(InvocationOnMock invocation) throws Throwable {
                 mImsCallListener =
                         (ImsCall.Listener) invocation.getArguments()[2];
+                mImsCall.setListener(mImsCallListener);
                 return mImsCall;
             }
         }).when(mImsManager).takeCall(eq(mServiceId), (Intent) any(), (ImsCall.Listener) any());
@@ -197,6 +208,7 @@
             public ImsCall answer(InvocationOnMock invocation) throws Throwable {
                 mImsCallListener =
                         (ImsCall.Listener) invocation.getArguments()[3];
+                mSecondImsCall.setListener(mImsCallListener);
                 return mSecondImsCall;
             }
         }).when(mImsManager).makeCall(eq(mServiceId), eq(mImsCallProfile), (String []) any(),
@@ -261,6 +273,9 @@
         assertEquals(PhoneConstants.State.RINGING, mCTUT.getState());
         assertTrue(mCTUT.mRingingCall.isRinging());
         assertEquals(1, mCTUT.mRingingCall.getConnections().size());
+        ImsPhoneConnection connection =
+                (ImsPhoneConnection) mCTUT.mRingingCall.getConnections().get(0);
+        connection.addListener(mImsPhoneConnectionListener);
     }
 
     @Test
@@ -419,6 +434,7 @@
     }
 
     @FlakyTest
+    @Ignore
     @Test
     @SmallTest
     public void testImsMOCallDial() {
@@ -567,4 +583,71 @@
         verify(mImsManager, times(2)).open(anyInt(), nullable(PendingIntent.class),
                 nullable(ImsConnectionStateListener.class));
     }
+
+    /**
+     * Test notification of handover from LTE to WIFI and WIFI to LTE and ensure that the expected
+     * connection events are sent.
+     */
+    @Test
+    @SmallTest
+    public void testNotifyHandovers() {
+        setupCarrierConfig();
+
+        //establish a MT call
+        testImsMTCallAccept();
+        ImsPhoneConnection connection =
+                (ImsPhoneConnection) mCTUT.mForegroundCall.getConnections().get(0);
+        ImsCall call = connection.getImsCall();
+        // Needs to be a video call to see this signalling.
+        doReturn(true).when(mImsCallProfile).isVideoCall();
+
+        // First handover from LTE to WIFI; this takes us into a mid-call state.
+        call.getImsCallSessionListenerProxy().callSessionHandover(call.getCallSession(),
+                ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN,
+                new ImsReasonInfo());
+        // Handover back to LTE.
+        call.getImsCallSessionListenerProxy().callSessionHandover(call.getCallSession(),
+                ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN, ServiceState.RIL_RADIO_TECHNOLOGY_LTE,
+                new ImsReasonInfo());
+        verify(mImsPhoneConnectionListener).onConnectionEvent(eq(
+                TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE), isNull());
+
+        // Finally hand back to WIFI
+        call.getImsCallSessionListenerProxy().callSessionHandover(call.getCallSession(),
+                ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN,
+                new ImsReasonInfo());
+        verify(mImsPhoneConnectionListener).onConnectionEvent(eq(
+                TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_LTE_TO_WIFI), isNull());
+    }
+
+    /**
+     * Configure carrier config options relevant to the unit test.
+     */
+    public void setupCarrierConfig() {
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean(CarrierConfigManager.KEY_NOTIFY_HANDOVER_VIDEO_FROM_LTE_TO_WIFI_BOOL,
+                true);
+        bundle.putBoolean(CarrierConfigManager.KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL,
+                true);
+        bundle.putBoolean(CarrierConfigManager.KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL, true);
+        mCTUT.updateCarrierConfigCache(bundle);
+    }
+
+    @Test
+    @SmallTest
+    public void testLowBatteryDisconnectMidCall() {
+        assertEquals(DisconnectCause.LOW_BATTERY, mCTUT.getDisconnectCauseFromReasonInfo(
+                new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_LOW_BATTERY, 0), Call.State.ACTIVE));
+        assertEquals(DisconnectCause.LOW_BATTERY, mCTUT.getDisconnectCauseFromReasonInfo(
+                new ImsReasonInfo(ImsReasonInfo.CODE_LOW_BATTERY, 0), Call.State.ACTIVE));
+    }
+
+    @Test
+    @SmallTest
+    public void testLowBatteryDisconnectDialing() {
+        assertEquals(DisconnectCause.DIAL_LOW_BATTERY, mCTUT.getDisconnectCauseFromReasonInfo(
+                new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_LOW_BATTERY, 0), Call.State.DIALING));
+        assertEquals(DisconnectCause.DIAL_LOW_BATTERY, mCTUT.getDisconnectCauseFromReasonInfo(
+                new ImsReasonInfo(ImsReasonInfo.CODE_LOW_BATTERY, 0), Call.State.DIALING));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/ConnectivityServiceMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/ConnectivityServiceMock.java
index 43763ec..9210a08 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/ConnectivityServiceMock.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/ConnectivityServiceMock.java
@@ -574,7 +574,7 @@
     }
 
     @Override
-    public boolean isTetheringSupported() {
+    public boolean isTetheringSupported(String callerPkg) {
         throw new RuntimeException("not implemented");
     }
 
@@ -651,6 +651,11 @@
     }
 
     @Override
+    public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) {
+        throw new RuntimeException("not implemented");
+    }
+
+    @Override
     public boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdownEnabled) {
         throw new RuntimeException("not implemented");
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
new file mode 100644
index 0000000..e6ca407
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2016 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.uicc;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doAnswer;
+
+import android.content.pm.Signature;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+public class UiccCarrierPrivilegeRulesTest extends TelephonyTest {
+    private UiccCarrierPrivilegeRules mUiccCarrierPrivilegeRules;
+    public UiccCarrierPrivilegeRulesTest() {
+        super();
+    }
+    private UiccCarrierPrivilegeRulesHandlerThread mTestHandlerThread;
+    private Handler mHandler;
+
+    private static final int EVENT_OPEN_LOGICAL_CHANNEL_DONE = 1;
+    private static final int EVENT_TEST_DONE = 2;
+
+    @Mock
+    private UiccCard mUiccCard;
+
+    private class UiccCarrierPrivilegeRulesHandlerThread extends HandlerThread {
+
+        private UiccCarrierPrivilegeRulesHandlerThread(String name) {
+            super(name);
+        }
+
+        @Override
+        public void onLooperPrepared() {
+            /* create a custom handler for the Handler Thread */
+            mHandler = new Handler(mTestHandlerThread.getLooper()) {
+                @Override
+                public void handleMessage(Message msg) {
+                    switch (msg.what) {
+                        case EVENT_OPEN_LOGICAL_CHANNEL_DONE:
+                            /* Upon handling this event, new CarrierPrivilegeRule
+                            will be created with the looper of HandlerThread */
+                            mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(
+                                    mUiccCard, mHandler.obtainMessage(EVENT_TEST_DONE));
+                            break;
+                        case EVENT_TEST_DONE:
+                            setReady(true);
+                            break;
+                        default:
+                            logd("Unknown Event " + msg.what);
+                    }
+                }
+            };
+            setReady(true);
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        mTestHandlerThread = new UiccCarrierPrivilegeRulesHandlerThread(TAG);
+        mTestHandlerThread.start();
+
+        waitUntilReady();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mTestHandlerThread.quit();
+        super.tearDown();
+        mUiccCarrierPrivilegeRules = null;
+    }
+
+    private void testHelper(String hexString) {
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Message message = (Message) invocation.getArguments()[2];
+                AsyncResult ar = new AsyncResult(null, new int[]{0}, null);
+                message.obj = ar;
+                message.sendToTarget();
+                return null;
+            }
+        }).when(mUiccCard).iccOpenLogicalChannel(anyString(), anyInt(), any(Message.class));
+
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Message message = (Message) invocation.getArguments()[7];
+                IccIoResult iir = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes(hexString));
+                AsyncResult ar = new AsyncResult(null, iir, null);
+                message.obj = ar;
+                message.sendToTarget();
+                return null;
+            }
+        }).when(mUiccCard).iccTransmitApduLogicalChannel(anyInt(), anyInt(), anyInt(), anyInt(),
+                anyInt(), anyInt(), anyString(), any(Message.class));
+
+
+        Message mCardOpenLogicalChannel = mHandler.obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE);
+        setReady(false);
+        mCardOpenLogicalChannel.sendToTarget();
+        waitUntilReady();
+    }
+
+    @Test
+    @SmallTest
+    public void testHandleMessage_Normal() {
+        /**
+         * FF40 45
+         *   E2 43
+         *      E1 35
+         *         C1 14 ABCD92CBB156B280FA4E1429A6ECEEB6E5C1BFE4
+         *         CA 1D 636F6D2E676F6F676C652E616E64726F69642E617070732E6D79617070
+         *      E3 0A
+         *         DB 08 0000000000000001
+         */
+        final String hexString =
+                "FF4045E243E135C114ABCD92CBB156B280FA4E1429A6ECEEB6E5C1BFE4CA1D636F6D2E676F6F676"
+                        + "C652E616E64726F69642E617070732E6D79617070E30ADB080000000000000001";
+
+        testHelper(hexString);
+
+        assertTrue(mUiccCarrierPrivilegeRules.hasCarrierPrivilegeRules());
+        assertEquals(1, mUiccCarrierPrivilegeRules.getPackageNames().size());
+        assertEquals("com.google.android.apps.myapp",
+                mUiccCarrierPrivilegeRules.getPackageNames().get(0));
+        Signature signature = new Signature("abcd92cbb156b280fa4e1429a6eceeb6e5c1bfe4");
+        assertEquals(0, mUiccCarrierPrivilegeRules.getCarrierPrivilegeStatus(signature,
+                mUiccCarrierPrivilegeRules.getPackageNames().get(0)));
+    }
+
+    @Test
+    @SmallTest
+    public void testHandleMessage_With4FD0D1() {
+        /**
+         * FF40 34
+         *   E2 32
+         *      E1 1E
+         *         4F 06 FF FF FF FF FF FF
+         *         C1 14 B6 1B E3 4A D2 C2 0D 7A FE D8 49 3C 31 3A 13 7F 89 FA 27 65
+         *      E3 10
+         *         D0 01 01
+         *         D1 01 01
+         *         DB 08 00 00 00 00 00 00 00 01
+         */
+        final String hexString = "FF4034E232E11E4F06FFFFFFFFFFFFC114B61BE34AD2C20D7AFED84"
+                + "93C313A137F89FA2765E310D00101D10101DB080000000000000001";
+
+        testHelper(hexString);
+
+        assertTrue(mUiccCarrierPrivilegeRules.hasCarrierPrivilegeRules());
+        assertEquals(0, mUiccCarrierPrivilegeRules.getPackageNames().size());
+    }
+
+    @Test
+    @SmallTest
+    public void testHandleMessage_With4FD0() {
+        /**
+         * FF40 31
+         *   E2 2F
+         *      E1 1E
+         *         4F 06 FF FF FF FF FF FF
+         *         C1 14 B6 1B E3 4A D2 C2 0D 7A FE D8 49 3C 31 3A 13 7F 89 FA 27 65
+         *      E3 0D
+         *         D0 01 01
+         *         DB 08 00 00 00 00 00 00 00 01
+         */
+        final String hexString = "FF4031E22FE11E4F06FFFFFFFFFFFFC114B61BE34AD2C20D7AFED8493C313A"
+                + "137F89FA2765E30DD00101DB080000000000000001";
+
+        testHelper(hexString);
+
+        assertTrue(mUiccCarrierPrivilegeRules.hasCarrierPrivilegeRules());
+        assertEquals(0, mUiccCarrierPrivilegeRules.getPackageNames().size());
+    }
+
+    @Test
+    @SmallTest
+    public void testHandleMessage_TwoMessages() {
+        /**
+         * FF40 68
+         *   E2 39
+         *      E1 2B
+         *         4F 06 FFFFFFFFFFFF
+         *         C1 02 B61B
+         *         CA 1D 636F6D2E676F6F676C652E616E64726F69642E617070732E6D79617070
+         *      E3 0A
+         *         D0 01 01
+         *         D1 01 01
+         *         DB 02 0001
+         *   E2 2B
+         *      E1 23
+         *         C1 02 ABCD
+         *         CA 1D 636F6D2E676F6F676C652E616E64726F69642E617070732E6D79617070
+         *      E3 04
+         *         DB 02 0001
+         */
+        final String hexString =
+                "FF4068E239E12B4F06FFFFFFFFFFFFC102B61BCA1D636F6D2E676F6F676C652E616E64726F69642"
+                        + "E617070732E6D79617070E30AD00101D10101DB020001E22BE123C102ABCDCA1D636F"
+                        + "6D2E676F6F676C652E616E64726F69642E617070732E6D79617070E304DB020001";
+
+        testHelper(hexString);
+
+        assertTrue(mUiccCarrierPrivilegeRules.hasCarrierPrivilegeRules());
+        assertEquals(2, mUiccCarrierPrivilegeRules.getPackageNames().size());
+        assertEquals("com.google.android.apps.myapp",
+                mUiccCarrierPrivilegeRules.getPackageNames().get(0));
+        Signature signature1 = new Signature("b61b");
+        assertEquals(0, mUiccCarrierPrivilegeRules.getCarrierPrivilegeStatus(signature1,
+                mUiccCarrierPrivilegeRules.getPackageNames().get(0)));
+
+        assertEquals("com.google.android.apps.myapp",
+                mUiccCarrierPrivilegeRules.getPackageNames().get(1));
+        Signature signature2 = new Signature("abcd");
+        assertEquals(0, mUiccCarrierPrivilegeRules.getCarrierPrivilegeStatus(signature2,
+                mUiccCarrierPrivilegeRules.getPackageNames().get(0)));
+    }
+
+    @Test
+    @SmallTest
+    public void testHandleMessage_InvalidRulesWith4F00() {
+        /**
+         * FF40 24
+         *   E2 22
+         *      E1 18
+         *         4F 00
+         *         C1 14 75C073AFD219AEB221948E828F066E778ADFDF23
+         *      E3 06
+         *         D0 01 01
+         *         D1 01 01
+         */
+        final String hexString = "FF4024E222E1184F00C11475C073AFD219AEB221948E828F066E778ADFDF23"
+                + "E306D00101D10101";
+
+        testHelper(hexString);
+
+        assertTrue(!mUiccCarrierPrivilegeRules.hasCarrierPrivilegeRules());
+        assertEquals(0, mUiccCarrierPrivilegeRules.getPackageNames().size());
+    }
+
+    @Test
+    @SmallTest
+    public void testHandleMessage_InvalidRulesWithoutDB() {
+        /**
+         * FF40 2A
+         *   E2 28
+         *      E1 1E
+         *         4F 06 FF FF FF FF FF FF
+         *         C1 14 B6 1B E3 4A D2 C2 0D 7A FE D8 49 3C 31 3A 13 7F 89 FA 27 65
+         *      E3 06
+         *         D0 01 01
+         *         D1 01 01
+         */
+        final String hexString = "FF402AE228E11E4F06FFFFFFFFFFFFC114B61BE34AD2C20D7AFED8493C313A"
+                + "137F89FA2765E306D00101D10101";
+
+        testHelper(hexString);
+
+        assertTrue(!mUiccCarrierPrivilegeRules.hasCarrierPrivilegeRules());
+        assertEquals(0, mUiccCarrierPrivilegeRules.getPackageNames().size());
+    }
+}