Merge "Remove sysprop based radio reset sync mechanism"
diff --git a/src/java/com/android/internal/telephony/Connection.java b/src/java/com/android/internal/telephony/Connection.java
index 4c42d71..74ee5c5 100644
--- a/src/java/com/android/internal/telephony/Connection.java
+++ b/src/java/com/android/internal/telephony/Connection.java
@@ -34,6 +34,7 @@
  * {@hide}
  */
 public abstract class Connection {
+    private static final String TAG = "Connection";
 
     public interface PostDialListener {
         void onPostDialWait();
@@ -827,6 +828,16 @@
     public void setConnectionExtras(Bundle extras) {
         if (extras != null) {
             mExtras = new Bundle(extras);
+
+            int previousCount = mExtras.size();
+            // Prevent vendors from passing in extras other than primitive types and android API
+            // parcelables.
+            mExtras = mExtras.filterValues();
+            int filteredCount = mExtras.size();
+            if (filteredCount != previousCount) {
+                Rlog.i(TAG, "setConnectionExtras: filtering " + (previousCount - filteredCount)
+                        + " invalid extras.");
+            }
         } else {
             mExtras = null;
         }
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 9873d97..0c34749 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -283,7 +283,7 @@
             tm.setPhoneType(getPhoneId(), PhoneConstants.PHONE_TYPE_GSM);
             mIccCardProxy.setVoiceRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_UMTS);
         } else {
-            mCdmaSubscriptionSource = CdmaSubscriptionSourceManager.SUBSCRIPTION_SOURCE_UNKNOWN;
+            mCdmaSubscriptionSource = mCdmaSSM.getCdmaSubscriptionSource();
             // This is needed to handle phone process crashes
             mIsPhoneInEcmState = getInEcmMode();
             if (mIsPhoneInEcmState) {
@@ -502,7 +502,7 @@
 
             ret = PhoneConstants.DataState.DISCONNECTED;
         } else if (mSST.getCurrentDataConnectionState() != ServiceState.STATE_IN_SERVICE
-                && (isPhoneTypeCdma() ||
+                && (isPhoneTypeCdma() || isPhoneTypeCdmaLte() ||
                 (isPhoneTypeGsm() && !apnType.equals(PhoneConstants.APN_TYPE_EMERGENCY)))) {
             // If we're out of service, open TCP sockets may still work
             // but no data will flow
@@ -1852,6 +1852,8 @@
 
     @Override
     public void setTTYMode(int ttyMode, Message onComplete) {
+        // Send out the TTY Mode change over RIL as well
+        super.setTTYMode(ttyMode, onComplete);
         if (mImsPhone != null) {
             mImsPhone.setTTYMode(ttyMode, onComplete);
         }
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 1ccf1c4..fad0ddb 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -224,6 +224,9 @@
     // Key used to read/write "disable DNS server check" pref (used for testing)
     private static final String DNS_SERVER_CHECK_DISABLED_KEY = "dns_server_check_disabled_key";
 
+    // Integer used to let the calling application know that the we are ignoring auto mode switch.
+    private static final int ALREADY_IN_AUTO_SELECTION = 1;
+
     /**
      * Small container class used to hold information relevant to
      * the carrier selection process. operatorNumeric can be ""
@@ -1175,6 +1178,11 @@
             mCi.setNetworkSelectionModeAutomatic(msg);
         } else {
             Rlog.d(LOG_TAG, "setNetworkSelectionModeAutomatic - already auto, ignoring");
+            // let the calling application know that the we are ignoring automatic mode switch.
+            if (nsm.message != null) {
+                nsm.message.arg1 = ALREADY_IN_AUTO_SELECTION;
+            }
+
             ar.userObj = nsm;
             handleSetSelectNetwork(ar);
         }
diff --git a/src/java/com/android/internal/telephony/TelephonyTester.java b/src/java/com/android/internal/telephony/TelephonyTester.java
index 3de356f..3608c20 100644
--- a/src/java/com/android/internal/telephony/TelephonyTester.java
+++ b/src/java/com/android/internal/telephony/TelephonyTester.java
@@ -20,11 +20,9 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.SharedPreferences;
 import android.net.Uri;
 import android.os.BadParcelableException;
 import android.os.Build;
-import android.preference.PreferenceManager;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 
@@ -33,10 +31,10 @@
 import com.android.ims.ImsConferenceState;
 import com.android.ims.ImsExternalCallState;
 import com.android.ims.ImsReasonInfo;
+import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCall;
-import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
 import com.android.internal.telephony.test.TestConferenceEventPackageParser;
 
 import java.io.File;
@@ -83,6 +81,17 @@
     private static final String ACTION_TEST_HANDOVER_FAIL =
             "com.android.internal.telephony.TestHandoverFail";
 
+    /**
+     * Test-only intent used to trigger signalling of a
+     * {@link com.android.internal.telephony.gsm.SuppServiceNotification} to the {@link ImsPhone}.
+     * Use {@link #EXTRA_CODE} to specify the
+     * {@link com.android.internal.telephony.gsm.SuppServiceNotification#code}.
+     */
+    private static final String ACTION_TEST_SUPP_SRVC_NOTIFICATION =
+            "com.android.internal.telephony.TestSuppSrvcNotification";
+
+    private static final String EXTRA_CODE = "code";
+
     private static List<ImsExternalCallState> mImsExternalCallStates = null;
 
     private Phone mPhone;
@@ -111,6 +120,9 @@
                 } else if (action.equals(ACTION_TEST_HANDOVER_FAIL)) {
                     log("handle handover fail test intent");
                     handleHandoverFailedIntent();
+                } else if (action.equals(ACTION_TEST_SUPP_SRVC_NOTIFICATION)) {
+                    log("handle supp service notification test intent");
+                    sendTestSuppServiceNotification(intent);
                 } else {
                     if (DBG) log("onReceive: unknown action=" + action);
                 }
@@ -137,6 +149,7 @@
                 filter.addAction(ACTION_TEST_CONFERENCE_EVENT_PACKAGE);
                 filter.addAction(ACTION_TEST_DIALOG_EVENT_PACKAGE);
                 filter.addAction(ACTION_TEST_HANDOVER_FAIL);
+                filter.addAction(ACTION_TEST_SUPP_SRVC_NOTIFICATION);
                 mImsExternalCallStates = new ArrayList<ImsExternalCallState>();
             }
 
@@ -251,4 +264,18 @@
             mImsExternalCallStates.add(state);
         }
     }
+
+    private void sendTestSuppServiceNotification(Intent intent) {
+        if (intent.hasExtra(EXTRA_CODE)) {
+            int code = intent.getIntExtra(EXTRA_CODE, -1);
+            ImsPhone imsPhone = (ImsPhone) mPhone;
+            if (imsPhone == null) {
+                return;
+            }
+            log("Test supp service notification:" + code);
+            SuppServiceNotification suppServiceNotification = new SuppServiceNotification();
+            suppServiceNotification.code = code;
+            imsPhone.notifySuppSvcNotification(suppServiceNotification);
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/cat/CatService.java b/src/java/com/android/internal/telephony/cat/CatService.java
index a242de4..6e4c899 100644
--- a/src/java/com/android/internal/telephony/cat/CatService.java
+++ b/src/java/com/android/internal/telephony/cat/CatService.java
@@ -881,8 +881,9 @@
         // This sends an intent with CARD_ABSENT (0 - false) /CARD_PRESENT (1 - true).
         intent.putExtra(AppInterface.CARD_STATUS, cardPresent);
         intent.setComponent(AppInterface.getDefaultSTKApplication());
+        intent.putExtra("SLOT_ID", mSlotId);
         CatLog.d(this, "Sending Card Status: "
-                + cardState + " " + "cardPresent: " + cardPresent);
+                + cardState + " " + "cardPresent: " + cardPresent +  "SLOT_ID: " +  mSlotId);
         mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
     }
 
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index 1f7c243..9b86712 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -705,7 +705,6 @@
 
     @Override
     public void setTTYMode(int ttyMode, Message onComplete) {
-        super.setTTYMode(ttyMode, onComplete);
         mCT.setTtyMode(ttyMode);
     }
 
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index 0898093..9b4aced 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -132,6 +132,7 @@
             "UTLTE", "UTWiFi"};
 
     private TelephonyMetrics mMetrics;
+    private boolean mCarrierConfigLoaded = false;
 
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
@@ -216,6 +217,7 @@
                     log("onReceive : Updating mAllowEmergencyVideoCalls = " +
                             mAllowEmergencyVideoCalls);
                 }
+                mCarrierConfigLoaded  = true;
             } else if (TelecomManager.ACTION_CHANGE_DEFAULT_DIALER.equals(intent.getAction())) {
                 mDefaultDialerUid.set(getPackageUid(context, intent.getStringExtra(
                         TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME)));
@@ -302,6 +304,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
@@ -704,6 +707,8 @@
             return NetworkStats.UID_ALL;
         }
 
+        // Initialize to UID_ALL so at least it can be counted to overall data usage if
+        // the dialer's package uid is not available.
         int uid = NetworkStats.UID_ALL;
         try {
             uid = context.getPackageManager().getPackageUid(pkg, 0);
@@ -756,6 +761,11 @@
             multiEndpoint.setExternalCallStateListener(
                     mPhone.getExternalCallTracker().getExternalCallStateListener());
         }
+
+        if (mCarrierConfigLoaded) {
+            ImsManager.updateImsServiceConfig(mPhone.getContext(),
+                    mPhone.getPhoneId(), true);
+        }
     }
 
     private void stopListeningForCalls() {
@@ -978,6 +988,8 @@
                 CarrierConfigManager.KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_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);
 
@@ -2498,7 +2510,7 @@
             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.
@@ -2510,19 +2522,28 @@
                 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.");
+            ImsPhoneConnection conn = findConnection(imsCall);
+            if (conn != null) {
+                // 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 (isHandoverFromWifi && imsCall.isVideoCall()) {
+                    if (mNotifyHandoverVideoFromWifiToLTE && mIsDataEnabled) {
+                        log("onCallHandover :: notifying of WIFI to LTE handover.");
+                        conn.onConnectionEvent(
+                                TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE, null);
+                    }
+
+                    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.");
             }
 
             mMetrics.writeOnImsCallHandoverEvent(mPhone.getPhoneId(),
@@ -2959,6 +2980,17 @@
         // a separate entry if uid is different from the previous snapshot.
         NetworkStats vtDataUsageUidSnapshot = new NetworkStats(currentTime, 1);
         vtDataUsageUidSnapshot.combineAllValues(mVtDataUsageUidSnapshot);
+
+        // The dialer uid might not be initialized correctly during boot up due to telecom service
+        // not ready or its default dialer cache not ready. So we double check again here to see if
+        // default dialer uid is really not available.
+        if (mDefaultDialerUid.get() == NetworkStats.UID_ALL) {
+            final TelecomManager telecomManager =
+                    (TelecomManager) mPhone.getContext().getSystemService(Context.TELECOM_SERVICE);
+            mDefaultDialerUid.set(
+                    getPackageUid(mPhone.getContext(), telecomManager.getDefaultDialerPackage()));
+        }
+
         // Since the modem only reports the total vt data usage rather than rx/tx separately,
         // the only thing we can do here is splitting the usage into half rx and half tx.
         vtDataUsageUidSnapshot.combineValues(new NetworkStats.Entry(
@@ -3346,27 +3378,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.
@@ -3386,27 +3437,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
@@ -3416,15 +3478,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);
+                }
+            }
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
index b39a6fb..0ad81d2 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
@@ -107,6 +107,14 @@
     private ImsRttTextHandler mRttTextHandler;
     private android.telecom.Connection.RttTextStream mRttTextStream;
 
+    /**
+     * 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;
@@ -242,11 +250,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
@@ -1185,6 +1197,7 @@
         Rlog.i(LOG_TAG, "ImsPhoneConnection: changeToPausedState - setting paused bit; "
                 + "newVideoState=" + VideoProfile.videoStateToString(newVideoState));
         updateVideoState(newVideoState);
+        mShouldIgnoreVideoStateChanges = true;
     }
 
     public void changeToUnPausedState() {
@@ -1192,5 +1205,17 @@
         Rlog.i(LOG_TAG, "ImsPhoneConnection: changeToUnPausedState - unsetting paused bit; "
                 + "newVideoState=" + VideoProfile.videoStateToString(newVideoState));
         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/uicc/AdnRecord.java b/src/java/com/android/internal/telephony/uicc/AdnRecord.java
index 203236c..4414caf 100644
--- a/src/java/com/android/internal/telephony/uicc/AdnRecord.java
+++ b/src/java/com/android/internal/telephony/uicc/AdnRecord.java
@@ -19,8 +19,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
 import android.telephony.Rlog;
+import android.text.TextUtils;
 
 import com.android.internal.telephony.GsmAlphabet;
 
@@ -248,7 +248,8 @@
             Rlog.w(LOG_TAG, "[buildAdnString] Max length of tag is " + footerOffset);
             return null;
         } else {
-            bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(mNumber);
+            bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(
+                    mNumber, PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
 
             System.arraycopy(bcdNumber, 0, adnString,
                     footerOffset + ADN_TON_AND_NPI, bcdNumber.length);
@@ -289,7 +290,10 @@
             }
 
             mNumber += PhoneNumberUtils.calledPartyBCDFragmentToString(
-                                        extRecord, 2, 0xff & extRecord[1]);
+                    extRecord,
+                    2,
+                    0xff & extRecord[1],
+                    PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
 
             // We don't support ext record chaining.
 
@@ -327,7 +331,10 @@
             // the ME (see note 2)."
 
             mNumber = PhoneNumberUtils.calledPartyBCDToString(
-                            record, footerOffset + 1, numberLength);
+                    record,
+                    footerOffset + 1,
+                    numberLength,
+                    PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
 
 
             mExtRecord = 0xff & record[record.length - 1];
diff --git a/src/java/com/android/internal/telephony/uicc/SIMRecords.java b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
index 724b478..dad1ee2 100755
--- a/src/java/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
@@ -563,7 +563,8 @@
                 // Spec reference for EF_CFIS contents, TS 51.011 section 10.3.46.
                 if (enable && !TextUtils.isEmpty(dialNumber)) {
                     logv("EF_CFIS: updating cf number, " + Rlog.pii(LOG_TAG, dialNumber));
-                    byte[] bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(dialNumber);
+                    byte[] bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(
+                            dialNumber, PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
 
                     System.arraycopy(bcdNumber, 0, mEfCfis, CFIS_TON_NPI_OFFSET, bcdNumber.length);
 
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java b/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
index 93d54d0..68b72c8 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
@@ -56,7 +56,10 @@
     private static final String LOG_TAG = "UiccCarrierPrivilegeRules";
     private static final boolean DBG = false;
 
-    private static final String AID = "A00000015141434C00";
+    private static final String ARAM_AID = "A00000015141434C00";
+    private static final String ARAD_AID = "A00000015144414300";
+    private static final int ARAM = 1;
+    private static final int ARAD = 0;
     private static final int CLA = 0x80;
     private static final int COMMAND = 0xCA;
     private static final int P1 = 0xFF;
@@ -210,18 +213,21 @@
     private String mStatusMessage;  // Only used for debugging.
     private int mChannelId; // Channel Id for communicating with UICC.
     private int mRetryCount;  // Number of retries for open logical channel.
+    private boolean mCheckedRules = false;  // Flag that used to mark whether get rules from ARA-D.
+    private int mAIDInUse;  // Message component to identify which AID is currently in-use.
     private final Runnable mRetryRunnable = new Runnable() {
         @Override
         public void run() {
-            openChannel();
+            openChannel(mAIDInUse);
         }
     };
 
-    private void openChannel() {
+    private void openChannel(int aidId) {
         // Send open logical channel request.
+        String aid = (aidId == ARAD) ? ARAD_AID : ARAM_AID;
         int p2 = 0x00;
-        mUiccCard.iccOpenLogicalChannel(AID, p2, /* supported p2 value */
-            obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE, null));
+        mUiccCard.iccOpenLogicalChannel(aid, p2, /* supported p2 value */
+                obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE, 0, aidId, null));
     }
 
     public UiccCarrierPrivilegeRules(UiccCard uiccCard, Message loadedCallback) {
@@ -233,7 +239,9 @@
         mRules = "";
         mAccessRules = new ArrayList<AccessRule>();
 
-        openChannel();
+        // Open logical channel with ARA_D.
+        mAIDInUse = ARAD;
+        openChannel(mAIDInUse);
     }
 
     /**
@@ -412,6 +420,7 @@
     @Override
     public void handleMessage(Message msg) {
         AsyncResult ar;
+        mAIDInUse = msg.arg2;  // 0 means ARA-D and 1 means ARA-M.
 
         switch (msg.what) {
 
@@ -421,23 +430,34 @@
                 if (ar.exception == null && ar.result != null) {
                     mChannelId = ((int[]) ar.result)[0];
                     mUiccCard.iccTransmitApduLogicalChannel(mChannelId, CLA, COMMAND, P1, P2, P3,
-                            DATA, obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE,
-                                    mChannelId));
+                            DATA, obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE, mChannelId,
+                                    mAIDInUse));
                 } else {
                     // MISSING_RESOURCE could be due to logical channels temporarily unavailable,
                     // so we retry up to MAX_RETRY times, with an interval of RETRY_INTERVAL_MS.
                     if (ar.exception instanceof CommandException && mRetryCount < MAX_RETRY
                             && ((CommandException) (ar.exception)).getCommandError()
-                                    == CommandException.Error.MISSING_RESOURCE) {
+                            == CommandException.Error.MISSING_RESOURCE) {
                         mRetryCount++;
                         removeCallbacks(mRetryRunnable);
                         postDelayed(mRetryRunnable, RETRY_INTERVAL_MS);
                     } else {
-                        // if rules cannot be read from ARA applet,
-                        // fallback to PKCS15-based ARF.
-                        log("No ARA, try ARF next.");
-                        mUiccPkcs15 = new UiccPkcs15(mUiccCard,
-                                obtainMessage(EVENT_PKCS15_READ_DONE));
+                        if (mAIDInUse == ARAD) {
+                            // Open logical channel with ARA_M.
+                            mRules = "";
+                            openChannel(1);
+                        }
+                        if (mAIDInUse == ARAM) {
+                            if (mCheckedRules) {
+                                updateState(STATE_LOADED, "Success!");
+                            } else {
+                                // if rules cannot be read from both ARA_D and ARA_M applet,
+                                // fallback to PKCS15-based ARF.
+                                log("No ARA, try ARF next.");
+                                mUiccPkcs15 = new UiccPkcs15(mUiccCard,
+                                        obtainMessage(EVENT_PKCS15_READ_DONE));
+                            }
+                        }
                     }
                 }
                 break;
@@ -453,34 +473,49 @@
                             mRules += IccUtils.bytesToHexString(response.payload)
                                     .toUpperCase(Locale.US);
                             if (isDataComplete()) {
-                                mAccessRules = parseRules(mRules);
-                                updateState(STATE_LOADED, "Success!");
+                                mAccessRules.addAll(parseRules(mRules));
+                                if (mAIDInUse == ARAD) {
+                                    mCheckedRules = true;
+                                } else {
+                                    updateState(STATE_LOADED, "Success!");
+                                }
                             } else {
                                 mUiccCard.iccTransmitApduLogicalChannel(mChannelId, CLA, COMMAND,
                                         P1, P2_EXTENDED_DATA, P3, DATA,
                                         obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE,
-                                                mChannelId));
+                                                mChannelId, mAIDInUse));
                                 break;
                             }
                         } catch (IllegalArgumentException | IndexOutOfBoundsException ex) {
-                            updateState(STATE_ERROR, "Error parsing rules: " + ex);
+                            if (mAIDInUse == ARAM) {
+                                updateState(STATE_ERROR, "Error parsing rules: " + ex);
+                            }
                         }
                     } else {
-                        String errorMsg = "Invalid response: payload=" + response.payload
-                                + " sw1=" + response.sw1 + " sw2=" + response.sw2;
-                        updateState(STATE_ERROR, errorMsg);
+                        if (mAIDInUse == ARAM) {
+                            String errorMsg = "Invalid response: payload=" + response.payload
+                                    + " sw1=" + response.sw1 + " sw2=" + response.sw2;
+                            updateState(STATE_ERROR, errorMsg);
+                        }
                     }
                 } else {
-                    updateState(STATE_ERROR, "Error reading value from SIM.");
+                    if (mAIDInUse == ARAM) {
+                        updateState(STATE_ERROR, "Error reading value from SIM.");
+                    }
                 }
 
                 mUiccCard.iccCloseLogicalChannel(mChannelId, obtainMessage(
-                        EVENT_CLOSE_LOGICAL_CHANNEL_DONE));
+                        EVENT_CLOSE_LOGICAL_CHANNEL_DONE, 0, mAIDInUse));
                 mChannelId = -1;
                 break;
 
             case EVENT_CLOSE_LOGICAL_CHANNEL_DONE:
                 log("EVENT_CLOSE_LOGICAL_CHANNEL_DONE");
+                if (mAIDInUse == ARAD) {
+                    // Close logical channel with ARA_D and then open logical channel with ARA_M.
+                    mRules = "";
+                    openChannel(1);
+                }
                 break;
 
             case EVENT_PKCS15_READ_DONE:
@@ -513,7 +548,7 @@
             String lengthBytes = allRules.parseLength(mRules);
             log("isDataComplete lengthBytes: " + lengthBytes);
             if (mRules.length() == TAG_ALL_REF_AR_DO.length() + lengthBytes.length() +
-                                   allRules.length) {
+                    allRules.length) {
                 log("isDataComplete yes");
                 return true;
             } else {
@@ -543,7 +578,7 @@
             if (accessRule != null) {
                 accessRules.add(accessRule);
             } else {
-              Rlog.e(LOG_TAG, "Skip unrecognized rule." + refArDo.value);
+                Rlog.e(LOG_TAG, "Skip unrecognized rule." + refArDo.value);
             }
         }
         return accessRules;
@@ -678,15 +713,15 @@
      * Converts state into human readable format.
      */
     private String getStateString(int state) {
-      switch (state) {
-        case STATE_LOADING:
-            return "STATE_LOADING";
-        case STATE_LOADED:
-            return "STATE_LOADED";
-        case STATE_ERROR:
-            return "STATE_ERROR";
-        default:
-            return "UNKNOWN";
-      }
+        switch (state) {
+            case STATE_LOADING:
+                return "STATE_LOADING";
+            case STATE_LOADED:
+                return "STATE_LOADED";
+            case STATE_ERROR:
+                return "STATE_ERROR";
+            default:
+                return "UNKNOWN";
+        }
     }
-}
+}
\ No newline at end of file
diff --git a/tests/telephonytests/src/android/telephony/ims/ImsCallProfileTest.java b/tests/telephonytests/src/android/telephony/ims/ImsCallProfileTest.java
new file mode 100644
index 0000000..bc8d4e1
--- /dev/null
+++ b/tests/telephonytests/src/android/telephony/ims/ImsCallProfileTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+// Note: Package name is intentionally wrong for this test; the internal junk class is used to test
+// that parcelables of types other than android.* are stripped out.
+package com.android.telephony.ims;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.test.runner.AndroidJUnit4;
+import android.telecom.DisconnectCause;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.ims.ImsCallProfile;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the {@link com.android.ims.ImsCallProfile} class.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ImsCallProfileTest {
+    // A test-only parcelable class which is not in the android.* namespace.
+    private static class JunkParcelable implements Parcelable {
+        private int mTest;
+
+        JunkParcelable() {
+        }
+
+        protected JunkParcelable(Parcel in) {
+            mTest = in.readInt();
+        }
+
+        public static final Creator<JunkParcelable> CREATOR = new Creator<JunkParcelable>() {
+            @Override
+            public JunkParcelable createFromParcel(Parcel in) {
+                return new JunkParcelable(in);
+            }
+
+            @Override
+            public JunkParcelable[] newArray(int size) {
+                return new JunkParcelable[size];
+            }
+        };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mTest);
+        }
+    }
+
+    /**
+     * Ensures that the {@link ImsCallProfile} will discard invalid extras when it is parceled.
+     */
+    @Test
+    @SmallTest
+    public void testExtrasCleanup() {
+        ImsCallProfile srcParcel = new ImsCallProfile();
+        // Put in a private parcelable type.
+        srcParcel.mCallExtras.putParcelable("JUNK", new JunkParcelable());
+        // Put in an api defined parcelable type.
+        srcParcel.mCallExtras.putParcelable("NOTJUNK", new DisconnectCause(DisconnectCause.BUSY));
+        // Put in some valid things.
+        srcParcel.mCallExtras.putInt("INT", 1);
+        srcParcel.mCallExtras.putString("STRING", "hello");
+
+        // Parcel it.
+        Parcel parcel = Parcel.obtain();
+        srcParcel.writeToParcel(parcel, 0);
+        byte[] parcelBytes = parcel.marshall();
+        parcel.recycle();
+
+        // Unparcel it.
+        parcel = Parcel.obtain();
+        parcel.unmarshall(parcelBytes, 0, parcelBytes.length);
+        parcel.setDataPosition(0);
+        ImsCallProfile unparceledProfile = ImsCallProfile.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        assertNotNull(unparceledProfile.mCallExtras);
+        assertEquals(3, unparceledProfile.mCallExtras.size());
+        assertEquals(1, unparceledProfile.getCallExtraInt("INT"));
+        assertEquals("hello", unparceledProfile.getCallExtra("STRING"));
+        assertFalse(unparceledProfile.mCallExtras.containsKey("JUNK"));
+
+        DisconnectCause parceledCause = unparceledProfile.mCallExtras.getParcelable("NOTJUNK");
+        assertEquals(DisconnectCause.BUSY, parceledCause.getCode());
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
index f2e5da1..06cd54f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
@@ -41,6 +41,10 @@
         assertEquals("+14155551212", sms.getServiceCenterAddress());
         assertEquals("+16505551111", sms.getOriginatingAddress());
         assertEquals("(Subject)Test", sms.getMessageBody());
+
+        pdu = "07914151551512F20409E1BADCBE5AF100006060605130308A04D4F29C0E";
+        sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
+        assertEquals("*#abc#*51", sms.getOriginatingAddress());
     }
 
     @SmallTest
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
index f4caf48..faeabd5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
@@ -201,6 +201,20 @@
 
     @SmallTest
     @Test
+    public void testNonIntegerAddress() {
+        byte[] b = new byte[6];
+        b[0] = (byte) 0x81; b[1] = (byte) 0xba; b[2] = (byte) 0xdc; b[3] = (byte) 0xbe;
+        b[4] = (byte) 0x5a; b[5] = (byte) 0xf1;
+        assertEquals("*#abc#*51",
+                PhoneNumberUtils.calledPartyBCDToString(
+                        b, 0, 6, PhoneNumberUtils.BCD_EXTENDED_TYPE_CALLED_PARTY));
+        assertEquals("*#,N;#*51",
+                PhoneNumberUtils.calledPartyBCDToString(
+                        b, 0, 6, PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN));
+    }
+
+    @SmallTest
+    @Test
     public void testExtractNetworkPortionAlt() throws Exception {
         assertEquals(
                 "+17005554141",
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..82a3466 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/ConnectivityServiceMock.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/ConnectivityServiceMock.java
@@ -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/UiccCardTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
index 78944eb..a7c3c48 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
@@ -208,9 +208,9 @@
         waitForMs(50);
 
         assertTrue(mUicccard.areCarrierPriviligeRulesLoaded());
-        verify(mSimulatedCommandsVerifier, times(1)).iccOpenLogicalChannel(isA(String.class),
+        verify(mSimulatedCommandsVerifier, times(2)).iccOpenLogicalChannel(isA(String.class),
                 anyInt(), isA(Message.class));
-        verify(mSimulatedCommandsVerifier, times(1)).iccTransmitApduLogicalChannel(
+        verify(mSimulatedCommandsVerifier, times(2)).iccTransmitApduLogicalChannel(
                 eq(mChannelId), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyString(),
                 isA(Message.class)
         );
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
index e6ca407..8bb5d8c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
@@ -18,6 +18,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doAnswer;
@@ -124,6 +125,14 @@
         }).when(mUiccCard).iccTransmitApduLogicalChannel(anyInt(), anyInt(), anyInt(), anyInt(),
                 anyInt(), anyInt(), anyString(), any(Message.class));
 
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Message message = (Message) invocation.getArguments()[1];
+                message.sendToTarget();
+                return null;
+            }
+        }).when(mUiccCard).iccCloseLogicalChannel(anyInt(), any(Message.class));
 
         Message mCardOpenLogicalChannel = mHandler.obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE);
         setReady(false);
@@ -133,7 +142,7 @@
 
     @Test
     @SmallTest
-    public void testHandleMessage_Normal() {
+    public void testParseRule_Normal() {
         /**
          * FF40 45
          *   E2 43
@@ -150,7 +159,7 @@
         testHelper(hexString);
 
         assertTrue(mUiccCarrierPrivilegeRules.hasCarrierPrivilegeRules());
-        assertEquals(1, mUiccCarrierPrivilegeRules.getPackageNames().size());
+        assertEquals(2, mUiccCarrierPrivilegeRules.getPackageNames().size());
         assertEquals("com.google.android.apps.myapp",
                 mUiccCarrierPrivilegeRules.getPackageNames().get(0));
         Signature signature = new Signature("abcd92cbb156b280fa4e1429a6eceeb6e5c1bfe4");
@@ -160,7 +169,7 @@
 
     @Test
     @SmallTest
-    public void testHandleMessage_With4FD0D1() {
+    public void testParseRule_With4FD0D1() {
         /**
          * FF40 34
          *   E2 32
@@ -183,7 +192,7 @@
 
     @Test
     @SmallTest
-    public void testHandleMessage_With4FD0() {
+    public void testParseRule_With4FD0() {
         /**
          * FF40 31
          *   E2 2F
@@ -205,7 +214,7 @@
 
     @Test
     @SmallTest
-    public void testHandleMessage_TwoMessages() {
+    public void testParseRule_TwoMessages() {
         /**
          * FF40 68
          *   E2 39
@@ -232,7 +241,7 @@
         testHelper(hexString);
 
         assertTrue(mUiccCarrierPrivilegeRules.hasCarrierPrivilegeRules());
-        assertEquals(2, mUiccCarrierPrivilegeRules.getPackageNames().size());
+        assertEquals(4, mUiccCarrierPrivilegeRules.getPackageNames().size());
         assertEquals("com.google.android.apps.myapp",
                 mUiccCarrierPrivilegeRules.getPackageNames().get(0));
         Signature signature1 = new Signature("b61b");
@@ -248,7 +257,7 @@
 
     @Test
     @SmallTest
-    public void testHandleMessage_InvalidRulesWith4F00() {
+    public void testParseRule_InvalidRulesWith4F00() {
         /**
          * FF40 24
          *   E2 22
@@ -270,7 +279,7 @@
 
     @Test
     @SmallTest
-    public void testHandleMessage_InvalidRulesWithoutDB() {
+    public void testParseRule_InvalidRulesWithoutDB() {
         /**
          * FF40 2A
          *   E2 28
@@ -289,4 +298,323 @@
         assertTrue(!mUiccCarrierPrivilegeRules.hasCarrierPrivilegeRules());
         assertEquals(0, mUiccCarrierPrivilegeRules.getPackageNames().size());
     }
+
+    private static final String ARAM = "A00000015141434C00";
+    private static final String ARAD = "A00000015144414300";
+    private static final String PKCS15_AID = "A000000063504B43532D3135";
+
+    @Test
+    @SmallTest
+    public void testAID_OnlyARAM() {
+        final String hexString =
+                "FF4045E243E135C114ABCD92CBB156B280FA4E1429A6ECEEB6E5C1BFE4CA1D636F6D2E676F6F676"
+                        + "C652E616E64726F69642E617070732E6D79617070E30ADB080000000000000001";
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                String aid = (String) invocation.getArguments()[0];
+                Message message = (Message) invocation.getArguments()[2];
+                if (aid.equals(ARAM)) {
+                    AsyncResult ar = new AsyncResult(null, new int[]{0}, null);
+                    message.obj = ar;
+                    message.arg2 = 1;
+                    message.sendToTarget();
+                } else {
+                    AsyncResult ar = new AsyncResult(null, null, 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));
+
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Message message = (Message) invocation.getArguments()[1];
+                message.sendToTarget();
+                return null;
+            }
+        }).when(mUiccCard).iccCloseLogicalChannel(anyInt(), any(Message.class));
+
+
+        Message mCardOpenLogicalChannel = mHandler.obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE);
+        setReady(false);
+        mCardOpenLogicalChannel.sendToTarget();
+        waitUntilReady();
+
+        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 testAID_OnlyARAD() {
+        final String hexString =
+                "FF4045E243E135C114ABCD92CBB156B280FA4E1429A6ECEEB6E5C1BFE4CA1D636F6D2E676F6F676"
+                        + "C652E616E64726F69642E617070732E6D79617070E30ADB080000000000000001";
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                String aid = (String) invocation.getArguments()[0];
+                Message message = (Message) invocation.getArguments()[2];
+                if (aid.equals(ARAD)) {
+                    AsyncResult ar = new AsyncResult(null, new int[]{0}, null);
+                    message.obj = ar;
+                    message.arg2 = 0;
+                    message.sendToTarget();
+                } else {
+                    AsyncResult ar = new AsyncResult(null, null, null);
+                    message.obj = ar;
+                    message.arg2 = 1;
+                    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));
+
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Message message = (Message) invocation.getArguments()[1];
+                message.sendToTarget();
+                return null;
+            }
+        }).when(mUiccCard).iccCloseLogicalChannel(anyInt(), any(Message.class));
+
+
+        Message mCardOpenLogicalChannel = mHandler.obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE);
+        setReady(false);
+        mCardOpenLogicalChannel.sendToTarget();
+        waitUntilReady();
+
+        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 testAID_BothARAMandARAD() {
+        final String hexString =
+                "FF4045E243E135C114ABCD92CBB156B280FA4E1429A6ECEEB6E5C1BFE4CA1D636F6D2E676F6F676"
+                        + "C652E616E64726F69642E617070732E6D79617070E30ADB080000000000000001";
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                String aid = (String) invocation.getArguments()[0];
+                Message message = (Message) invocation.getArguments()[2];
+                AsyncResult ar = new AsyncResult(null, new int[]{0}, null);
+                message.obj = ar;
+                if (aid.equals(ARAD)) {
+                    message.arg2 = 0;
+                } else {
+                    message.arg2 = 1;
+                }
+                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));
+
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Message message = (Message) invocation.getArguments()[1];
+                message.sendToTarget();
+                return null;
+            }
+        }).when(mUiccCard).iccCloseLogicalChannel(anyInt(), any(Message.class));
+
+
+        Message mCardOpenLogicalChannel = mHandler.obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE);
+        setReady(false);
+        mCardOpenLogicalChannel.sendToTarget();
+        waitUntilReady();
+
+        Signature signature = new Signature("abcd92cbb156b280fa4e1429a6eceeb6e5c1bfe4");
+        assertTrue(mUiccCarrierPrivilegeRules.hasCarrierPrivilegeRules());
+        assertEquals(2, mUiccCarrierPrivilegeRules.getPackageNames().size());
+        assertEquals("com.google.android.apps.myapp",
+                mUiccCarrierPrivilegeRules.getPackageNames().get(0));
+        assertEquals(0, mUiccCarrierPrivilegeRules.getCarrierPrivilegeStatus(signature,
+                mUiccCarrierPrivilegeRules.getPackageNames().get(0)));
+        assertEquals("com.google.android.apps.myapp",
+                mUiccCarrierPrivilegeRules.getPackageNames().get(1));
+        assertEquals(0, mUiccCarrierPrivilegeRules.getCarrierPrivilegeStatus(signature,
+                mUiccCarrierPrivilegeRules.getPackageNames().get(1)));
+    }
+
+    @Test
+    @SmallTest
+    public void testAID_NeitherARAMorARAD() {
+        final String hexString =
+                "FF4045E243E135C114ABCD92CBB156B280FA4E1429A6ECEEB6E5C1BFE4CA1D636F6D2E676F6F676"
+                        + "C652E616E64726F69642E617070732E6D79617070E30ADB080000000000000001";
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                String aid = (String) invocation.getArguments()[0];
+                Message message = (Message) invocation.getArguments()[2];
+                AsyncResult ar = new AsyncResult(null, null, null);
+                if (aid.equals(ARAM)) {
+                    message.arg2 = 1;
+                } else if (aid.equals(ARAD)) {
+                    message.arg2 = 0;
+                } else {
+                    // PKCS15
+                    ar = new AsyncResult(null, null, new Throwable());
+                }
+                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()[1];
+                message.sendToTarget();
+                return null;
+            }
+        }).when(mUiccCard).iccCloseLogicalChannel(anyInt(), any(Message.class));
+
+        Message mCardOpenLogicalChannel = mHandler.obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE);
+        setReady(false);
+        mCardOpenLogicalChannel.sendToTarget();
+        waitUntilReady();
+
+        assertTrue(!mUiccCarrierPrivilegeRules.hasCarrierPrivilegeRules());
+    }
+
+    private static final int P2 = 0x40;
+    private static final int P2_EXTENDED_DATA = 0x60;
+    @Test
+    @SmallTest
+    public void testAID_RetransmitLogicalChannel() {
+        final String hexString1 =
+                "FF4045E243E135C114ABCD92CBB156B280FA4E1429A6ECEEB6E5C1BFE4CA1D636F6D2E676F6F676"
+                        + "C652E616E64726F69642E617070732E6D79617070E30A";
+
+        final String hexString2 =
+                "DB080000000000000001";
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                String aid = (String) invocation.getArguments()[0];
+                Message message = (Message) invocation.getArguments()[2];
+                if (aid.equals(ARAD)) {
+                    AsyncResult ar = new AsyncResult(null, new int[]{0}, null);
+                    message.obj = ar;
+                    message.arg2 = 0;
+                    message.sendToTarget();
+                } else {
+                    AsyncResult ar = new AsyncResult(null, null, null);
+                    message.obj = ar;
+                    message.arg2 = 1;
+                    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(hexString1));
+                AsyncResult ar = new AsyncResult(null, iir, null);
+                message.obj = ar;
+                message.sendToTarget();
+                return null;
+            }
+        }).when(mUiccCard).iccTransmitApduLogicalChannel(anyInt(), anyInt(), anyInt(), anyInt(),
+                eq(P2), anyInt(), anyString(), 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(hexString2));
+                AsyncResult ar = new AsyncResult(null, iir, null);
+                message.obj = ar;
+                message.sendToTarget();
+                return null;
+            }
+        }).when(mUiccCard).iccTransmitApduLogicalChannel(anyInt(), anyInt(), anyInt(), anyInt(),
+                eq(P2_EXTENDED_DATA), anyInt(), anyString(), any(Message.class));
+
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Message message = (Message) invocation.getArguments()[1];
+                message.sendToTarget();
+                return null;
+            }
+        }).when(mUiccCard).iccCloseLogicalChannel(anyInt(), any(Message.class));
+
+
+        Message mCardOpenLogicalChannel = mHandler.obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE);
+        setReady(false);
+        mCardOpenLogicalChannel.sendToTarget();
+        waitUntilReady();
+
+        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)));
+    }
 }