Merge "Send shutdown command to modem"
diff --git a/proto/carrierId.proto b/proto/carrierId.proto
new file mode 100644
index 0000000..47b5571
--- /dev/null
+++ b/proto/carrierId.proto
@@ -0,0 +1,77 @@
+//
+// 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.
+//
+
+syntax = "proto2";
+
+package carrierIdentification;
+
+option java_package = "com.android.internal.telephony";
+option java_outer_classname = "CarrierIdProto";
+
+// A complete list of carriers
+message CarrierList {
+  // A collection of carriers. one entry for one carrier.
+  repeated CarrierId carrier_id = 1;
+};
+
+// CarrierId is the unique representation of a carrier in CID table.
+message CarrierId {
+  // [Required] A unique canonical number designated to a carrier.
+  required int32 canonical_id = 1;
+
+  // [Optional] A user-friendly carrier name (not localized).
+  optional string carrier_name = 2;
+
+  // [Required] Carrier attributes to match a carrier. At least one value is required.
+  repeated CarrierAttribute carrier_attribute = 3;
+};
+
+// Attributes used to match a carrier.
+// For each field within this message:
+//   - if not set, the attribute is ignored;
+//   - if set, the device must have one of the specified values to match.
+// Match is based on AND between any field that is set and OR for values within a repeated field.
+message CarrierAttribute {
+  // [Required] The MCC and MNC that map to this carrier. At least one value is required.
+  repeated string mccmnc_tuple = 1;
+
+  // [Optional] Prefix of IMSI (International Mobile Subscriber Identity) in
+  // decimal format. Some digits can be replaced with "x" symbols matching any digit.
+  // Sample values: 20404794, 21670xx2xxx.
+  repeated string imsi_prefix_xpattern = 2;
+
+  // [Optional] The Service Provider Name. Read from subscription EF_SPN.
+  // Sample values: C Spire, LeclercMobile
+  repeated string spn = 3;
+
+  // [Optional] PLMN network name. Read from subscription EF_PNN.
+  // Sample values:
+  repeated string plmn = 4;
+
+  // [Optional] Group Identifier Level1 for a GSM phone. Read from subscription EF_GID1.
+  // Sample values: 6D, BAE0000000000000
+  repeated string gid1 = 5;
+
+  // [Optional] Group Identifier Level2 for a GSM phone. Read from subscription EF_GID2.
+  // Sample values: 6D, BAE0000000000000
+  repeated string gid2 = 6;
+
+  // [Optional] The Access Point Name, corresponding to "apn" field returned by
+  // "content://telephony/carriers/preferapn" on device.
+  // Sample values: fast.t-mobile.com, internet
+  repeated string preferred_apn = 7;
+};
+
diff --git a/src/java/com/android/internal/telephony/ExponentialBackoff.java b/src/java/com/android/internal/telephony/ExponentialBackoff.java
new file mode 100644
index 0000000..80958c0
--- /dev/null
+++ b/src/java/com/android/internal/telephony/ExponentialBackoff.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.Looper;
+
+/** The implementation of exponential backoff with jitter applied. */
+public class ExponentialBackoff {
+    private int mRetryCounter;
+    private long mStartDelayMs;
+    private long mMaximumDelayMs;
+    private long mCurrentDelayMs;
+    private int mMultiplier;
+    private Runnable mRunnable;
+    private Handler mHandler;
+
+    public ExponentialBackoff(
+            long initialDelayMs,
+            long maximumDelayMs,
+            int multiplier,
+            @NonNull Looper looper,
+            @NonNull Runnable runnable) {
+        this(initialDelayMs, maximumDelayMs, multiplier, new Handler(looper), runnable);
+    }
+
+    public ExponentialBackoff(
+            long initialDelayMs,
+            long maximumDelayMs,
+            int multiplier,
+            @NonNull Handler handler,
+            @NonNull Runnable runnable) {
+        mRetryCounter = 0;
+        mStartDelayMs = initialDelayMs;
+        mMaximumDelayMs = maximumDelayMs;
+        mMultiplier = multiplier;
+        mHandler = handler;
+        mRunnable = runnable;
+    }
+
+    /** Starts the backoff, the runnable will be executed after {@link #mStartDelayMs}. */
+    public void start() {
+        mRetryCounter = 0;
+        mCurrentDelayMs = mStartDelayMs;
+        mHandler.removeCallbacks(mRunnable);
+        mHandler.postDelayed(mRunnable, mCurrentDelayMs);
+    }
+
+    /** Stops the backoff, all pending messages will be removed from the message queue. */
+    public void stop() {
+        mRetryCounter = 0;
+        mHandler.removeCallbacks(mRunnable);
+    }
+
+    /** Should call when the retry action has failed and we want to retry after a longer delay. */
+    public void notifyFailed() {
+        mRetryCounter++;
+        long temp = Math.min(
+                mMaximumDelayMs, (long) (mStartDelayMs * Math.pow(mMultiplier, mRetryCounter)));
+        mCurrentDelayMs = (long) (((1 + Math.random()) / 2) * temp);
+        mHandler.removeCallbacks(mRunnable);
+        mHandler.postDelayed(mRunnable, mCurrentDelayMs);
+    }
+
+    /** Returns the delay for the most recently posted message. */
+    public long getCurrentDelay() {
+        return mCurrentDelayMs;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceController.java b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
index 5257dad..6fcefbd 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceController.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
@@ -33,6 +33,7 @@
 import com.android.ims.internal.IImsServiceController;
 import com.android.ims.internal.IImsServiceFeatureListener;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.ExponentialBackoff;
 
 import java.util.HashSet;
 import java.util.Iterator;
@@ -76,6 +77,7 @@
 
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
+            mBackoff.stop();
             synchronized (mLock) {
                 mIsBound = true;
                 mIsBinding = false;
@@ -142,21 +144,28 @@
     @VisibleForTesting
     public interface RebindRetry {
         /**
-         * Return a long in ms indiciating how long the ImsServiceController should wait before
-         * rebinding.
+         * Returns a long in ms indicating how long the ImsServiceController should wait before
+         * rebinding for the first time.
          */
-        long getRetryTimeout();
+        long getStartDelay();
+
+        /**
+         * Returns a long in ms indicating the maximum time the ImsServiceController should wait
+         * before rebinding.
+         */
+        long getMaximumDelay();
     }
 
     private static final String LOG_TAG = "ImsServiceController";
-    private static final int REBIND_RETRY_TIME = 5000;
+    private static final int REBIND_START_DELAY_MS = 2 * 1000; // 2 seconds
+    private static final int REBIND_MAXIMUM_DELAY_MS = 60 * 1000; // 1 minute
     private final Context mContext;
     private final ComponentName mComponentName;
     private final Object mLock = new Object();
     private final HandlerThread mHandlerThread = new HandlerThread("ImsServiceControllerHandler");
     private final IPackageManager mPackageManager;
     private ImsServiceControllerCallbacks mCallbacks;
-    private Handler mHandler;
+    private ExponentialBackoff mBackoff;
 
     private boolean mIsBound = false;
     private boolean mIsBinding = false;
@@ -213,17 +222,17 @@
         }
     };
 
-    private RebindRetry mRebindRetry = () -> REBIND_RETRY_TIME;
+    private RebindRetry mRebindRetry = new RebindRetry() {
+        @Override
+        public long getStartDelay() {
+            return REBIND_START_DELAY_MS;
+        }
 
-    @VisibleForTesting
-    public void setRebindRetryTime(RebindRetry retry) {
-        mRebindRetry = retry;
-    }
-
-    @VisibleForTesting
-    public Handler getHandler() {
-        return mHandler;
-    }
+        @Override
+        public long getMaximumDelay() {
+            return REBIND_MAXIMUM_DELAY_MS;
+        }
+    };
 
     public ImsServiceController(Context context, ComponentName componentName,
             ImsServiceControllerCallbacks callbacks) {
@@ -231,7 +240,12 @@
         mComponentName = componentName;
         mCallbacks = callbacks;
         mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
+        mBackoff = new ExponentialBackoff(
+                mRebindRetry.getStartDelay(),
+                mRebindRetry.getMaximumDelay(),
+                2, /* multiplier */
+                mHandlerThread.getLooper(),
+                mRestartImsServiceRunnable);
         mPackageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
     }
 
@@ -239,11 +253,16 @@
     // Creating a new HandlerThread and background handler for each test causes a segfault, so for
     // testing, use a handler supplied by the testing system.
     public ImsServiceController(Context context, ComponentName componentName,
-            ImsServiceControllerCallbacks callbacks, Handler testHandler) {
+            ImsServiceControllerCallbacks callbacks, Handler handler, RebindRetry rebindRetry) {
         mContext = context;
         mComponentName = componentName;
         mCallbacks = callbacks;
-        mHandler = testHandler;
+        mBackoff = new ExponentialBackoff(
+                rebindRetry.getStartDelay(),
+                rebindRetry.getMaximumDelay(),
+                2, /* multiplier */
+                handler,
+                mRestartImsServiceRunnable);
         mPackageManager = null;
     }
 
@@ -258,8 +277,6 @@
      */
     public boolean bind(HashSet<Pair<Integer, Integer>> imsFeatureSet) {
         synchronized (mLock) {
-            // Remove pending rebind retry
-            mHandler.removeCallbacks(mRestartImsServiceRunnable);
             if (!mIsBound && !mIsBinding) {
                 mIsBinding = true;
                 mImsFeatures = imsFeatureSet;
@@ -273,8 +290,10 @@
                     return mContext.bindService(imsServiceIntent, mImsServiceConnection,
                             serviceFlags);
                 } catch (Exception e) {
+                    mBackoff.notifyFailed();
                     Log.e(LOG_TAG, "Error binding (" + mComponentName + ") with exception: "
-                            + e.getMessage());
+                            + e.getMessage() + ", rebinding in " + mBackoff.getCurrentDelay()
+                            + " ms");
                     return false;
                 }
             } else {
@@ -289,8 +308,7 @@
      */
     public void unbind() throws RemoteException {
         synchronized (mLock) {
-            // Remove pending rebind retry
-            mHandler.removeCallbacks(mRestartImsServiceRunnable);
+            mBackoff.stop();
             if (mImsServiceConnection == null || mImsDeathRecipient == null) {
                 return;
             }
@@ -343,6 +361,11 @@
         return mImsServiceControllerBinder;
     }
 
+    @VisibleForTesting
+    public long getRebindDelay() {
+        return mBackoff.getCurrentDelay();
+    }
+
     public ComponentName getComponentName() {
         return mComponentName;
     }
@@ -364,9 +387,7 @@
 
     // Only add a new rebind if there are no pending rebinds waiting.
     private void startDelayedRebindToService() {
-        if (!mHandler.hasCallbacks(mRestartImsServiceRunnable)) {
-            mHandler.postDelayed(mRestartImsServiceRunnable, mRebindRetry.getRetryTimeout());
-        }
+        mBackoff.start();
     }
 
     // Grant runtime permissions to ImsService. PackageManager ensures that the ImsService is
diff --git a/src/java/com/android/internal/telephony/uicc/UiccProfile.java b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
index 7433a43..3e7d2d7 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccProfile.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
@@ -105,7 +105,10 @@
         update(c, ci, ics);
     }
 
-    void dispose() {
+    /**
+     * Dispose the UiccProfile.
+     */
+    public void dispose() {
         synchronized (mLock) {
             if (DBG) log("Disposing profile");
             if (mCatService != null) mCatService.dispose();
@@ -120,7 +123,10 @@
         }
     }
 
-    void update(Context c, CommandsInterface ci, IccCardStatus ics) {
+    /**
+     * Update the UiccProfile.
+     */
+    public void update(Context c, CommandsInterface ci, IccCardStatus ics) {
         synchronized (mLock) {
             mUniversalPinState = ics.mUniversalPinState;
             mGsmUmtsSubscriptionAppIndex = ics.mGsmUmtsSubscriptionAppIndex;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ExponentialBackoffTest.java b/tests/telephonytests/src/com/android/internal/telephony/ExponentialBackoffTest.java
new file mode 100644
index 0000000..87347e5
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/ExponentialBackoffTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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 org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.telephony.ims.ImsTestBase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ExponentialBackoffTest extends ImsTestBase {
+
+    private static final int START_DELAY_MS = 10;
+    private static final int MAXIMUM_DELAY_MS = 1000;
+    private static final int MULTIPLIER = 2;
+
+    private ExponentialBackoff mBackoffUnderTest;
+    private Handler mHandler = spy(new Handler(Looper.getMainLooper()));
+    private Runnable mRunnable = spy(new MyRunnable());
+
+    public class MyRunnable implements Runnable {
+        @Override
+        public void run() {
+            // do nothing
+        }
+    }
+
+    @Before
+    public void setUp() {
+        mBackoffUnderTest = new ExponentialBackoff(
+                START_DELAY_MS, MAXIMUM_DELAY_MS, MULTIPLIER, mHandler, mRunnable);
+    }
+
+    @After
+    public void tearDown() {
+        mBackoffUnderTest.stop();
+    }
+
+    @Test
+    public void testStartBackoff() {
+        mBackoffUnderTest.start();
+        long delay = mBackoffUnderTest.getCurrentDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
+
+        // The runnable is executed after timeout event occurred.
+        verify(mRunnable).run();
+    }
+
+    @Test
+    public void testStopBackoff() {
+        mBackoffUnderTest.start();
+        reset(mHandler);
+
+        mBackoffUnderTest.stop();
+        verify(mHandler).removeCallbacks(mRunnable);
+    }
+
+    @Test
+    public void testDelayIncreasedExponentially() {
+        mBackoffUnderTest.start();
+        // guarantee START_DELAY_MS * 2 ^ i <= MAXIMUM_DELAY_MS
+        for (int i = 1; i < 5; i++) {
+            mBackoffUnderTest.notifyFailed();
+            long delay = mBackoffUnderTest.getCurrentDelay();
+            long minDelay = (long) (START_DELAY_MS * Math.pow(MULTIPLIER, i - 1));
+            long maxDelay = (long) (START_DELAY_MS * Math.pow(MULTIPLIER, i));
+            assertTrue("delay = " + delay + " minDelay = " + minDelay, delay >= minDelay);
+            assertTrue("delay = " + delay + " maxDelay = " + maxDelay, delay <= maxDelay);
+        }
+    }
+
+    @Test
+    public void testDelayShouldNotExceededTheMaximumLimit() {
+        mBackoffUnderTest.start();
+        // guarantee START_DELAY_MS * 2 ^ 30 > MAXIMUM_DELAY_MS
+        for (int i = 1; i < 30; i++) {
+            mBackoffUnderTest.notifyFailed();
+        }
+        long delay = mBackoffUnderTest.getCurrentDelay();
+        assertTrue(
+                "delay = " + delay + " maximumDelay = " + MAXIMUM_DELAY_MS,
+                delay <= MAXIMUM_DELAY_MS);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
index 7b831a0..f29c6d0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
@@ -41,6 +41,7 @@
 import android.util.Pair;
 
 import com.android.ims.internal.IImsServiceFeatureListener;
+import com.android.internal.telephony.ims.ImsServiceController.RebindRetry;
 
 import org.junit.After;
 import org.junit.Before;
@@ -60,7 +61,17 @@
 @Ignore
 public class ImsServiceControllerTest extends ImsTestBase {
 
-    private static final int RETRY_TIMEOUT = 50; // ms
+    private static final RebindRetry REBIND_RETRY = new RebindRetry() {
+        @Override
+        public long getStartDelay() {
+            return 50;
+        }
+
+        @Override
+        public long getMaximumDelay() {
+            return 1000;
+        }
+    };
 
     @Spy TestImsServiceControllerAdapter mMockServiceControllerBinder;
     @Mock IBinder mMockBinder;
@@ -69,15 +80,15 @@
     @Mock Context mMockContext;
     private final ComponentName mTestComponentName = new ComponentName("TestPkg",
             "ImsServiceControllerTest");
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
     private ImsServiceController mTestImsServiceController;
-    private final Handler mTestHandler = new Handler(Looper.getMainLooper());
 
     @Before
     @Override
     public void setUp() throws Exception {
         super.setUp();
         mTestImsServiceController = new ImsServiceController(mMockContext, mTestComponentName,
-                mMockCallbacks, mTestHandler);
+                mMockCallbacks, mHandler, REBIND_RETRY);
         mTestImsServiceController.addImsServiceFeatureListener(mMockProxyCallbacks);
         when(mMockContext.bindService(any(), any(), anyInt())).thenReturn(true);
     }
@@ -86,7 +97,6 @@
     @After
     @Override
     public void tearDown() throws Exception {
-        mTestHandler.removeCallbacksAndMessages(null);
         mTestImsServiceController = null;
         super.tearDown();
     }
@@ -354,13 +364,12 @@
         testFeatures.add(new Pair<>(1, 1));
         testFeatures.add(new Pair<>(1, 2));
         bindAndConnectService(testFeatures);
-        mTestImsServiceController.setRebindRetryTime(() -> RETRY_TIMEOUT);
 
         getDeathRecipient().binderDied();
 
-        waitForHandlerActionDelayed(mTestImsServiceController.getHandler(), RETRY_TIMEOUT,
-                2 * RETRY_TIMEOUT);
-        // The service should autobind after RETRY_TIMEOUT occurs
+        long delay = mTestImsServiceController.getRebindDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
+        // The service should autobind after rebind event occurs
         verify(mMockContext, times(2)).bindService(any(), any(), anyInt());
     }
 
@@ -374,7 +383,6 @@
         testFeatures.add(new Pair<>(1, 1));
         testFeatures.add(new Pair<>(1, 2));
         bindAndConnectService(testFeatures);
-        mTestImsServiceController.setRebindRetryTime(() -> RETRY_TIMEOUT);
 
         getDeathRecipient().binderDied();
 
@@ -392,13 +400,13 @@
         testFeatures.add(new Pair<>(1, 1));
         testFeatures.add(new Pair<>(1, 2));
         bindAndConnectService(testFeatures);
-        mTestImsServiceController.setRebindRetryTime(() -> RETRY_TIMEOUT);
 
         getDeathRecipient().binderDied();
         mTestImsServiceController.unbind();
 
-        waitForHandlerActionDelayed(mTestImsServiceController.getHandler(), RETRY_TIMEOUT,
-                2 * RETRY_TIMEOUT);
+        long delay = mTestImsServiceController.getRebindDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
+
         // Unbind should stop the autobind from occurring.
         verify(mMockContext, times(1)).bindService(any(), any(), anyInt());
     }
@@ -414,12 +422,11 @@
         testFeatures.add(new Pair<>(1, 1));
         testFeatures.add(new Pair<>(1, 2));
         bindAndConnectService(testFeatures);
-        mTestImsServiceController.setRebindRetryTime(() -> RETRY_TIMEOUT);
         getDeathRecipient().binderDied();
         mTestImsServiceController.bind(testFeatures);
 
-        waitForHandlerActionDelayed(mTestImsServiceController.getHandler(), RETRY_TIMEOUT,
-                2 * RETRY_TIMEOUT);
+        long delay = mTestImsServiceController.getRebindDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
         // Should only see two binds, not three from the auto rebind that occurs.
         verify(mMockContext, times(2)).bindService(any(), any(), anyInt());
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
new file mode 100644
index 0000000..51acbab
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 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.uicc;
+
+import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.isA;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.cat.CatService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+public class UiccProfileTest extends TelephonyTest {
+    private UiccProfile mUiccProfile;
+
+    public UiccProfileTest() {
+        super();
+    }
+
+    private IccIoResult mIccIoResult;
+
+    private UiccProfileHandlerThread mTestHandlerThread;
+    private Handler mHandler;
+    private static final int UICCPROFILE_UPDATE_PROFILE_EVENT = 1;
+    private static final int UICCPROFILE_UPDATE_APPLICATION_EVENT = 2;
+    private static final int UICCPROFILE_CARRIER_PRIVILEDGE_LOADED_EVENT = 3;
+
+    @Mock
+    private CatService mCAT;
+    @Mock
+    private IccCardStatus mIccCardStatus;
+    @Mock
+    private Handler mMockedHandler;
+    @Mock
+    private UiccCard mUiccCard;
+
+
+    private class UiccProfileHandlerThread extends HandlerThread {
+
+        private UiccProfileHandlerThread(String name) {
+            super(name);
+        }
+
+        @Override
+        public void onLooperPrepared() {
+            mUiccProfile = new UiccProfile(mContextFixture.getTestDouble(),
+                                           mSimulatedCommands, mIccCardStatus, 0 /* phoneId */,
+                                           mUiccCard);
+            /* create a custom handler for the Handler Thread */
+            mHandler = new Handler(mTestHandlerThread.getLooper()) {
+                @Override
+                public void handleMessage(Message msg) {
+                    switch (msg.what) {
+                        case UICCPROFILE_UPDATE_PROFILE_EVENT:
+                            /* Upon handling this event, new CarrierPrivilegeRule
+                            will be created with the looper of HandlerThread */
+                            logd("Update UICC Profile");
+                            mUiccProfile.update(mContextFixture.getTestDouble(),
+                                    mSimulatedCommands, mIccCardStatus);
+                            setReady(true);
+                            break;
+                        case UICCPROFILE_UPDATE_APPLICATION_EVENT:
+                            logd("Update UICC Profile Applications");
+                            mUiccProfile.update(mContextFixture.getTestDouble(),
+                                    mSimulatedCommands, mIccCardStatus);
+                            setReady(true);
+                            break;
+                        default:
+                            logd("Unknown Event " + msg.what);
+                    }
+                }
+            };
+            /* wait for the carrier privilege rules to be loaded */
+            waitForMs(50);
+            setReady(true);
+            logd("Create UiccProfile");
+        }
+    }
+
+    private IccCardApplicationStatus composeUiccApplicationStatus(
+            IccCardApplicationStatus.AppType appType,
+            IccCardApplicationStatus.AppState appState, String aid) {
+        IccCardApplicationStatus mIccCardAppStatus = new IccCardApplicationStatus();
+        mIccCardAppStatus.aid = aid;
+        mIccCardAppStatus.app_type = appType;
+        mIccCardAppStatus.app_state = appState;
+        mIccCardAppStatus.pin1 = mIccCardAppStatus.pin2 =
+                IccCardStatus.PinState.PINSTATE_ENABLED_VERIFIED;
+        return mIccCardAppStatus;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        /* initially there are no application available */
+        mIccCardStatus.mApplications = new IccCardApplicationStatus[]{};
+        mIccCardStatus.mCdmaSubscriptionAppIndex =
+                mIccCardStatus.mImsSubscriptionAppIndex =
+                        mIccCardStatus.mGsmUmtsSubscriptionAppIndex = -1;
+        testHelper("FF40");
+        /* starting the Handler Thread */
+        mTestHandlerThread = new UiccProfileHandlerThread(TAG);
+        mTestHandlerThread.start();
+
+        waitUntilReady();
+        replaceInstance(UiccProfile.class, "mCatService", mUiccProfile, mCAT);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mTestHandlerThread.quit();
+        super.tearDown();
+    }
+
+    private void testHelper(String hexString) {
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                logd("Open");
+                Message message = (Message) invocation.getArguments()[2];
+                AsyncResult ar = new AsyncResult(null, new int[]{0}, null);
+                message.obj = ar;
+                message.sendToTarget();
+                return null;
+            }
+        }).when(mUiccCard).iccOpenLogicalChannel(anyString(), anyInt(), any(Message.class));
+
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                logd("Transmit");
+                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 {
+                logd("Close");
+                Message message = (Message) invocation.getArguments()[1];
+                message.sendToTarget();
+                return null;
+            }
+        }).when(mUiccCard).iccCloseLogicalChannel(anyInt(), any(Message.class));
+    }
+
+    @Test
+    @SmallTest
+    public void tesUiccProfileInfoSanity() {
+        assertEquals(0, mUiccProfile.getNumApplications());
+        assertNull(mUiccProfile.getUniversalPinState());
+        assertNull(mUiccProfile.getOperatorBrandOverride());
+        for (IccCardApplicationStatus.AppType mAppType :
+                IccCardApplicationStatus.AppType.values()) {
+            assertFalse(mUiccProfile.isApplicationOnIcc(mAppType));
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testUpdateUiccProfileApplication() {
+        /* update app status and index */
+        IccCardApplicationStatus cdmaApp = composeUiccApplicationStatus(
+                IccCardApplicationStatus.AppType.APPTYPE_CSIM,
+                IccCardApplicationStatus.AppState.APPSTATE_UNKNOWN, "0xA0");
+        IccCardApplicationStatus imsApp = composeUiccApplicationStatus(
+                IccCardApplicationStatus.AppType.APPTYPE_ISIM,
+                IccCardApplicationStatus.AppState.APPSTATE_UNKNOWN, "0xA1");
+        IccCardApplicationStatus umtsApp = composeUiccApplicationStatus(
+                IccCardApplicationStatus.AppType.APPTYPE_USIM,
+                IccCardApplicationStatus.AppState.APPSTATE_UNKNOWN, "0xA2");
+        mIccCardStatus.mApplications = new IccCardApplicationStatus[]{cdmaApp, imsApp, umtsApp};
+        mIccCardStatus.mCdmaSubscriptionAppIndex = 0;
+        mIccCardStatus.mImsSubscriptionAppIndex = 1;
+        mIccCardStatus.mGsmUmtsSubscriptionAppIndex = 2;
+        Message mProfileUpdate = mHandler.obtainMessage(UICCPROFILE_UPDATE_APPLICATION_EVENT);
+        setReady(false);
+        mProfileUpdate.sendToTarget();
+
+        waitUntilReady();
+
+        assertEquals(3, mUiccProfile.getNumApplications());
+        assertTrue(mUiccProfile.isApplicationOnIcc(IccCardApplicationStatus.AppType.APPTYPE_CSIM));
+        assertTrue(mUiccProfile.isApplicationOnIcc(IccCardApplicationStatus.AppType.APPTYPE_ISIM));
+        assertTrue(mUiccProfile.isApplicationOnIcc(IccCardApplicationStatus.AppType.APPTYPE_USIM));
+    }
+
+    @Test
+    @SmallTest
+    public void testUpdateUiccProfile() {
+        Message mCardUpdate = mHandler.obtainMessage(UICCPROFILE_UPDATE_PROFILE_EVENT);
+        setReady(false);
+        mCardUpdate.sendToTarget();
+        /* try to create a new CarrierPrivilege, loading state -> loaded state */
+        /* wait till the async result and message delay */
+        waitUntilReady();
+        /* wait for the carrier privilege rules to be loaded */
+        waitForMs(50);
+
+        assertTrue(mUiccProfile.areCarrierPriviligeRulesLoaded());
+        verify(mUiccCard, times(2)).iccOpenLogicalChannel(isA(String.class),
+                anyInt(), isA(Message.class));
+        verify(mUiccCard, times(2)).iccTransmitApduLogicalChannel(
+                anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyString(),
+                isA(Message.class)
+        );
+    }
+
+    @Test
+    @SmallTest
+    public void testUpdateUiccProfilePinState() {
+        mIccCardStatus.mUniversalPinState = IccCardStatus.PinState.PINSTATE_ENABLED_VERIFIED;
+        mUiccProfile.update(mContextFixture.getTestDouble(), mSimulatedCommands, mIccCardStatus);
+        assertEquals(IccCardStatus.PinState.PINSTATE_ENABLED_VERIFIED,
+                mUiccProfile.getUniversalPinState());
+    }
+
+    @Test
+    @SmallTest
+    public void testCarrierPriviledgeLoadedListener() {
+        mUiccProfile.registerForCarrierPrivilegeRulesLoaded(mMockedHandler,
+                UICCPROFILE_CARRIER_PRIVILEDGE_LOADED_EVENT, null);
+        ArgumentCaptor<Message> mCaptorMessage = ArgumentCaptor.forClass(Message.class);
+        ArgumentCaptor<Long> mCaptorLong = ArgumentCaptor.forClass(Long.class);
+        testUpdateUiccProfile();
+        verify(mMockedHandler, times(1)).sendMessageDelayed(mCaptorMessage.capture(),
+                mCaptorLong.capture());
+        assertEquals(UICCPROFILE_CARRIER_PRIVILEDGE_LOADED_EVENT, mCaptorMessage.getValue().what);
+    }
+}