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

Commit 7e376251 authored by Sarah Chin's avatar Sarah Chin
Browse files

Fix APM race conditions in ServiceStateTracker

Root cause: When APM is turned on, SST waits for all data connections
to be disconnected before sending the power off request to the modem
and starts a timer to force power off the radio if data disconnection
takes too long. If the radio is powered on again before all connections
are disconnected, the radio power remains on, but the force power off
timer is not cleared. This causes us to send a false radio power off
request with a potentially incorrect emergency state, causing radio to
power off temporarily and issues with emergency calling immediately
afterwards.

If data teardown is pending after APM and the radio is powered on,
cancel the pending radio power off timer. To prevent additional race
conditions, also synchronize all blocks where we modify the pending
radio power off state.

Bug: 235941750
Test: manual basic tests
Test: atest ServiceStateTrackerTest
Change-Id: I1f5503d2420f6e8449adadc6bc4100a37f35251a
parent 20c815e5
Loading
Loading
Loading
Loading
+29 −11
Original line number Diff line number Diff line
@@ -1093,7 +1093,7 @@ public class ServiceStateTracker extends Handler {
    /**
     * Turn on or off radio power with option to specify whether it's for emergency call and specify
     * a reason for setting the power state.
     * More details check {@link PhoneInternalInterface#setRadioPower(
     * More details check {@link PhoneInternalInterface#setRadioPowerForReason(
     * boolean, boolean, boolean, boolean, int)}.
     */
    public void setRadioPowerForReason(boolean power, boolean forEmergencyCall,
@@ -1197,8 +1197,8 @@ public class ServiceStateTracker extends Handler {
            case EVENT_SET_RADIO_POWER_OFF:
                synchronized (this) {
                    if (mPhone.isUsingNewDataStack()) {
                        mPendingRadioPowerOffAfterDataOff = false;
                        log("Wait for all data networks torn down timed out. Power off now.");
                        cancelPendingRadioPowerOff();
                        hangupAndPowerOff();
                        return;
                    }
@@ -1494,12 +1494,13 @@ public class ServiceStateTracker extends Handler {
                            areAllDataDisconnectedOnAllPhones = false;
                        }
                    }
                    synchronized (this) {
                        if (areAllDataDisconnectedOnAllPhones) {
                        mPendingRadioPowerOffAfterDataOff = false;
                        removeMessages(EVENT_SET_RADIO_POWER_OFF);
                            if (DBG) log("Data disconnected for all phones, turn radio off now.");
                            cancelPendingRadioPowerOff();
                            hangupAndPowerOff();
                        }
                    }
                    return;
                }
                int dds = SubscriptionManager.getDefaultDataSubscriptionId();
@@ -3135,12 +3136,12 @@ public class ServiceStateTracker extends Handler {
                    && getRadioPowerOffDelayTimeoutForImsRegistration() > 0) {
                if (DBG) log("setPowerStateToDesired: delaying power off until IMS dereg.");
                startDelayRadioOffWaitingForImsDeregTimeout();
                // Return early here as we do not want to hit the cancel timeout code below.
                return;
            } else {
                if (DBG) log("setPowerStateToDesired: powerOffRadioSafely()");
                powerOffRadioSafely();
            }
            // Return early here as we do not want to hit the cancel timeout code below.
            return;
        } else if (mDeviceShuttingDown
                && (mCi.getRadioState() != TelephonyManager.RADIO_POWER_UNAVAILABLE)) {
            // !mDesiredPowerState condition above will happen first if the radio is on, so we will
@@ -3150,6 +3151,23 @@ public class ServiceStateTracker extends Handler {
        }
        // Cancel any pending timeouts because the state has been re-evaluated.
        cancelDelayRadioOffWaitingForImsDeregTimeout();
        cancelPendingRadioPowerOff();
    }

    /**
     * Cancel the pending radio power off request that was sent to force the radio to power off
     * if waiting for all data to disconnect times out.
     */
    private synchronized void cancelPendingRadioPowerOff() {
        if (mPhone.isUsingNewDataStack() && mPendingRadioPowerOffAfterDataOff) {
            if (DBG) log("cancelPendingRadioPowerOff: cancelling.");
            mPendingRadioPowerOffAfterDataOff = false;
            for (Phone phone : PhoneFactory.getPhones()) {
                phone.getDataNetworkController().unregisterDataNetworkControllerCallback(
                        mDataDisconnectedCallback);
            }
            removeMessages(EVENT_SET_RADIO_POWER_OFF);
        }
    }

    /**
+52 −1
Original line number Diff line number Diff line
@@ -417,6 +417,7 @@ public class ServiceStateTrackerTest extends TelephonyTest {
                callback1.capture());
        verify(dataNetworkController_phone2, times(1)).registerDataNetworkControllerCallback(
                callback2.capture());
        assertTrue(sst.hasMessages(38 /* EVENT_SET_RADIO_POWER_OFF */));

        // Data disconnected on sub 2, still waiting for data disconnected on sub 1
        doReturn(true).when(dataNetworkController_phone2).areAllDataDisconnected();
@@ -430,10 +431,60 @@ public class ServiceStateTrackerTest extends TelephonyTest {
        doReturn(true).when(mDataNetworkController).areAllDataDisconnected();
        callback1.getValue().onAnyDataNetworkExistingChanged(false);
        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
        verify(mDataNetworkController, times(1)).unregisterDataNetworkControllerCallback(any());
        verify(mDataNetworkController, times(2)).unregisterDataNetworkControllerCallback(any());
        assertEquals(TelephonyManager.RADIO_POWER_OFF, mSimulatedCommands.getRadioState());
    }

    @Test
    public void testSetRadioPowerCancelWaitForAllDataDisconnected() throws Exception {
        // Set up DSDS environment
        GsmCdmaPhone phone2 = Mockito.mock(GsmCdmaPhone.class);
        DataNetworkController dataNetworkController_phone2 =
                Mockito.mock(DataNetworkController.class);
        mPhones = new Phone[] {mPhone, phone2};
        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
        doReturn(dataNetworkController_phone2).when(phone2).getDataNetworkController();
        doReturn(mSST).when(phone2).getServiceStateTracker();
        doReturn(true).when(phone2).isUsingNewDataStack();
        doReturn(false).when(mDataNetworkController).areAllDataDisconnected();
        doReturn(false).when(dataNetworkController_phone2).areAllDataDisconnected();
        doReturn(1).when(mPhone).getSubId();
        doReturn(2).when(phone2).getSubId();

        // Start with radio on
        sst.setRadioPower(true);
        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
        assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());

        // Turn on APM and verify that both subs are waiting for all data disconnected
        sst.setRadioPower(false);
        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
        assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());
        verify(mDataNetworkController).tearDownAllDataNetworks(
                eq(3 /* TEAR_DOWN_REASON_AIRPLANE_MODE_ON */));
        verify(dataNetworkController_phone2, never()).tearDownAllDataNetworks(anyInt());
        ArgumentCaptor<DataNetworkController.DataNetworkControllerCallback> callback1 =
                ArgumentCaptor.forClass(DataNetworkController.DataNetworkControllerCallback.class);
        ArgumentCaptor<DataNetworkController.DataNetworkControllerCallback> callback2 =
                ArgumentCaptor.forClass(DataNetworkController.DataNetworkControllerCallback.class);
        verify(mDataNetworkController, times(1)).registerDataNetworkControllerCallback(
                callback1.capture());
        verify(dataNetworkController_phone2, times(1)).registerDataNetworkControllerCallback(
                callback2.capture());
        assertTrue(sst.hasMessages(38 /* EVENT_SET_RADIO_POWER_OFF */));

        // Turn off APM while waiting for data to disconnect
        sst.setRadioPower(true);
        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());

        // Check that radio is on and pending power off messages were cleared
        assertFalse(sst.hasMessages(38 /* EVENT_SET_RADIO_POWER_OFF */));
        verify(mDataNetworkController, times(1)).unregisterDataNetworkControllerCallback(any());
        verify(dataNetworkController_phone2, times(1)).unregisterDataNetworkControllerCallback(
                any());
        assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());
    }

    @Test
    @SmallTest
    public void testSetRadioPowerOnForEmergencyCall() {