Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 026ad2e3 authored by Hunsuk Choi's avatar Hunsuk Choi
Browse files

Switch phones if PUK locked in DSDS configuration

There can be cases that the Phone selected for emergency call
is not served in the modem since the modem stack corresponding
to the selected Phone has been stopped due to the SIM PIN/PUK
locked state while there is another stack with an active SIM.

In that case, setting emergency mode in the selected modem can
cause abnormal behavior in the modem side.

Determine whether the modem is active or not before setting
emergency mode, and switch Phones if it's not active.

Bug: 343862428
Test: atest EmergencyStateTrackerTest
Manual test:
Step 1. Power ON the device with two SIMs in 5G HOME only and
        3rd-party LTE coverage with 5G capapable SIM1 and not 5G capable SIM2.
Step 2. Set the SIM1 preferred for calls and data.
Step 3. Go to SIM lock settings and enter incorrect SIM PIN for SIM1, 3 times.
Step 4. Confirm that PUK lock screen is displayed.
Step 5. Initiate an emergency call and observe the behavior.

Change-Id: I8a7e62900c30c35061f1fc63c952b5f7032f1788
parent dd991040
Loading
Loading
Loading
Loading
+64 −0
Original line number Diff line number Diff line
@@ -564,6 +564,11 @@ public class EmergencyStateTracker {
        Rlog.i(TAG, "startEmergencyCall: phoneId=" + phone.getPhoneId()
                + ", callId=" + c.getTelecomCallId());

        if (needToSwitchPhone(phone)) {
            Rlog.e(TAG, "startEmergencyCall failed. need to switch stacks.");
            return CompletableFuture.completedFuture(DisconnectCause.EMERGENCY_PERM_FAILURE);
        }

        if (mPhone != null) {
            // Create new future to return as to not interfere with any uncompleted futures.
            // Case1) When 2nd emergency call is initiated during an active call on the same phone.
@@ -2105,4 +2110,63 @@ public class EmergencyStateTracker {
            endNormalRoutingEmergencyCall(c);
        }
    }

    /**
     * Determines whether switching stacks is needed or not.
     *
     * @param phone the {@code Phone} on which to process the emergency call.
     * @return true if switching stacks is needed.
     */
    @VisibleForTesting
    public boolean needToSwitchPhone(Phone phone) {
        int subId = phone.getSubId();
        int phoneId = phone.getPhoneId();

        if (isSimReady(phoneId, subId)) return false;

        boolean switchPhone = false;
        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
            Rlog.i(TAG, "needToSwitchPhone SIM absent");
            if (phoneId != 0 || isThereOtherPhone(phoneId, true)) {
                // Prefer default Phone or other Phone with a SIM regardless of lock state.
                switchPhone = true;
            }
        } else {
            Rlog.i(TAG, "needToSwitchPhone SIM not ready");
            if ((phoneId == 0 && isThereOtherPhone(phoneId, false))
                    || (phoneId != 0 && isThereOtherPhone(phoneId, true))) {
                // If there is another one with a SIM ready, switch Phones.
                // Otherwise, prefer default Phone if both SIMs are locked.
                switchPhone = true;
            }
        }
        Rlog.i(TAG, "needToSwitchPhone " + switchPhone);
        return switchPhone;
    }

    private boolean isThereOtherPhone(int skipPhoneId, boolean ignoreLockState) {
        for (Phone phone : mPhoneFactoryProxy.getPhones()) {
            int phoneId = phone.getPhoneId();
            if (phoneId == skipPhoneId) {
                continue;
            }

            int subId = phone.getSubId();
            if (!SubscriptionManager.isValidSubscriptionId(subId)) {
                Rlog.i(TAG, "isThereOtherPhone phoneId=" + phoneId + ", subId=" + subId);
                continue;
            }
            int simState = mTelephonyManagerProxy.getSimState(phoneId);
            if ((simState == TelephonyManager.SIM_STATE_READY) || (ignoreLockState
                    && simState != TelephonyManager.SIM_STATE_ABSENT
                    && simState != TelephonyManager.SIM_STATE_NOT_READY)) {
                Rlog.i(TAG, "isThereOtherPhone found, ignoreLockState=" + ignoreLockState
                        + ", phoneId=" + phoneId + ", simState=" + simState);
                return true;
            }
            Rlog.i(TAG, "isThereOtherPhone phoneId=" + phoneId + ", simState=" + simState);
        }

        return false;
    }
}
+201 −0
Original line number Diff line number Diff line
@@ -3171,6 +3171,207 @@ public class EmergencyStateTrackerTest extends TelephonyTest {
                anyBoolean(), eq(0));
    }

    /**
     * Test Phone selection.
     * SIM absent and SIM ready on the other Phone.
     */
    @Test
    @SmallTest
    public void testSwitchPhoneAbsentAndReady() {
        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
                /* isRadioOn= */ true);
        Phone phone1 = getPhone(1);

        // Phone0
        doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
                .when(phone0).getSubId();
        doReturn(TelephonyManager.SIM_STATE_ABSENT)
                .when(mTelephonyManagerProxy).getSimState(eq(0));

        // Phone1
        doReturn(2).when(phone1).getSubId();
        doReturn(TelephonyManager.SIM_STATE_READY)
                .when(mTelephonyManagerProxy).getSimState(eq(1));

        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(
                phone0, mTestConnection1, true);
        processAllMessages();

        assertTrue(future.isDone());
        // Expect: DisconnectCause#EMERGENCY_PERM_FAILURE
        assertEquals(future.getNow(DisconnectCause.NOT_DISCONNECTED),
                Integer.valueOf(DisconnectCause.EMERGENCY_PERM_FAILURE));
        verify(phone0, never()).setEmergencyMode(anyInt(), any(Message.class));
    }

    /**
     * Test Phone selection.
     * PIN locked and SIM ready on the other Phone.
     */
    @Test
    @SmallTest
    public void testSwitchPhonePinLockedAndReady() {
        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
                /* isRadioOn= */ true);
        Phone phone1 = getPhone(1);

        // Phone0
        doReturn(1).when(phone0).getSubId();
        doReturn(TelephonyManager.SIM_STATE_PIN_REQUIRED)
                .when(mTelephonyManagerProxy).getSimState(eq(0));

        // Phone1
        doReturn(2).when(phone1).getSubId();
        doReturn(TelephonyManager.SIM_STATE_READY)
                .when(mTelephonyManagerProxy).getSimState(eq(1));

        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(
                phone0, mTestConnection1, true);
        processAllMessages();

        assertTrue(future.isDone());
        // Expect: DisconnectCause#EMERGENCY_PERM_FAILURE
        assertEquals(future.getNow(DisconnectCause.NOT_DISCONNECTED),
                Integer.valueOf(DisconnectCause.EMERGENCY_PERM_FAILURE));
        verify(phone0, never()).setEmergencyMode(anyInt(), any(Message.class));
    }

    /**
     * Test Phone selection.
     * SIM ready and SIM ready on the other Phone.
     */
    @Test
    @SmallTest
    public void testSwitchPhoneReadyAndReady() {
        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
                /* isRadioOn= */ true);
        Phone phone1 = getPhone(1);

        // Phone0
        doReturn(1).when(phone0).getSubId();
        doReturn(TelephonyManager.SIM_STATE_READY)
                .when(mTelephonyManagerProxy).getSimState(eq(0));

        // Phone1
        doReturn(2).when(phone1).getSubId();
        doReturn(TelephonyManager.SIM_STATE_READY)
                .when(mTelephonyManagerProxy).getSimState(eq(1));

        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(
                phone0, mTestConnection1, true);
        processAllMessages();

        assertFalse(future.isDone());
        verify(phone0).setEmergencyMode(anyInt(), any(Message.class));
    }

    /**
     * Test Phone selection.
     * PIN locked and PIN locked on the other Phone.
     */
    @Test
    @SmallTest
    public void testSwitchPhonePinLockedAndPinLocked() {
        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
                /* isRadioOn= */ true);
        Phone phone1 = getPhone(1);

        // Phone0
        doReturn(1).when(phone0).getSubId();
        doReturn(TelephonyManager.SIM_STATE_PIN_REQUIRED)
                .when(mTelephonyManagerProxy).getSimState(eq(0));

        // Phone1
        doReturn(2).when(phone1).getSubId();
        doReturn(TelephonyManager.SIM_STATE_PIN_REQUIRED)
                .when(mTelephonyManagerProxy).getSimState(eq(1));

        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(
                phone0, mTestConnection1, true);
        processAllMessages();

        assertFalse(future.isDone());
        verify(phone0).setEmergencyMode(anyInt(), any(Message.class));
    }

    /**
     * Test Phone selection.
     * SIM absent and SIM absent on default Phone.
     */
    @Test
    @SmallTest
    public void testSwitchPhoneAbsentAndAbsentOnDefaultPhone() {
        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
                /* isRadioOn= */ true);
        Phone phone1 = getPhone(1);

        // Phone0
        doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
                .when(phone0).getSubId();
        doReturn(TelephonyManager.SIM_STATE_ABSENT)
                .when(mTelephonyManagerProxy).getSimState(eq(0));

        // Phone1
        doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
                .when(phone1).getSubId();
        doReturn(TelephonyManager.SIM_STATE_ABSENT)
                .when(mTelephonyManagerProxy).getSimState(eq(1));

        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(
                phone1, mTestConnection1, true);
        processAllMessages();

        assertTrue(future.isDone());
        // Expect: DisconnectCause#EMERGENCY_PERM_FAILURE
        assertEquals(future.getNow(DisconnectCause.NOT_DISCONNECTED),
                Integer.valueOf(DisconnectCause.EMERGENCY_PERM_FAILURE));
        verify(phone1, never()).setEmergencyMode(anyInt(), any(Message.class));
    }

    /**
     * Test Phone selection.
     * PIN locked and PIN locked on default Phone.
     */
    @Test
    @SmallTest
    public void testSwitchPhonePinLockedandPinLockedOnDefaultPhone() {
        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
                /* isRadioOn= */ true);
        Phone phone1 = getPhone(1);

        // Phone0
        doReturn(1).when(phone0).getSubId();
        doReturn(TelephonyManager.SIM_STATE_PIN_REQUIRED)
                .when(mTelephonyManagerProxy).getSimState(eq(0));

        // Phone1
        doReturn(2).when(phone1).getSubId();
        doReturn(TelephonyManager.SIM_STATE_PIN_REQUIRED)
                .when(mTelephonyManagerProxy).getSimState(eq(1));

        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(
                phone1, mTestConnection1, true);
        processAllMessages();

        assertTrue(future.isDone());
        // Expect: DisconnectCause#EMERGENCY_PERM_FAILURE
        assertEquals(future.getNow(DisconnectCause.NOT_DISCONNECTED),
                Integer.valueOf(DisconnectCause.EMERGENCY_PERM_FAILURE));
        verify(phone1, never()).setEmergencyMode(anyInt(), any(Message.class));
    }

    private EmergencyStateTracker setupEmergencyStateTracker(
            boolean isSuplDdsSwitchRequiredForEmergencyCall) {
        doReturn(mPhoneSwitcher).when(mPhoneSwitcherProxy).getPhoneSwitcher();