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

Commit ee4fb0c7 authored by Brad Ebinger's avatar Brad Ebinger
Browse files

Remove dependency on Intent for IMS deregistration timeout event

When the device is under high load situations or booting up for the
first time, Intent delivery to the telephony process is not
guaranteed to be timely. Old code was using a PendingIntent + AlarmManager
to deliver the event to time out turning off the radio if IMS was
registered when Airplane mode was enabled, which would sometimes not
be delivered for a long time after Airplane mode was enabled.

This change removes the AlarmManager pattern and uses a more traditional
Handler approach to triggering a timeout.

Fixes: 178682618
Test: atest FrameworksTelephonyTests
Change-Id: I38fbc2f4fe24b8ad2f6721149e2b7d77fa9d590b
parent ae91ab32
Loading
Loading
Loading
Loading
+74 −66
Original line number Diff line number Diff line
@@ -25,10 +25,8 @@ import static com.android.internal.telephony.uicc.IccRecords.CARRIER_NAME_DISPLA
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -251,6 +249,12 @@ public class ServiceStateTracker extends Handler {
    /** Signal strength poll rate. */
    private static final int POLL_PERIOD_MILLIS = 20 * 1000;

    /**
     * The time we wait for IMS to deregister before executing a pending radio power off request.
     */
    @VisibleForTesting
    public static final int DELAY_RADIO_OFF_FOR_IMS_DEREG_TIMEOUT = 3 * 1000;

    /** Waiting period before recheck gprs and voice registration. */
    public static final int DEFAULT_GPRS_CHECK_PERIOD_MILLIS = 60 * 1000;

@@ -290,6 +294,7 @@ public class ServiceStateTracker extends Handler {
    public    static final int EVENT_ICC_CHANGED                       = 42;
    protected static final int EVENT_GET_CELL_INFO_LIST                = 43;
    protected static final int EVENT_UNSOL_CELL_INFO_LIST              = 44;
    // Only sent if the IMS state is moving from true -> false
    protected static final int EVENT_CHANGE_IMS_STATE                  = 45;
    protected static final int EVENT_IMS_STATE_CHANGED                 = 46;
    protected static final int EVENT_IMS_STATE_DONE                    = 47;
@@ -306,6 +311,8 @@ public class ServiceStateTracker extends Handler {
    private static final int EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST  = 59;
    private static final int EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST = 60;
    private static final int EVENT_ON_DEVICE_IDLE_STATE_CHANGED         = 61;
    // Timeout event used when delaying radio power off to wait for IMS deregistration to happen.
    private static final int EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT    = 62;

    /**
     * The current service state.
@@ -340,12 +347,8 @@ public class ServiceStateTracker extends Handler {
    private CarrierDisplayNameResolver mCdnr;

    private boolean mImsRegistrationOnOff = false;
    private boolean mAlarmSwitch = false;
    /** Radio is disabled by carrier. Radio power will not be override if this field is set */
    private boolean mRadioDisabledByCarrier = false;
    private PendingIntent mRadioOffIntent = null;
    private static final String ACTION_RADIO_OFF = "android.intent.action.ACTION_RADIO_OFF";
    private boolean mPowerOffDelayNeed = true;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private boolean mDeviceShuttingDown = false;
    /** Keep track of SPN display rules, so we only broadcast intent if something changes. */
@@ -589,9 +592,6 @@ public class ServiceStateTracker extends Handler {
            if (intent.getAction().equals(Intent.ACTION_LOCALE_CHANGED)) {
                // Update emergency string or operator name, polling service state.
                pollState();
            } else if (intent.getAction().equals(ACTION_RADIO_OFF)) {
                mAlarmSwitch = false;
                powerOffRadioSafely();
            } else if (intent.getAction().equals(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED)) {
                String lastKnownNetworkCountry = intent.getStringExtra(
                        TelephonyManager.EXTRA_LAST_KNOWN_NETWORK_COUNTRY);
@@ -725,14 +725,7 @@ public class ServiceStateTracker extends Handler {
        Context context = mPhone.getContext();
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
        context.registerReceiver(mIntentReceiver, filter);
        filter = new IntentFilter();
        filter.addAction(ACTION_RADIO_OFF);
        context.registerReceiver(mIntentReceiver, filter);
        filter = new IntentFilter();
        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
        context.registerReceiver(mIntentReceiver, filter);
        filter = new IntentFilter();
        filter.addAction(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED);
        context.registerReceiver(mIntentReceiver, filter);

@@ -872,6 +865,7 @@ public class ServiceStateTracker extends Handler {
        mCi.unregisterForImsNetworkStateChanged(this);
        mPhone.getCarrierActionAgent().unregisterForCarrierAction(this,
                CARRIER_ACTION_SET_RADIO_ENABLED);
        mPhone.getContext().unregisterReceiver(mIntentReceiver);
        if (mCSST != null) {
            mCSST.dispose();
            mCSST = null;
@@ -1838,6 +1832,12 @@ public class ServiceStateTracker extends Handler {
                break;
            }

            case EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT: {
                if (DBG) log("EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT triggered");
                powerOffRadioSafely();
                break;
            }

            default:
                log("Unhandled message with number: " + msg.what);
                break;
@@ -3185,24 +3185,16 @@ public class ServiceStateTracker extends Handler {
    protected void setPowerStateToDesired(boolean forEmergencyCall,
            boolean isSelectedPhoneForEmergencyCall, boolean forceApply) {
        if (DBG) {
            String tmpLog = "mDeviceShuttingDown=" + mDeviceShuttingDown +
            String tmpLog = "setPowerStateToDesired: mDeviceShuttingDown=" + mDeviceShuttingDown +
                    ", mDesiredPowerState=" + mDesiredPowerState +
                    ", getRadioState=" + mCi.getRadioState() +
                    ", mPowerOffDelayNeed=" + mPowerOffDelayNeed +
                    ", mAlarmSwitch=" + mAlarmSwitch +
                    ", mRadioDisabledByCarrier=" + mRadioDisabledByCarrier;
                    ", mRadioDisabledByCarrier=" + mRadioDisabledByCarrier +
                    ", IMS reg state=" + mImsRegistrationOnOff +
                    ", pending radio off=" + hasMessages(EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT);
            log(tmpLog);
            mRadioPowerLog.log(tmpLog);
        }

        if (mAlarmSwitch) {
            if(DBG) log("mAlarmSwitch == true");
            Context context = mPhone.getContext();
            AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
            am.cancel(mRadioOffIntent);
            mAlarmSwitch = false;
        }

        // If we want it on and it's off, turn it on
        if (mDesiredPowerState && !mRadioDisabledByCarrier
                && (forceApply || mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF)) {
@@ -3210,30 +3202,50 @@ public class ServiceStateTracker extends Handler {
        } else if ((!mDesiredPowerState || mRadioDisabledByCarrier) && mCi.getRadioState()
                == TelephonyManager.RADIO_POWER_ON) {
            // If it's on and available and we want it off gracefully
            if (mPowerOffDelayNeed) {
                if (mImsRegistrationOnOff && !mAlarmSwitch) {
                    if(DBG) log("mImsRegistrationOnOff == true");
                    Context context = mPhone.getContext();
                    AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

                    Intent intent = new Intent(ACTION_RADIO_OFF);
                    mRadioOffIntent = PendingIntent.getBroadcast(
                            context, 0, intent, PendingIntent.FLAG_IMMUTABLE);

                    mAlarmSwitch = true;
                    if (DBG) log("Alarm setting");
                    am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                            SystemClock.elapsedRealtime() + 3000, mRadioOffIntent);
                } else {
                    powerOffRadioSafely();
                }
            if (mImsRegistrationOnOff) {
                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: powering off");
                powerOffRadioSafely();
            }
        } else if (mDeviceShuttingDown
                && (mCi.getRadioState() != TelephonyManager.RADIO_POWER_UNAVAILABLE)) {
            // !mDesiredPowerState condition above will happen first if the radio is on, so we will
            // see the following: (delay for IMS dereg) -> RADIO_POWER_OFF ->
            // RADIO_POWER_UNAVAILABLE
            mCi.requestShutdown(null);
        }
        // Cancel any pending timeouts because the state has been re-evaluated.
        cancelDelayRadioOffWaitingForImsDeregTimeout();
    }

    /**
     * Cancel the EVENT_POWER_OFF_RADIO_DELAYED event if it is currently pending to be completed.
     * @return true if there was a pending timeout message in the queue, false otherwise.
     */
    private void cancelDelayRadioOffWaitingForImsDeregTimeout() {
        if (hasMessages(EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT)) {
            if (DBG) log("cancelDelayRadioOffWaitingForImsDeregTimeout: cancelling.");
            removeMessages(EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT);
        }
    }

    /**
     * Start a timer to turn off the radio if IMS does not move to deregistered after the
     * radio power off event occurred. If this event already exists in the message queue, then
     * ignore the new request and use the existing one.
     */
    private void startDelayRadioOffWaitingForImsDeregTimeout() {
        if (hasMessages(EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT)) {
            if (DBG) log("startDelayRadioOffWaitingForImsDeregTimeout: timer exists, ignoring");
            return;
        }
        if (DBG) log("startDelayRadioOffWaitingForImsDeregTimeout: starting timer");
        sendEmptyMessageDelayed(EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT,
                DELAY_RADIO_OFF_FOR_IMS_DEREG_TIMEOUT);
    }

    protected void onUpdateIccAvailability() {
@@ -3356,22 +3368,12 @@ public class ServiceStateTracker extends Handler {
    public void setImsRegistrationState(final boolean registered) {
        log("setImsRegistrationState: {registered=" + registered
                + " mImsRegistrationOnOff=" + mImsRegistrationOnOff
                + " mAlarmSwitch=" + mAlarmSwitch
                + "}");

        if (mImsRegistrationOnOff && !registered) {
            if (mAlarmSwitch) {
                mImsRegistrationOnOff = registered;

                final Context context = mPhone.getContext();
                final AlarmManager am =
                        (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
                am.cancel(mRadioOffIntent);
                mAlarmSwitch = false;

        if (mImsRegistrationOnOff && !registered) {
            // moving to deregistered, only send this event if we need to re-evaluate
            sendMessage(obtainMessage(EVENT_CHANGE_IMS_STATE));
                return;
            }
        }
        mImsRegistrationOnOff = registered;
    }
@@ -5046,7 +5048,9 @@ public class ServiceStateTracker extends Handler {
                                    Phone.REASON_RADIO_TURNED_OFF);
                        }
                    }
                    if (DBG) log("Data disconnected, turn off radio right away.");
                    if (DBG) {
                        log("powerOffRadioSafely: Data disconnected, turn off radio now.");
                    }
                    hangupAndPowerOff();
                } else {
                    // hang up all active voice calls first
@@ -5065,7 +5069,7 @@ public class ServiceStateTracker extends Handler {
                    if (dds != mPhone.getSubId()
                            && !ProxyController.getInstance().areAllDataDisconnected(dds)) {
                        if (DBG) {
                            log(String.format("Data is active on DDS (%d)."
                            log(String.format("powerOffRadioSafely: Data is active on DDS (%d)."
                                    + " Wait for all data disconnect", dds));
                        }
                        // Data is not disconnected on DDS. Wait for the data disconnect complete
@@ -5078,10 +5082,14 @@ public class ServiceStateTracker extends Handler {
                    msg.what = EVENT_SET_RADIO_POWER_OFF;
                    msg.arg1 = ++mPendingRadioPowerOffAfterDataOffTag;
                    if (sendMessageDelayed(msg, 30000)) {
                        if (DBG) log("Wait up to 30s for data to disconnect, then turn off radio.");
                        if (DBG) {
                            log("powerOffRadioSafely: Wait up to 30s for data to isconnect, then"
                                    + " turn off radio.");
                        }
                        mPendingRadioPowerOffAfterDataOff = true;
                    } else {
                        log("Cannot send delayed Msg, turn off radio right away.");
                        log("powerOffRadioSafely: Cannot send delayed Msg, turn off radio right"
                                + " away.");
                        hangupAndPowerOff();
                        mPendingRadioPowerOffAfterDataOff = false;
                    }
@@ -5602,9 +5610,9 @@ public class ServiceStateTracker extends Handler {
        pw.flush();
        pw.println(" mImsRegistered=" + mImsRegistered);
        pw.println(" mImsRegistrationOnOff=" + mImsRegistrationOnOff);
        pw.println(" mAlarmSwitch=" + mAlarmSwitch);
        pw.println(" pending radio off event="
                + hasMessages(DELAY_RADIO_OFF_FOR_IMS_DEREG_TIMEOUT));
        pw.println(" mRadioDisabledByCarrier" + mRadioDisabledByCarrier);
        pw.println(" mPowerOffDelayNeed=" + mPowerOffDelayNeed);
        pw.println(" mDeviceShuttingDown=" + mDeviceShuttingDown);
        pw.println(" mSpnUpdatePending=" + mSpnUpdatePending);
        pw.println(" mLteRsrpBoost=" + mLteRsrpBoost);
+69 −7
Original line number Diff line number Diff line
@@ -36,10 +36,8 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -127,8 +125,6 @@ public class ServiceStateTrackerTest extends TelephonyTest {
    private ProxyController mProxyController;
    @Mock
    private Handler mTestHandler;
    @Mock
    protected AlarmManager mAlarmManager;

    private CellularNetworkService mCellularNetworkService;

@@ -235,8 +231,6 @@ public class ServiceStateTrackerTest extends TelephonyTest {
        logd("ServiceStateTrackerTest +Setup!");
        super.setUp("ServiceStateTrackerTest");

        doReturn(mAlarmManager).when(mContext).getSystemService(eq(Context.ALARM_SERVICE));

        mContextFixture.putResource(R.string.config_wwan_network_service_package,
                "com.android.phone");
        mContextFixture.putResource(R.string.config_wlan_network_service_package,
@@ -1937,11 +1931,79 @@ public class ServiceStateTrackerTest extends TelephonyTest {
        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());

        sst.setImsRegistrationState(false);
        verify(mAlarmManager).cancel(any(PendingIntent.class));
        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
        assertEquals(TelephonyManager.RADIO_POWER_UNAVAILABLE, mSimulatedCommands.getRadioState());
    }

    @Test
    @SmallTest
    public void testImsRegisteredDelayShutDown() throws Exception {
        doReturn(true).when(mPhone).isPhoneTypeGsm();
        sst.setImsRegistrationState(true);
        mSimulatedCommands.setRadioPowerFailResponse(false);
        sst.setRadioPower(true);
        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());

        // Turn off the radio and ensure radio power is still on
        assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());
        sst.setRadioPower(false);
        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
        assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());

        // Now set IMS reg state to false and ensure we see the modem move to power off.
        sst.setImsRegistrationState(false);
        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
        assertEquals(TelephonyManager.RADIO_POWER_OFF, mSimulatedCommands.getRadioState());
    }

    @Test
    @SmallTest
    public void testImsRegisteredDelayShutDownTimeout() throws Exception {
        doReturn(true).when(mPhone).isPhoneTypeGsm();
        sst.setImsRegistrationState(true);
        mSimulatedCommands.setRadioPowerFailResponse(false);
        sst.setRadioPower(true);
        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());

        // Turn off the radio and ensure radio power is still on
        assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());
        sst.setRadioPower(false);
        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
        assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());

        // Ensure that if we never turn deregister for IMS, we still eventually see radio state
        // move to off.
        // Timeout for IMS reg + some extra time to remove race conditions
        waitForDelayedHandlerAction(mSSTTestHandler.getThreadHandler(),
                ServiceStateTracker.DELAY_RADIO_OFF_FOR_IMS_DEREG_TIMEOUT + 100, 1000);
        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
        assertEquals(TelephonyManager.RADIO_POWER_OFF, mSimulatedCommands.getRadioState());
    }

    @Test
    @SmallTest
    public void testImsRegisteredAPMOnOffToggle() throws Exception {
        doReturn(true).when(mPhone).isPhoneTypeGsm();
        sst.setImsRegistrationState(true);
        mSimulatedCommands.setRadioPowerFailResponse(false);
        sst.setRadioPower(true);
        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());

        // Turn off the radio and ensure radio power is still on and then turn it back on again
        assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());
        sst.setRadioPower(false);
        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
        sst.setRadioPower(true);
        assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());

        // Ensure the timeout was cancelled and we still see radio power is on.
        // Timeout for IMS reg + some extra time to remove race conditions
        waitForDelayedHandlerAction(mSSTTestHandler.getThreadHandler(),
                ServiceStateTracker.DELAY_RADIO_OFF_FOR_IMS_DEREG_TIMEOUT + 100, 1000);
        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
        assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());
    }

    @Test
    @SmallTest
    public void testSetTimeFromNITZStr() throws Exception {
+13 −0
Original line number Diff line number Diff line
@@ -934,6 +934,19 @@ public abstract class TelephonyTest {
                mockTelephonyManager).getCarrierPrivilegeStatus(anyInt());
    }

    protected final void waitForDelayedHandlerAction(Handler h, long delayMillis,
            long timeoutMillis) {
        final CountDownLatch lock = new CountDownLatch(1);
        h.postDelayed(lock::countDown, delayMillis);
        while (lock.getCount() > 0) {
            try {
                lock.await(delayMillis + timeoutMillis, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                // do nothing
            }
        }
    }

    protected final void waitForHandlerAction(Handler h, long timeoutMillis) {
        final CountDownLatch lock = new CountDownLatch(1);
        h.post(lock::countDown);