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

Commit f33476a2 authored by Sarah Chin's avatar Sarah Chin Committed by Android Build Coastguard Worker
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 interfere with calls 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
Merged-In: I1f5503d2420f6e8449adadc6bc4100a37f35251a
(cherry picked from commit 9b928674)
Merged-In: I1f5503d2420f6e8449adadc6bc4100a37f35251a
parent ecfac863
Loading
Loading
Loading
Loading
+25 −11
Original line number Diff line number Diff line
@@ -1086,7 +1086,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,
@@ -1190,8 +1190,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;
                    }
@@ -1491,12 +1491,13 @@ public class ServiceStateTracker extends Handler {
            case EVENT_ALL_DATA_DISCONNECTED:
                if (mPhone.isUsingNewDataStack()) {
                    log("EVENT_ALL_DATA_DISCONNECTED");
                    synchronized (this) {
                        if (mPendingRadioPowerOffAfterDataOff) {
                        mPendingRadioPowerOffAfterDataOff = false;
                        removeMessages(EVENT_SET_RADIO_POWER_OFF);
                            if (DBG) log("EVENT_ALL_DATA_DISCONNECTED, turn radio off now.");
                            cancelPendingRadioPowerOff();
                            hangupAndPowerOff();
                        }
                    }
                    return;
                }
                int dds = SubscriptionManager.getDefaultDataSubscriptionId();
@@ -3132,12 +3133,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
@@ -3147,6 +3148,19 @@ 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;
            removeMessages(EVENT_SET_RADIO_POWER_OFF);
        }
    }

    /**
+60 −0
Original line number Diff line number Diff line
@@ -92,6 +92,7 @@ import androidx.test.filters.FlakyTest;

import com.android.internal.R;
import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
import com.android.internal.telephony.data.DataNetworkController;
import com.android.internal.telephony.metrics.ServiceStateStats;
import com.android.internal.telephony.test.SimulatedCommands;
import com.android.internal.telephony.uicc.IccCardApplicationStatus;
@@ -379,6 +380,65 @@ public class ServiceStateTrackerTest extends TelephonyTest {
                != (mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON));
    }

    @Test
    public void testSetRadioPowerWaitForAllDataDisconnected() {
        // Start with radio on
        sst.setRadioPower(true);
        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
        assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());

        // Ensure data is connected
        ArgumentCaptor<DataNetworkController.DataNetworkControllerCallback> callback =
                ArgumentCaptor.forClass(DataNetworkController.DataNetworkControllerCallback.class);
        verify(mDataNetworkController, times(1)).registerDataNetworkControllerCallback(
                callback.capture());
        callback.getValue().onAnyDataNetworkExistingChanged(true);

        // Turn on APM and verify that SST is 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 */));
        assertTrue(sst.hasMessages(38 /* EVENT_SET_RADIO_POWER_OFF */));

        // Data disconnected, radio should power off now
        callback.getValue().onAnyDataNetworkExistingChanged(false);
        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
        assertEquals(TelephonyManager.RADIO_POWER_OFF, mSimulatedCommands.getRadioState());
    }

    @Test
    public void testSetRadioPowerCancelWaitForAllDataDisconnected() {
        // Start with radio on
        sst.setRadioPower(true);
        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
        assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());

        // Ensure data is connected
        ArgumentCaptor<DataNetworkController.DataNetworkControllerCallback> callback =
                ArgumentCaptor.forClass(DataNetworkController.DataNetworkControllerCallback.class);
        verify(mDataNetworkController, times(1)).registerDataNetworkControllerCallback(
                callback.capture());
        callback.getValue().onAnyDataNetworkExistingChanged(true);

        // Turn on APM and verify that SST is 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 */));
        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 */));
        assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());
    }

    @Test
    @SmallTest
    public void testSetRadioPowerOnForEmergencyCall() {