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);
+ }
+}