From 27ed45f3a629fad1b9d3b347484c169474bdee82 Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Wed, 1 Mar 2023 16:23:21 +0000 Subject: [PATCH] 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 --- .../android/server/DeviceIdleController.java | 90 ++++++++++- .../server/DeviceIdleControllerTest.java | 148 ++++++++++++++++++ 2 files changed, 232 insertions(+), 6 deletions(-) diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 4caaa09f6a31b..79a26597ed244 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.SystemClock; 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 @@ import java.util.stream.Collectors; 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 @@ import java.util.stream.Collectors; 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 @@ public class DeviceIdleController extends SystemService 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 @@ public class DeviceIdleController extends SystemService } }; + 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 @@ public class DeviceIdleController extends SystemService } } + 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 @@ public class DeviceIdleController extends SystemService 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 @@ public class DeviceIdleController extends SystemService mLocalActivityTaskManager.registerScreenObserver(mScreenObserver); + mInjector.getTelephonyManager().registerTelephonyCallback( + JobSchedulerBackgroundThread.getExecutor(), mEmergencyCallListener); + passWhiteListsToForceAppStandbyTrackerLocked(); updateInteractivityLocked(); } @@ -3435,6 +3484,7 @@ public class DeviceIdleController extends SystemService 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 @@ public class DeviceIdleController extends SystemService + ", 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 @@ public class DeviceIdleController extends SystemService } 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 @@ public class DeviceIdleController extends SystemService 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 @@ public class DeviceIdleController extends SystemService } } + @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 bcd69fda8c286..85820127d36c8 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.PowerManagerInternal; 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 @@ public class DeviceIdleControllerTest { 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 @@ public class DeviceIdleControllerTest { private Sensor mMotionSensor; @Mock private SensorManager mSensorManager; + @Mock + private TelephonyManager mTelephonyManager; class InjectorForTest extends DeviceIdleController.Injector { ConnectivityManager connectivityManager; @@ -231,6 +238,11 @@ public class DeviceIdleControllerTest { return constraintController; } + @Override + TelephonyManager getTelephonyManager() { + return mTelephonyManager; + } + @Override boolean useMotionSensor() { return true; @@ -343,6 +355,15 @@ public class DeviceIdleControllerTest { // Get the same Constants object that mDeviceIdleController got. mConstants = mInjector.getConstants(mDeviceIdleController); + + final ArgumentCaptor 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 @@ public class DeviceIdleControllerTest { 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 @@ public class DeviceIdleControllerTest { 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 @@ public class DeviceIdleControllerTest { setAlarmSoon(false); setChargingOn(false); setScreenOn(false); + setEmergencyCallActive(false); mDeviceIdleController.becomeInactiveIfAppropriateLocked(); verifyStateConditions(STATE_INACTIVE); @@ -613,6 +654,7 @@ public class DeviceIdleControllerTest { setChargingOn(false); setScreenOn(false); + setEmergencyCallActive(false); mDeviceIdleController.becomeInactiveIfAppropriateLocked(); verifyLightStateConditions(LIGHT_STATE_INACTIVE); @@ -1147,6 +1189,22 @@ public class DeviceIdleControllerTest { 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 @@ public class DeviceIdleControllerTest { .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 @@ public class DeviceIdleControllerTest { setQuickDozeEnabled(true); setScreenOn(false); setChargingOn(false); + setEmergencyCallActive(false); mDeviceIdleController.becomeInactiveIfAppropriateLocked(); break; case STATE_LOCATING: @@ -2128,6 +2256,7 @@ public class DeviceIdleControllerTest { setQuickDozeEnabled(false); setScreenOn(false); setChargingOn(false); + setEmergencyCallActive(false); mDeviceIdleController.becomeInactiveIfAppropriateLocked(); int count = 0; while (mDeviceIdleController.getState() != state) { @@ -2159,6 +2288,7 @@ public class DeviceIdleControllerTest { enterLightState(LIGHT_STATE_ACTIVE); setScreenOn(false); setChargingOn(false); + setEmergencyCallActive(false); int count = 0; mDeviceIdleController.becomeInactiveIfAppropriateLocked(); while (mDeviceIdleController.getLightState() != lightState) { @@ -2177,6 +2307,7 @@ public class DeviceIdleControllerTest { case LIGHT_STATE_OVERRIDE: setScreenOn(false); setChargingOn(false); + setEmergencyCallActive(false); mDeviceIdleController.setLightStateForTest(lightState); break; default: @@ -2188,6 +2319,14 @@ public class DeviceIdleControllerTest { 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 @@ public class DeviceIdleControllerTest { assertFalse(mDeviceIdleController.isCharging()); assertFalse(mDeviceIdleController.isScreenOn() && !mDeviceIdleController.isKeyguardShowing()); + assertFalse(mDeviceIdleController.isEmergencyCallActive()); break; case STATE_IDLE_PENDING: assertEquals( @@ -2244,6 +2384,7 @@ public class DeviceIdleControllerTest { assertFalse(mDeviceIdleController.isCharging()); assertFalse(mDeviceIdleController.isScreenOn() && !mDeviceIdleController.isKeyguardShowing()); + assertFalse(mDeviceIdleController.isEmergencyCallActive()); break; case STATE_SENSING: assertEquals( @@ -2255,6 +2396,7 @@ public class DeviceIdleControllerTest { assertFalse(mDeviceIdleController.isCharging()); assertFalse(mDeviceIdleController.isScreenOn() && !mDeviceIdleController.isKeyguardShowing()); + assertFalse(mDeviceIdleController.isEmergencyCallActive()); break; case STATE_LOCATING: assertEquals( @@ -2263,6 +2405,7 @@ public class DeviceIdleControllerTest { assertFalse(mDeviceIdleController.isCharging()); assertFalse(mDeviceIdleController.isScreenOn() && !mDeviceIdleController.isKeyguardShowing()); + assertFalse(mDeviceIdleController.isEmergencyCallActive()); break; case STATE_IDLE: if (mDeviceIdleController.hasMotionSensor()) { @@ -2276,6 +2419,7 @@ public class DeviceIdleControllerTest { && !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 @@ public class DeviceIdleControllerTest { 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 @@ public class DeviceIdleControllerTest { assertFalse(mDeviceIdleController.isCharging()); assertFalse(mDeviceIdleController.isScreenOn() && !mDeviceIdleController.isKeyguardShowing()); + assertFalse(mDeviceIdleController.isEmergencyCallActive()); break; default: fail("Conditions for " + stateToString(expectedState) + " unknown."); @@ -2312,6 +2458,7 @@ public class DeviceIdleControllerTest { 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 @@ public class DeviceIdleControllerTest { assertFalse(mDeviceIdleController.isCharging()); assertFalse(mDeviceIdleController.isScreenOn() && !mDeviceIdleController.isKeyguardShowing()); + assertFalse(mDeviceIdleController.isEmergencyCallActive()); break; default: fail("Conditions for " + lightStateToString(expectedLightState) + " unknown.");