release-request-ee600ee1-fadc-4a92-9feb-e22548c84a75-for-git_oc-mr1-release-4318546 snap-temp-L23800000099760172

Change-Id: Ib36217cc33f4c3f8264dfcb7e5a875c1349ec237
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..bca337d
--- /dev/null
+++ b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
@@ -0,0 +1,471 @@
+/*
+ * 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 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.Base64;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Date;
+
+/**
+ * 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";
+
+    private int mKeyAvailability = 0;
+
+    public static final String MNC = "MNC";
+    public static final String MCC = "MCC";
+    private static final String SEPARATOR = ":";
+
+    private static final String JSON_KEY = "key";
+    private static final String JSON_TYPE = "type";
+    private static final String JSON_IDENTIFIER = "identifier";
+    private static final String JSON_EXPIRATION_DATE = "expiration-date";
+    private static final String JSON_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;
+    private 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 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.
+     **/
+    private void resetRenewalAlarm() {
+        cleanupRenewalAlarms();
+        int slotId = mPhone.getPhoneId();
+        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;
+        }
+        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);
+    }
+
+    /**
+     *  checks if the download was sent by this particular instance. We do this by including the
+     *  slot id in the key. If no value is found, we know that the download was not for this
+     *  instance of the phone.
+     **/
+    private boolean isValidDownload(String mccMnc) {
+        String mccCurrent = "";
+        String mncCurrent = "";
+        String mccSource = "";
+        String mncSource = "";
+        final TelephonyManager telephonyManager =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        String networkOperator = telephonyManager.getNetworkOperator(mPhone.getSubId());
+
+        if (TextUtils.isEmpty(networkOperator) || TextUtils.isEmpty(mccMnc)) {
+            Log.e(LOG_TAG, "networkOperator or mcc/mnc is empty");
+            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 = networkOperator.substring(0, 3);
+        mncCurrent = networkOperator.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);
+
+        if (cursor == null) {
+            return;
+        }
+        if (cursor.moveToFirst()) {
+            int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
+            if (DownloadManager.STATUS_SUCCESSFUL == cursor.getInt(columnIndex)) {
+                try {
+                    final InputStream 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);
+                }
+            }
+            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) {
+        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+        StringBuilder sb = new StringBuilder();
+
+        String line;
+        try {
+            while ((line = reader.readLine()) != null) {
+                sb.append(line).append('\n');
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                is.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Converts the string into a json object to retreive the nodes. The Json should have 3 nodes,
+     * including the Carrier public key, the key type and the key identifier. Once the nodes have
+     * been extracted, they get persisted to the database. Sample:
+     *      "carrier-keys": [ { "key": "",
+     *                         "type": WLAN,
+     *                         "identifier": "",
+     *                         "expiration-date": 1502577746000
+     *                        } ]
+     * @param jsonStr the json string.
+     * @param mccMnc contains the mcc, mnc
+     */
+    private void parseJsonAndPersistKey(String jsonStr, String mccMnc) {
+        if (TextUtils.isEmpty(jsonStr) || TextUtils.isEmpty(mccMnc)) {
+            Log.e(LOG_TAG, "jsonStr or mcc, mnc: is empty");
+            return;
+        }
+        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);
+                String carrierKey = key.getString(JSON_KEY);
+                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;
+                }
+                long expiration_date = key.getLong(JSON_EXPIRATION_DATE);
+                String identifier = key.getString(JSON_IDENTIFIER);
+                savePublicKey(carrierKey, type, identifier, expiration_date,
+                        mcc, mnc);
+            }
+        } catch (final JSONException e) {
+            Log.e(LOG_TAG, "Json parsing error: " + e.getMessage());
+        }
+    }
+
+    /**
+     * introspects the mKeyAvailability bitmask
+     * @return true if the digit at position k is 1, else false.
+     */
+
+    private 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);
+        final TelephonyManager telephonyManager =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        String mcc = "";
+        String mnc = "";
+        String networkOperator = telephonyManager.getNetworkOperator(mPhone.getSubId());
+
+        if (!TextUtils.isEmpty(networkOperator)) {
+            mcc = networkOperator.substring(0, 3);
+            mnc = networkOperator.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;
+    }
+
+    private void savePublicKey(String key, int type, String identifier, long expirationDate,
+                               String mcc, String mnc) {
+        byte[] keyBytes = Base64.decode(key.getBytes(), Base64.DEFAULT);
+        ImsiEncryptionInfo imsiEncryptionInfo = new ImsiEncryptionInfo(mcc, mnc, type, identifier,
+                keyBytes, new Date(expirationDate));
+        mPhone.setCarrierInfoForImsiEncryption(imsiEncryptionInfo);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index afaa431..35a2d41 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) {
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index 4598b0c..2ec5101 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -76,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;
@@ -970,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");
 
@@ -1075,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;
@@ -1150,7 +1152,7 @@
             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));
 
@@ -1188,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));
@@ -1401,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) {
@@ -1710,7 +1720,7 @@
                 }
                 sendMessage(msg);
             }
-            setPremiumSmsPermission(mTracker.mAppInfo.packageName, newSmsPermission);
+            setPremiumSmsPermission(mTracker.getAppPackageName(), newSmsPermission);
         }
 
         @Override
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/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 54c8bea..850ccaa 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -217,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) {
@@ -328,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);
@@ -2774,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()) {
@@ -2783,6 +2787,8 @@
             return;
         }
 
+        checkDataRoamingStatus(settingChanged);
+
         if (getDataRoamingEnabled()) {
             if (DBG) log("onDataRoamingOnOrSettingsChanged: setup data on roaming");
 
@@ -2798,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) {
@@ -2927,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) {
@@ -2951,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) {
@@ -3762,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:
@@ -4176,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/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index 9a03c44..7e8a439 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
@@ -442,7 +442,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
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 c488348..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,10 +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;
@@ -36,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;
@@ -43,8 +49,10 @@
 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;
@@ -53,8 +61,11 @@
 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;
@@ -67,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;
@@ -197,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());
+    }
 }