Exit doze during an emergency call.

Exit Doze when there's an active emergency call so that
location and other critical features can work during the call.

Bug: 247441185
Bug: 263218207
Test: atest DeviceIdleTest
Test: atest FrameworksMockingServicesTests:DeviceIdleControllerTest

Change-Id: If2d643c4a371f6633968541b3b238a5898cba15d
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 4caaa09..79a2659 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -79,6 +79,9 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -146,15 +149,17 @@
        label="deep";
 
        STATE_ACTIVE [
-         label="STATE_ACTIVE\nScreen on OR Charging OR Alarm going off soon",
+         label="STATE_ACTIVE\nScreen on OR charging OR alarm going off soon\n"
+             + "OR active emergency call",
          color=black,shape=diamond
        ]
        STATE_INACTIVE [
-         label="STATE_INACTIVE\nScreen off AND Not charging",color=black,shape=diamond
+         label="STATE_INACTIVE\nScreen off AND not charging AND no active emergency call",
+         color=black,shape=diamond
        ]
        STATE_QUICK_DOZE_DELAY [
          label="STATE_QUICK_DOZE_DELAY\n"
-             + "Screen off AND Not charging\n"
+             + "Screen off AND not charging AND no active emergency call\n"
              + "Location, motion detection, and significant motion monitoring turned off",
          color=black,shape=diamond
        ]
@@ -237,11 +242,12 @@
        label="light"
 
        LIGHT_STATE_ACTIVE [
-         label="LIGHT_STATE_ACTIVE\nScreen on OR Charging OR Alarm going off soon",
+         label="LIGHT_STATE_ACTIVE\n"
+             + "Screen on OR charging OR alarm going off soon OR active emergency call",
          color=black,shape=diamond
        ]
        LIGHT_STATE_INACTIVE [
-         label="LIGHT_STATE_INACTIVE\nScreen off AND Not charging",
+         label="LIGHT_STATE_INACTIVE\nScreen off AND not charging AND no active emergency call",
          color=black,shape=diamond
        ]
        LIGHT_STATE_IDLE [label="LIGHT_STATE_IDLE\n",color=red,shape=box]
@@ -411,6 +417,7 @@
     private static final int ACTIVE_REASON_FROM_BINDER_CALL = 5;
     private static final int ACTIVE_REASON_FORCED = 6;
     private static final int ACTIVE_REASON_ALARM = 7;
+    private static final int ACTIVE_REASON_EMERGENCY_CALL = 8;
     @VisibleForTesting
     static final int SET_IDLE_FACTOR_RESULT_UNINIT = -1;
     @VisibleForTesting
@@ -765,6 +772,8 @@
         }
     };
 
+    private final EmergencyCallListener mEmergencyCallListener = new EmergencyCallListener();
+
     /** Post stationary status only to this listener. */
     private void postStationaryStatus(DeviceIdleInternal.StationaryListener listener) {
         mHandler.obtainMessage(MSG_REPORT_STATIONARY_STATUS, listener).sendToTarget();
@@ -2323,6 +2332,39 @@
         }
     }
 
+    private class EmergencyCallListener extends TelephonyCallback implements
+            TelephonyCallback.OutgoingEmergencyCallListener,
+            TelephonyCallback.CallStateListener {
+        private volatile boolean mIsEmergencyCallActive;
+
+        @Override
+        public void onOutgoingEmergencyCall(EmergencyNumber placedEmergencyNumber,
+                int subscriptionId) {
+            mIsEmergencyCallActive = true;
+            if (DEBUG) Slog.d(TAG, "onOutgoingEmergencyCall(): subId = " + subscriptionId);
+            synchronized (DeviceIdleController.this) {
+                mActiveReason = ACTIVE_REASON_EMERGENCY_CALL;
+                becomeActiveLocked("emergency call", Process.myUid());
+            }
+        }
+
+        @Override
+        public void onCallStateChanged(int state) {
+            if (DEBUG) Slog.d(TAG, "onCallStateChanged(): state is " + state);
+            // An emergency call just finished
+            if (state == TelephonyManager.CALL_STATE_IDLE && mIsEmergencyCallActive) {
+                mIsEmergencyCallActive = false;
+                synchronized (DeviceIdleController.this) {
+                    becomeInactiveIfAppropriateLocked();
+                }
+            }
+        }
+
+        boolean isEmergencyCallActive() {
+            return mIsEmergencyCallActive;
+        }
+    }
+
     static class Injector {
         private final Context mContext;
         private ConnectivityManager mConnectivityManager;
@@ -2406,6 +2448,10 @@
             return mContext.getSystemService(SensorManager.class);
         }
 
+        TelephonyManager getTelephonyManager() {
+            return mContext.getSystemService(TelephonyManager.class);
+        }
+
         ConstraintController getConstraintController(Handler handler,
                 DeviceIdleInternal localService) {
             if (mContext.getPackageManager()
@@ -2634,6 +2680,9 @@
 
                 mLocalActivityTaskManager.registerScreenObserver(mScreenObserver);
 
+                mInjector.getTelephonyManager().registerTelephonyCallback(
+                        JobSchedulerBackgroundThread.getExecutor(), mEmergencyCallListener);
+
                 passWhiteListsToForceAppStandbyTrackerLocked();
                 updateInteractivityLocked();
             }
@@ -3435,6 +3484,7 @@
 
         final boolean isScreenBlockingInactive =
                 mScreenOn && (!mConstants.WAIT_FOR_UNLOCK || !mScreenLocked);
+        final boolean isEmergencyCallActive = mEmergencyCallListener.isEmergencyCallActive();
         if (DEBUG) {
             Slog.d(TAG, "becomeInactiveIfAppropriateLocked():"
                     + " isScreenBlockingInactive=" + isScreenBlockingInactive
@@ -3442,10 +3492,11 @@
                     + ", WAIT_FOR_UNLOCK=" + mConstants.WAIT_FOR_UNLOCK
                     + ", mScreenLocked=" + mScreenLocked + ")"
                     + " mCharging=" + mCharging
+                    + " emergencyCall=" + isEmergencyCallActive
                     + " mForceIdle=" + mForceIdle
             );
         }
-        if (!mForceIdle && (mCharging || isScreenBlockingInactive)) {
+        if (!mForceIdle && (mCharging || isScreenBlockingInactive || isEmergencyCallActive)) {
             return;
         }
         // Become inactive and determine if we will ultimately go idle.
@@ -3568,6 +3619,17 @@
         }
         EventLogTags.writeDeviceIdleLightStep();
 
+        if (mEmergencyCallListener.isEmergencyCallActive()) {
+            // The emergency call should have raised the state to ACTIVE and kept it there,
+            // so this method shouldn't be called. Don't proceed further.
+            Slog.wtf(TAG, "stepLightIdleStateLocked called when emergency call is active");
+            if (mLightState != LIGHT_STATE_ACTIVE) {
+                mActiveReason = ACTIVE_REASON_EMERGENCY_CALL;
+                becomeActiveLocked("emergency", Process.myUid());
+            }
+            return;
+        }
+
         switch (mLightState) {
             case LIGHT_STATE_INACTIVE:
                 mCurLightIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
@@ -3650,6 +3712,17 @@
         if (DEBUG) Slog.d(TAG, "stepIdleStateLocked: mState=" + mState);
         EventLogTags.writeDeviceIdleStep();
 
+        if (mEmergencyCallListener.isEmergencyCallActive()) {
+            // The emergency call should have raised the state to ACTIVE and kept it there,
+            // so this method shouldn't be called. Don't proceed further.
+            Slog.wtf(TAG, "stepIdleStateLocked called when emergency call is active");
+            if (mState != STATE_ACTIVE) {
+                mActiveReason = ACTIVE_REASON_EMERGENCY_CALL;
+                becomeActiveLocked("emergency", Process.myUid());
+            }
+            return;
+        }
+
         if (isUpcomingAlarmClock()) {
             // Whoops, there is an upcoming alarm.  We don't actually want to go idle.
             if (mState != STATE_ACTIVE) {
@@ -3984,6 +4057,11 @@
         }
     }
 
+    @VisibleForTesting
+    boolean isEmergencyCallActive() {
+        return mEmergencyCallListener.isEmergencyCallActive();
+    }
+
     @GuardedBy("this")
     boolean isOpsInactiveLocked() {
         return mActiveIdleOpCount <= 0 && !mJobsActive && !mAlarmsActive;
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index bcd69fda..8582012 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -86,6 +86,9 @@
 import android.os.PowerSaveState;
 import android.os.SystemClock;
 import android.provider.DeviceConfig;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -119,6 +122,8 @@
     private AnyMotionDetectorForTest mAnyMotionDetector;
     private AppStateTrackerForTest mAppStateTracker;
     private DeviceIdleController.Constants mConstants;
+    private TelephonyCallback.OutgoingEmergencyCallListener mEmergencyCallListener;
+    private TelephonyCallback.CallStateListener mCallStateListener;
     private InjectorForTest mInjector;
 
     private MockitoSession mMockingSession;
@@ -140,6 +145,8 @@
     private Sensor mMotionSensor;
     @Mock
     private SensorManager mSensorManager;
+    @Mock
+    private TelephonyManager mTelephonyManager;
 
     class InjectorForTest extends DeviceIdleController.Injector {
         ConnectivityManager connectivityManager;
@@ -232,6 +239,11 @@
         }
 
         @Override
+        TelephonyManager getTelephonyManager() {
+            return mTelephonyManager;
+        }
+
+        @Override
         boolean useMotionSensor() {
             return true;
         }
@@ -343,6 +355,15 @@
 
         // Get the same Constants object that mDeviceIdleController got.
         mConstants = mInjector.getConstants(mDeviceIdleController);
+
+        final ArgumentCaptor<TelephonyCallback> telephonyCallbackCaptor =
+                ArgumentCaptor.forClass(TelephonyCallback.class);
+        verify(mTelephonyManager)
+                .registerTelephonyCallback(any(), telephonyCallbackCaptor.capture());
+        mEmergencyCallListener = (TelephonyCallback.OutgoingEmergencyCallListener)
+                telephonyCallbackCaptor.getValue();
+        mCallStateListener =
+                (TelephonyCallback.CallStateListener) telephonyCallbackCaptor.getValue();
     }
 
     @After
@@ -531,6 +552,16 @@
 
         mDeviceIdleController.becomeInactiveIfAppropriateLocked();
         verifyStateConditions(STATE_ACTIVE);
+
+        // All other conditions allow for going INACTIVE...
+        setAlarmSoon(false);
+        setChargingOn(false);
+        setScreenOn(false);
+        // ...except the emergency call.
+        setEmergencyCallActive(true);
+
+        mDeviceIdleController.becomeInactiveIfAppropriateLocked();
+        verifyStateConditions(STATE_ACTIVE);
     }
 
     @Test
@@ -559,6 +590,15 @@
 
         mDeviceIdleController.becomeInactiveIfAppropriateLocked();
         verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+
+        // All other conditions allow for going INACTIVE...
+        setChargingOn(false);
+        setScreenOn(false);
+        // ...except the emergency call.
+        setEmergencyCallActive(true);
+
+        mDeviceIdleController.becomeInactiveIfAppropriateLocked();
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
     }
 
     @Test
@@ -569,6 +609,7 @@
         setAlarmSoon(false);
         setChargingOn(false);
         setScreenOn(false);
+        setEmergencyCallActive(false);
 
         mDeviceIdleController.becomeInactiveIfAppropriateLocked();
         verifyStateConditions(STATE_INACTIVE);
@@ -613,6 +654,7 @@
 
         setChargingOn(false);
         setScreenOn(false);
+        setEmergencyCallActive(false);
 
         mDeviceIdleController.becomeInactiveIfAppropriateLocked();
         verifyLightStateConditions(LIGHT_STATE_INACTIVE);
@@ -1147,6 +1189,22 @@
                 eq(true));
     }
 
+    @Test
+    public void testEmergencyCallEndTriggersInactive() {
+        setAlarmSoon(false);
+        setChargingOn(false);
+        setScreenOn(false);
+        setEmergencyCallActive(true);
+
+        verifyStateConditions(STATE_ACTIVE);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+
+        setEmergencyCallActive(false);
+
+        verifyStateConditions(STATE_INACTIVE);
+        verifyLightStateConditions(LIGHT_STATE_INACTIVE);
+    }
+
     ///////////////// EXIT conditions ///////////////////
 
     @Test
@@ -2096,6 +2154,75 @@
                 .onDeviceStationaryChanged(eq(true));
     }
 
+    @Test
+    public void testEmergencyEndsIdle() {
+        enterDeepState(STATE_ACTIVE);
+        setEmergencyCallActive(true);
+        verifyStateConditions(STATE_ACTIVE);
+
+        enterDeepState(STATE_INACTIVE);
+        setEmergencyCallActive(true);
+        verifyStateConditions(STATE_ACTIVE);
+
+        enterDeepState(STATE_IDLE_PENDING);
+        setEmergencyCallActive(true);
+        verifyStateConditions(STATE_ACTIVE);
+
+        enterDeepState(STATE_SENSING);
+        setEmergencyCallActive(true);
+        verifyStateConditions(STATE_ACTIVE);
+
+        enterDeepState(STATE_LOCATING);
+        setEmergencyCallActive(true);
+        verifyStateConditions(STATE_ACTIVE);
+
+        // Quick doze enabled or not shouldn't affect the end state.
+        enterDeepState(STATE_QUICK_DOZE_DELAY);
+        setQuickDozeEnabled(true);
+        setEmergencyCallActive(true);
+        verifyStateConditions(STATE_ACTIVE);
+
+        enterDeepState(STATE_QUICK_DOZE_DELAY);
+        setQuickDozeEnabled(false);
+        setEmergencyCallActive(true);
+        verifyStateConditions(STATE_ACTIVE);
+
+        enterDeepState(STATE_IDLE);
+        setEmergencyCallActive(true);
+        verifyStateConditions(STATE_ACTIVE);
+
+        enterDeepState(STATE_IDLE_MAINTENANCE);
+        setEmergencyCallActive(true);
+        verifyStateConditions(STATE_ACTIVE);
+    }
+
+    @Test
+    public void testEmergencyEndsLightIdle() {
+        enterLightState(LIGHT_STATE_ACTIVE);
+        setEmergencyCallActive(true);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+
+        enterLightState(LIGHT_STATE_INACTIVE);
+        setEmergencyCallActive(true);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+
+        enterLightState(LIGHT_STATE_WAITING_FOR_NETWORK);
+        setEmergencyCallActive(true);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+
+        enterLightState(LIGHT_STATE_IDLE);
+        setEmergencyCallActive(true);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+
+        enterLightState(LIGHT_STATE_IDLE_MAINTENANCE);
+        setEmergencyCallActive(true);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+
+        enterLightState(LIGHT_STATE_OVERRIDE);
+        setEmergencyCallActive(true);
+        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
+    }
+
     private void enterDeepState(int state) {
         switch (state) {
             case STATE_ACTIVE:
@@ -2108,6 +2235,7 @@
                 setQuickDozeEnabled(true);
                 setScreenOn(false);
                 setChargingOn(false);
+                setEmergencyCallActive(false);
                 mDeviceIdleController.becomeInactiveIfAppropriateLocked();
                 break;
             case STATE_LOCATING:
@@ -2128,6 +2256,7 @@
                 setQuickDozeEnabled(false);
                 setScreenOn(false);
                 setChargingOn(false);
+                setEmergencyCallActive(false);
                 mDeviceIdleController.becomeInactiveIfAppropriateLocked();
                 int count = 0;
                 while (mDeviceIdleController.getState() != state) {
@@ -2159,6 +2288,7 @@
                 enterLightState(LIGHT_STATE_ACTIVE);
                 setScreenOn(false);
                 setChargingOn(false);
+                setEmergencyCallActive(false);
                 int count = 0;
                 mDeviceIdleController.becomeInactiveIfAppropriateLocked();
                 while (mDeviceIdleController.getLightState() != lightState) {
@@ -2177,6 +2307,7 @@
             case LIGHT_STATE_OVERRIDE:
                 setScreenOn(false);
                 setChargingOn(false);
+                setEmergencyCallActive(false);
                 mDeviceIdleController.setLightStateForTest(lightState);
                 break;
             default:
@@ -2188,6 +2319,14 @@
         mDeviceIdleController.updateChargingLocked(on);
     }
 
+    private void setEmergencyCallActive(boolean active) {
+        if (active) {
+            mEmergencyCallListener.onOutgoingEmergencyCall(mock(EmergencyNumber.class), 0);
+        } else {
+            mCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_IDLE);
+        }
+    }
+
     private void setScreenLocked(boolean locked) {
         mDeviceIdleController.keyguardShowingLocked(locked);
     }
@@ -2235,6 +2374,7 @@
                 assertFalse(mDeviceIdleController.isCharging());
                 assertFalse(mDeviceIdleController.isScreenOn()
                         && !mDeviceIdleController.isKeyguardShowing());
+                assertFalse(mDeviceIdleController.isEmergencyCallActive());
                 break;
             case STATE_IDLE_PENDING:
                 assertEquals(
@@ -2244,6 +2384,7 @@
                 assertFalse(mDeviceIdleController.isCharging());
                 assertFalse(mDeviceIdleController.isScreenOn()
                         && !mDeviceIdleController.isKeyguardShowing());
+                assertFalse(mDeviceIdleController.isEmergencyCallActive());
                 break;
             case STATE_SENSING:
                 assertEquals(
@@ -2255,6 +2396,7 @@
                 assertFalse(mDeviceIdleController.isCharging());
                 assertFalse(mDeviceIdleController.isScreenOn()
                         && !mDeviceIdleController.isKeyguardShowing());
+                assertFalse(mDeviceIdleController.isEmergencyCallActive());
                 break;
             case STATE_LOCATING:
                 assertEquals(
@@ -2263,6 +2405,7 @@
                 assertFalse(mDeviceIdleController.isCharging());
                 assertFalse(mDeviceIdleController.isScreenOn()
                         && !mDeviceIdleController.isKeyguardShowing());
+                assertFalse(mDeviceIdleController.isEmergencyCallActive());
                 break;
             case STATE_IDLE:
                 if (mDeviceIdleController.hasMotionSensor()) {
@@ -2276,6 +2419,7 @@
                         && !mDeviceIdleController.isKeyguardShowing());
                 // Light state should be OVERRIDE at this point.
                 verifyLightStateConditions(LIGHT_STATE_OVERRIDE);
+                assertFalse(mDeviceIdleController.isEmergencyCallActive());
                 break;
             case STATE_IDLE_MAINTENANCE:
                 if (mDeviceIdleController.hasMotionSensor()) {
@@ -2287,6 +2431,7 @@
                 assertFalse(mDeviceIdleController.isCharging());
                 assertFalse(mDeviceIdleController.isScreenOn()
                         && !mDeviceIdleController.isKeyguardShowing());
+                assertFalse(mDeviceIdleController.isEmergencyCallActive());
                 break;
             case STATE_QUICK_DOZE_DELAY:
                 // If quick doze is enabled, the motion listener should NOT be active.
@@ -2295,6 +2440,7 @@
                 assertFalse(mDeviceIdleController.isCharging());
                 assertFalse(mDeviceIdleController.isScreenOn()
                         && !mDeviceIdleController.isKeyguardShowing());
+                assertFalse(mDeviceIdleController.isEmergencyCallActive());
                 break;
             default:
                 fail("Conditions for " + stateToString(expectedState) + " unknown.");
@@ -2312,6 +2458,7 @@
             case LIGHT_STATE_ACTIVE:
                 assertTrue(
                         mDeviceIdleController.isCharging() || mDeviceIdleController.isScreenOn()
+                                || mDeviceIdleController.isEmergencyCallActive()
                                 // Or there's an alarm coming up soon.
                                 || SystemClock.elapsedRealtime() + mConstants.MIN_TIME_TO_ALARM
                                 > mAlarmManager.getNextWakeFromIdleTime());
@@ -2324,6 +2471,7 @@
                 assertFalse(mDeviceIdleController.isCharging());
                 assertFalse(mDeviceIdleController.isScreenOn()
                         && !mDeviceIdleController.isKeyguardShowing());
+                assertFalse(mDeviceIdleController.isEmergencyCallActive());
                 break;
             default:
                 fail("Conditions for " + lightStateToString(expectedLightState) + " unknown.");