Merge "Change initial IccCardProxy Card State to UNKNOWN"
diff --git a/src/java/com/android/internal/telephony/uicc/IccCardProxy.java b/src/java/com/android/internal/telephony/uicc/IccCardProxy.java
index 2235654..5466402 100644
--- a/src/java/com/android/internal/telephony/uicc/IccCardProxy.java
+++ b/src/java/com/android/internal/telephony/uicc/IccCardProxy.java
@@ -34,6 +34,7 @@
import android.text.TextUtils;
import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.CommandsInterface.RadioState;
import com.android.internal.telephony.IccCard;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.IccCardConstants.State;
@@ -107,7 +108,7 @@
private UiccCardApplication mUiccApplication = null;
private IccRecords mIccRecords = null;
private CdmaSubscriptionSourceManager mCdmaSSM = null;
- private boolean mRadioOn = false;
+ private RadioState mRadioState = RadioState.RADIO_UNAVAILABLE;
private boolean mQuietMode = false; // when set to true IccCardProxy will not broadcast
// ACTION_SIM_STATE_CHANGED intents
private boolean mInitialized = false;
@@ -130,7 +131,6 @@
ci.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_UNAVAILABLE, null);
resetProperties();
- setExternalState(State.NOT_READY, false);
}
public void dispose() {
@@ -211,15 +211,18 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_RADIO_OFF_OR_UNAVAILABLE:
- mRadioOn = false;
- if (CommandsInterface.RadioState.RADIO_UNAVAILABLE == mCi.getRadioState()) {
- setExternalState(State.NOT_READY);
- }
+ mRadioState = mCi.getRadioState();
+ updateExternalState();
break;
case EVENT_RADIO_ON:
- mRadioOn = true;
+ mRadioState = RadioState.RADIO_ON;
if (!mInitialized) {
updateQuietMode();
+ } else {
+ // updateQuietMode() triggers ICC_CHANGED, which eventually
+ // calls updateExternalState; thus, we don't need this in the
+ // above case
+ updateExternalState();
}
break;
case EVENT_ICC_CHANGED:
@@ -326,11 +329,9 @@
private void updateIccAvailability() {
synchronized (mLock) {
UiccCard newCard = mUiccController.getUiccCard(mPhoneId);
- CardState state = CardState.CARDSTATE_ABSENT;
UiccCardApplication newApp = null;
IccRecords newRecords = null;
if (newCard != null) {
- state = newCard.getCardState();
newApp = newCard.getApplication(mCurrentAppType);
if (newApp != null) {
newRecords = newApp.getIccRecords();
@@ -338,7 +339,7 @@
}
if (mIccRecords != newRecords || mUiccApplication != newApp || mUiccCard != newCard) {
- if (DBG) log("Icc changed. Reregestering.");
+ if (DBG) log("Icc changed. Reregistering.");
unregisterUiccCardEvents();
mUiccCard = newCard;
mUiccApplication = newApp;
@@ -368,15 +369,27 @@
// mUiccCard could be null at bootup, before valid card states have
// been received from UiccController.
if (mUiccCard == null) {
- setExternalState(State.NOT_READY);
+ setExternalState(State.UNKNOWN);
return;
}
if (mUiccCard.getCardState() == CardState.CARDSTATE_ABSENT) {
- if (mRadioOn) {
- setExternalState(State.ABSENT);
+ /*
+ * Both IccCardProxy and UiccController are registered for
+ * RadioState changes. When the UiccController receives a radio
+ * state changed to Unknown it will dispose of all of the IccCard
+ * objects, which will then notify the IccCardProxy and the null
+ * object will force the state to unknown. However, because the
+ * IccCardProxy is also registered for RadioState changes, it will
+ * recieve that signal first. By triggering on radio state changes
+ * directly, we reduce the time window during which the modem is
+ * UNAVAILABLE but the IccStatus is reported as something valid.
+ * This is not ideal.
+ */
+ if (mRadioState == RadioState.RADIO_UNAVAILABLE) {
+ setExternalState(State.UNKNOWN);
} else {
- setExternalState(State.NOT_READY);
+ setExternalState(State.ABSENT);
}
return;
}
@@ -396,9 +409,20 @@
return;
}
+ // By process of elimination, the UICC Card State = PRESENT
switch (mUiccApplication.getState()) {
case APPSTATE_UNKNOWN:
- setExternalState(State.UNKNOWN);
+ /*
+ * APPSTATE_UNKNOWN is a catch-all state reported whenever the app
+ * is not explicitly in one of the other states. To differentiate the
+ * case where we know that there is a card present, but the APP is not
+ * ready, we choose NOT_READY here instead of unknown. This is possible
+ * in at least two cases:
+ * 1) A transient during the process of the SIM bringup
+ * 2) There is no valid App on the SIM to load, which can be the case with an
+ * eSIM/soft SIM.
+ */
+ setExternalState(State.NOT_READY);
break;
case APPSTATE_DETECTED:
HandleDetectedState();
@@ -516,15 +540,16 @@
}
if (!override && newState == mExternalState) {
- loge("setExternalState: !override and newstate unchanged from " + newState);
+ log("setExternalState: !override and newstate unchanged from " + newState);
return;
}
mExternalState = newState;
- loge("setExternalState: set mPhoneId=" + mPhoneId + " mExternalState=" + mExternalState);
+ log("setExternalState: set mPhoneId=" + mPhoneId + " mExternalState=" + mExternalState);
mTelephonyManager.setSimStateForPhone(mPhoneId, getState().toString());
// For locked states, we should be sending internal broadcast.
- if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(getIccStateIntentString(mExternalState))) {
+ if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(
+ getIccStateIntentString(mExternalState))) {
broadcastInternalIccStateChangedIntent(getIccStateIntentString(mExternalState),
getIccStateReason(mExternalState));
} else {
@@ -937,7 +962,7 @@
pw.println(" mUiccApplication=" + mUiccApplication);
pw.println(" mIccRecords=" + mIccRecords);
pw.println(" mCdmaSSM=" + mCdmaSSM);
- pw.println(" mRadioOn=" + mRadioOn);
+ pw.println(" mRadioState=" + mRadioState);
pw.println(" mQuietMode=" + mQuietMode);
pw.println(" mInitialized=" + mInitialized);
pw.println(" mExternalState=" + mExternalState);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccCardProxyTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccCardProxyTest.java
new file mode 100644
index 0000000..b211218
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccCardProxyTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.uicc;
+
+import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.IccCardConstants.State;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
+import com.android.internal.telephony.uicc.IccCardStatus.CardState;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+public class IccCardProxyTest extends TelephonyTest {
+ private IccCardProxy mIccCardProxyUT;
+ // private UiccCard mUiccCard;
+ private IccCardProxyHandlerThread mIccCardProxyHandlerThread;
+ private static final int PHONE_ID = 0;
+ private static final int PHONE_COUNT = 1;
+
+ private static final int SCARY_SLEEP_MS = 200;
+ // Must match IccCardProxy.EVENT_ICC_CHANGED
+ private static final int EVENT_ICC_CHANGED = 3;
+
+ @Mock private Handler mMockedHandler;
+ @Mock private IccCardStatus mIccCardStatus;
+ @Mock private UiccCard mUiccCard;
+ @Mock private UiccCardApplication mUiccCardApplication;
+
+ private class IccCardProxyHandlerThread extends HandlerThread {
+
+ private IccCardProxyHandlerThread(String name) {
+ super(name);
+ }
+
+ @Override
+ public void onLooperPrepared() {
+ /* create a new UICC Controller associated with the simulated Commands */
+ mIccCardProxyUT = new IccCardProxy(mContext, mSimulatedCommands, PHONE_ID);
+ setReady(true);
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp(this.getClass().getSimpleName());
+ doReturn(PHONE_COUNT).when(mTelephonyManager).getPhoneCount();
+ doReturn(PHONE_COUNT).when(mTelephonyManager).getSimCount();
+ mSimulatedCommands.setIccCardStatus(mIccCardStatus);
+ mIccCardProxyHandlerThread = new IccCardProxyHandlerThread(TAG);
+ mIccCardProxyHandlerThread.start();
+ waitUntilReady();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mIccCardProxyHandlerThread.quitSafely();
+ super.tearDown();
+ }
+
+ @Test
+ @SmallTest
+ public void testInitialCardState() {
+ assertEquals(mIccCardProxyUT.getState(), State.UNKNOWN);
+ }
+
+ @Test
+ @SmallTest
+ public void testPowerOn() {
+ mSimulatedCommands.setRadioPower(true, null);
+ mSimulatedCommands.notifyRadioOn();
+ when(mUiccController.getUiccCard(anyInt())).thenReturn(mUiccCard);
+ mIccCardProxyUT.sendMessage(mIccCardProxyUT.obtainMessage(EVENT_ICC_CHANGED));
+ waitForMs(SCARY_SLEEP_MS);
+ assertEquals(CommandsInterface.RadioState.RADIO_ON, mSimulatedCommands.getRadioState());
+ assertEquals(mIccCardProxyUT.getState(), State.NOT_READY);
+ logd("IccCardProxy state = " + mIccCardProxyUT.getState());
+ }
+
+ @Test
+ @SmallTest
+ public void testCardLoaded() {
+ testPowerOn();
+ when(mUiccCard.getCardState()).thenReturn(CardState.CARDSTATE_PRESENT);
+ mIccCardProxyUT.sendMessage(mIccCardProxyUT.obtainMessage(EVENT_ICC_CHANGED));
+ waitForMs(SCARY_SLEEP_MS);
+ assertEquals(mIccCardProxyUT.getState(), State.NOT_READY);
+ }
+
+ @Test
+ @SmallTest
+ public void testAppNotLoaded() {
+ testPowerOn();
+ when(mUiccCard.getCardState()).thenReturn(CardState.CARDSTATE_PRESENT);
+ mIccCardProxyUT.sendMessage(mIccCardProxyUT.obtainMessage(EVENT_ICC_CHANGED));
+ when(mUiccCardApplication.getState()).thenReturn(AppState.APPSTATE_UNKNOWN);
+ when(mUiccCard.getApplication(anyInt())).thenReturn(mUiccCardApplication);
+
+ waitForMs(SCARY_SLEEP_MS);
+ assertEquals(mIccCardProxyUT.getState(), State.NOT_READY);
+ }
+
+ @Test
+ @SmallTest
+ public void testAppReady() {
+ testPowerOn();
+ when(mUiccCard.getCardState()).thenReturn(CardState.CARDSTATE_PRESENT);
+ mIccCardProxyUT.sendMessage(mIccCardProxyUT.obtainMessage(EVENT_ICC_CHANGED));
+ when(mUiccCardApplication.getState()).thenReturn(AppState.APPSTATE_READY);
+ when(mUiccCard.getApplication(anyInt())).thenReturn(mUiccCardApplication);
+
+ waitForMs(SCARY_SLEEP_MS);
+ assertEquals(mIccCardProxyUT.getState(), State.READY);
+ }
+}