Merge "Set radio power state to OFF only after getting the state changed event from modem" into main
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index e2f5980..e7500a2 100644
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -1164,6 +1164,9 @@
 
         mDesiredPowerState = power;
         setPowerStateToDesired(forEmergencyCall, isSelectedPhoneForEmergencyCall, forceApply);
+        if (mDesiredPowerState) {
+            SatelliteController.getInstance().onSetCellularRadioPowerStateRequested(true);
+        }
     }
 
     /**
@@ -1325,6 +1328,12 @@
                     // Hence, issuing shut down regardless of radio power response
                     mCi.requestShutdown(null);
                 }
+
+                ar = (AsyncResult) msg.obj;
+                if (ar.exception != null) {
+                    loge("EVENT_RADIO_POWER_OFF_DONE: exception=" + ar.exception);
+                    SatelliteController.getInstance().onPowerOffCellularRadioFailed();
+                }
                 break;
 
             // GSM
@@ -4979,7 +4988,7 @@
      */
     public void powerOffRadioSafely() {
         synchronized (this) {
-            SatelliteController.getInstance().onCellularRadioPowerOffRequested();
+            SatelliteController.getInstance().onSetCellularRadioPowerStateRequested(false);
             if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
                 EmergencyStateTracker.getInstance().onCellularRadioPowerOffRequested();
             }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index 194865b..5fb6fa7 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -241,6 +241,7 @@
     private static final int EVENT_NOTIFY_NTN_ELIGIBILITY_HYSTERESIS_TIMED_OUT = 46;
     private static final int EVENT_WIFI_CONNECTIVITY_STATE_CHANGED = 47;
     private static final int EVENT_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT = 48;
+    protected static final int EVENT_WAIT_FOR_CELLULAR_MODEM_OFF_TIMED_OUT = 49;
 
     @NonNull private static SatelliteController sInstance;
     @NonNull private final Context mContext;
@@ -344,9 +345,13 @@
     private final Object mIsSatelliteEnabledLock = new Object();
     @GuardedBy("mIsSatelliteEnabledLock")
     private Boolean mIsSatelliteEnabled = null;
-    private final Object mIsRadioOnLock = new Object();
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected final Object mIsRadioOnLock = new Object();
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected boolean mIsRadioOn;
     @GuardedBy("mIsRadioOnLock")
-    private boolean mIsRadioOn = false;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected boolean mRadioOffRequested = false;
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected final Object mSatelliteViaOemProvisionLock = new Object();
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -1325,6 +1330,14 @@
                         mIsRadioOn = true;
                     } else if (mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF) {
                         resetCarrierRoamingSatelliteModeParams();
+                        synchronized (mIsRadioOnLock) {
+                            if (mRadioOffRequested) {
+                                logd("EVENT_RADIO_STATE_CHANGED: set mIsRadioOn to false");
+                                stopWaitForCellularModemOffTimer();
+                                mIsRadioOn = false;
+                                mRadioOffRequested = false;
+                            }
+                        }
                     }
                 }
 
@@ -1635,6 +1648,13 @@
                 break;
             }
 
+            case EVENT_WAIT_FOR_CELLULAR_MODEM_OFF_TIMED_OUT: {
+                plogw("Timed out to wait for cellular modem OFF state");
+                synchronized (mIsRadioOnLock) {
+                    mRadioOffRequested = false;
+                }
+            }
+
             default:
                 Log.w(TAG, "SatelliteControllerHandler: unexpected message code: " +
                         msg.what);
@@ -1716,6 +1736,12 @@
                             SatelliteManager.SATELLITE_RESULT_INVALID_MODEM_STATE, result);
                     return;
                 }
+                if (mRadioOffRequested) {
+                    ploge("Radio is being powering off, can not enable satellite");
+                    sendErrorAndReportSessionMetrics(
+                            SatelliteManager.SATELLITE_RESULT_INVALID_MODEM_STATE, result);
+                    return;
+                }
             }
 
             if (mTelecomManager.isInEmergencyCall()) {
@@ -2900,27 +2926,50 @@
 
     /**
      * This function is used by {@link com.android.internal.telephony.ServiceStateTracker} to notify
-     * {@link SatelliteController} that it has received a request to power off the cellular radio
-     * modem. {@link SatelliteController} will then power off the satellite modem.
+     * {@link SatelliteController} that it has received a request to power on or off the cellular
+     * radio modem.
+     *
+     * @param powerOn {@code true} means cellular radio is about to be powered on, {@code false}
+     *                 means cellular modem is about to be powered off.
      */
-    public void onCellularRadioPowerOffRequested() {
-        logd("onCellularRadioPowerOffRequested()");
+    public void onSetCellularRadioPowerStateRequested(boolean powerOn) {
+        logd("onSetCellularRadioPowerStateRequested: powerOn=" + powerOn);
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            plogd("onCellularRadioPowerOffRequested: oemEnabledSatelliteFlag is disabled");
+            plogd("onSetCellularRadioPowerStateRequested: oemEnabledSatelliteFlag is disabled");
             return;
         }
 
         synchronized (mIsRadioOnLock) {
-            mIsRadioOn = false;
+            mRadioOffRequested = !powerOn;
         }
-        requestSatelliteEnabled(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
-                false /* enableSatellite */, false /* enableDemoMode */, false /* isEmergency */,
-                new IIntegerConsumer.Stub() {
-                    @Override
-                    public void accept(int result) {
-                        plogd("onRadioPowerOffRequested: requestSatelliteEnabled result=" + result);
-                    }
-                });
+        if (powerOn) {
+            stopWaitForCellularModemOffTimer();
+        } else {
+            requestSatelliteEnabled(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                    false /* enableSatellite */, false /* enableDemoMode */,
+                    false /* isEmergency */,
+                    new IIntegerConsumer.Stub() {
+                        @Override
+                        public void accept(int result) {
+                            plogd("onSetCellularRadioPowerStateRequested: requestSatelliteEnabled"
+                                    + " result=" + result);
+                        }
+                    });
+            startWaitForCellularModemOffTimer();
+        }
+    }
+
+    /**
+     * This function is used by {@link com.android.internal.telephony.ServiceStateTracker} to notify
+     * {@link SatelliteController} that the request to power off the cellular radio modem has
+     * failed.
+     */
+    public void onPowerOffCellularRadioFailed() {
+        logd("onPowerOffCellularRadioFailed");
+        synchronized (mIsRadioOnLock) {
+            mRadioOffRequested = false;
+            stopWaitForCellularModemOffTimer();
+        }
     }
 
     /**
@@ -4956,6 +5005,32 @@
                 R.integer.config_wait_for_satellite_enabling_response_timeout_millis);
     }
 
+    private long getWaitForCellularModemOffTimeoutMillis() {
+        return mContext.getResources().getInteger(
+                R.integer.config_satellite_wait_for_cellular_modem_off_timeout_millis);
+    }
+
+    private void startWaitForCellularModemOffTimer() {
+        synchronized (mIsRadioOnLock) {
+            if (hasMessages(EVENT_WAIT_FOR_CELLULAR_MODEM_OFF_TIMED_OUT)) {
+                plogd("startWaitForCellularModemOffTimer: the timer was already started");
+                return;
+            }
+            long timeoutMillis = getWaitForCellularModemOffTimeoutMillis();
+            plogd("Start timer to wait for cellular modem OFF state, timeoutMillis="
+                    + timeoutMillis);
+            sendMessageDelayed(obtainMessage(EVENT_WAIT_FOR_CELLULAR_MODEM_OFF_TIMED_OUT),
+                    timeoutMillis);
+        }
+    }
+
+    private void stopWaitForCellularModemOffTimer() {
+        synchronized (mSatelliteEnabledRequestLock) {
+            plogd("Stop timer to wait for cellular modem OFF state");
+            removeMessages(EVENT_WAIT_FOR_CELLULAR_MODEM_OFF_TIMED_OUT);
+        }
+    }
+
     private void startWaitForSatelliteEnablingResponseTimer(
             @NonNull RequestSatelliteEnabledArgument argument) {
         synchronized (mSatelliteEnabledRequestLock) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
index b9fac4a..1465176 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -26,6 +26,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.nullable;
@@ -594,7 +595,7 @@
         sst.setRadioPowerForReason(false, false, false, false, reason);
         assertTrue(sst.getRadioPowerOffReasons().contains(reason));
         assertTrue(sst.getRadioPowerOffReasons().size() == 1);
-        verify(mSatelliteController).onCellularRadioPowerOffRequested();
+        verify(mSatelliteController).onSetCellularRadioPowerStateRequested(eq(false));
         clearInvocations(mSatelliteController);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_OFF);
@@ -602,7 +603,7 @@
                 TelephonyManager.RADIO_POWER_REASON_USER);
         assertTrue(sst.getRadioPowerOffReasons().contains(reason));
         assertTrue(sst.getRadioPowerOffReasons().size() == 1);
-        verify(mSatelliteController, never()).onCellularRadioPowerOffRequested();
+        verify(mSatelliteController, never()).onSetCellularRadioPowerStateRequested(anyBoolean());
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_OFF);
 
@@ -610,7 +611,7 @@
         // had been turned off for.
         sst.setRadioPowerForReason(true, false, false, false, reason);
         assertTrue(sst.getRadioPowerOffReasons().isEmpty());
-        verify(mSatelliteController, never()).onCellularRadioPowerOffRequested();
+        verify(mSatelliteController).onSetCellularRadioPowerStateRequested(eq(true));
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
 
@@ -1928,6 +1929,8 @@
         sst.setRadioPower(false);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
+        verify(mSatelliteController).onSetCellularRadioPowerStateRequested(eq(false));
+        verify(mSatelliteController).onPowerOffCellularRadioFailed();
         sst.requestShutdown();
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertFalse(mSimulatedCommands.getRadioState()
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
index 245ae91..a3a189b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
@@ -184,6 +184,8 @@
     private static final int[] ACTIVE_SUB_IDS = {SUB_ID};
     private static final int TEST_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMEOUT_MILLIS =
             (int) TimeUnit.SECONDS.toMillis(60);
+    private static final int TEST_WAIT_FOR_CELLULAR_MODEM_OFF_TIMEOUT_MILLIS =
+            (int) TimeUnit.SECONDS.toMillis(60);
 
     private static final String SATELLITE_PLMN = "00103";
     private List<Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener>>
@@ -518,6 +520,9 @@
         mContextFixture.putIntResource(
                 R.integer.config_wait_for_satellite_enabling_response_timeout_millis,
                 TEST_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMEOUT_MILLIS);
+        mContextFixture.putIntResource(
+                R.integer.config_satellite_wait_for_cellular_modem_off_timeout_millis,
+                TEST_WAIT_FOR_CELLULAR_MODEM_OFF_TIMEOUT_MILLIS);
         doReturn(ACTIVE_SUB_IDS).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
 
         mCarrierConfigBundle = mContextFixture.getCarrierConfigBundle();
@@ -728,6 +733,87 @@
         processAllMessages();
         verify(mMockSatelliteModemInterface, times(5))
                 .requestIsSatelliteSupported(any(Message.class));
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // Radio is off during TN -> NTN image switch, SatelliteController should not set radio
+        // state to OFF
+        setRadioPower(false);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // Turn on radio
+        setRadioPower(true);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // APM is triggered
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertTrue(mSatelliteControllerUT.isRadioOffRequested());
+        assertTrue(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // SatelliteController should set the radio state to OFF
+        setRadioPower(false);
+        processAllMessages();
+        assertFalse(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // Turn on radio
+        setRadioPower(true);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // APM is triggered
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertTrue(mSatelliteControllerUT.isRadioOffRequested());
+        assertTrue(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // Modem fails to power off radio. APM is disabled
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(true);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // APM is triggered
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertTrue(mSatelliteControllerUT.isRadioOffRequested());
+        assertTrue(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // The timer WaitForCellularModemOff time out
+        moveTimeForward(TEST_WAIT_FOR_CELLULAR_MODEM_OFF_TIMEOUT_MILLIS);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // APM is triggered
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertTrue(mSatelliteControllerUT.isRadioOffRequested());
+        assertTrue(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // Modem failed to power off the radio
+        mSatelliteControllerUT.onPowerOffCellularRadioFailed();
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
     }
 
     @Test
@@ -848,7 +934,7 @@
         mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
         setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
         setRadioPower(false);
-        mSatelliteControllerUT.onCellularRadioPowerOffRequested();
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
         processAllMessages();
         sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_OFF, null);
         processAllMessages();
@@ -1062,12 +1148,49 @@
 
         resetSatelliteControllerUTToOnAndProvisionedState();
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(false);
-        mSatelliteControllerUT.onCellularRadioPowerOffRequested();
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
         processAllMessages();
         // Satellite should not be powered off since the feature flag oemEnabledSatelliteFlag is
         // disabled
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
         verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
+
+        // Successfully disable satellite.
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
+                mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+
+        // Fail to enable satellite when radio is being powered off.
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        // Radio is being powered off, can not enable satellite
+        assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
+
+        // Modem failed to power off
+        mSatelliteControllerUT.onPowerOffCellularRadioFailed();
+
+        // Successfully enable satellite when radio is on.
+        mIIntegerConsumerResults.clear();
+        mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
+        mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
+        setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
     }
 
     @Test
@@ -4719,5 +4842,21 @@
                 mIsSatelliteViaOemProvisioned = isProvisioned;
             }
         }
+
+        public boolean isRadioOn() {
+            synchronized (mIsRadioOnLock) {
+                return mIsRadioOn;
+            }
+        }
+
+        public boolean isRadioOffRequested() {
+            synchronized (mIsRadioOnLock) {
+                return mRadioOffRequested;
+            }
+        }
+
+        public boolean isWaitForCellularModemOffTimerStarted() {
+            return hasMessages(EVENT_WAIT_FOR_CELLULAR_MODEM_OFF_TIMED_OUT);
+        }
     }
 }