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