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.");