diff options
6 files changed, 244 insertions, 16 deletions
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java index 42fe9d88825b..6baa1bd2ae20 100644 --- a/services/core/java/com/android/server/devicestate/DeviceState.java +++ b/services/core/java/com/android/server/devicestate/DeviceState.java @@ -64,10 +64,20 @@ public final class DeviceState { */ public static final int FLAG_EMULATED_ONLY = 1 << 2; + /** + * This flag indicates that the corresponding state should be automatically canceled when the + * requesting app is no longer on top. The app is considered not on top when (1) the top + * activity in the system is from a different app, (2) the device is in sleep mode, or + * (3) the keyguard shows up. + */ + public static final int FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP = 1 << 3; + /** @hide */ @IntDef(prefix = {"FLAG_"}, flag = true, value = { FLAG_CANCEL_OVERRIDE_REQUESTS, - FLAG_APP_INACCESSIBLE + FLAG_APP_INACCESSIBLE, + FLAG_EMULATED_ONLY, + FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP }) @Retention(RetentionPolicy.SOURCE) public @interface DeviceStateFlags {} diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index c856cabdc967..064cd2d50161 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -33,6 +33,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.TaskStackListener; import android.content.Context; import android.hardware.devicestate.DeviceStateInfo; import android.hardware.devicestate.DeviceStateManager; @@ -176,6 +177,12 @@ public final class DeviceStateManagerService extends SystemService { @NonNull private final SystemPropertySetter mSystemPropertySetter; + @VisibleForTesting + TaskStackListener mOverrideRequestTaskStackListener = new OverrideRequestTaskStackListener(); + @VisibleForTesting + ActivityTaskManagerInternal.ScreenObserver mOverrideRequestScreenObserver = + new OverrideRequestScreenObserver(); + public DeviceStateManagerService(@NonNull Context context) { this(context, DeviceStatePolicy.Provider .fromResources(context.getResources()) @@ -215,6 +222,9 @@ public final class DeviceStateManagerService extends SystemService { readStatesAvailableForRequestFromApps(); mFoldedDeviceStates = readFoldedStates(); } + + mActivityTaskManagerInternal.registerTaskStackListener(mOverrideRequestTaskStackListener); + mActivityTaskManagerInternal.registerScreenObserver(mOverrideRequestScreenObserver); } @VisibleForTesting @@ -842,9 +852,7 @@ public final class DeviceStateManagerService extends SystemService { * @param state state that is being requested. */ private void assertCanRequestDeviceState(int callingPid, int state) { - final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp(); - if (topApp == null || topApp.getPid() != callingPid - || !isStateAvailableForAppRequests(state)) { + if (!isTopApp(callingPid) || !isStateAvailableForAppRequests(state)) { getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, "Permission required to request device state, " + "or the call must come from the top app " @@ -859,14 +867,18 @@ public final class DeviceStateManagerService extends SystemService { * @param callingPid Process ID that is requesting this state change */ private void assertCanControlDeviceState(int callingPid) { - final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp(); - if (topApp == null || topApp.getPid() != callingPid) { + if (!isTopApp(callingPid)) { getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, "Permission required to request device state, " + "or the call must come from the top app."); } } + private boolean isTopApp(int callingPid) { + final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp(); + return topApp != null && topApp.getPid() == callingPid; + } + private boolean isStateAvailableForAppRequests(int state) { synchronized (mLock) { return mDeviceStatesAvailableForAppRequests.contains(state); @@ -1185,4 +1197,52 @@ public final class DeviceStateManagerService extends SystemService { } } } + + @GuardedBy("mLock") + private boolean shouldCancelOverrideRequestWhenRequesterNotOnTop() { + if (mActiveOverride.isEmpty()) { + return false; + } + int identifier = mActiveOverride.get().getRequestedState(); + DeviceState deviceState = mDeviceStates.get(identifier); + return deviceState.hasFlag(DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP); + } + + private class OverrideRequestTaskStackListener extends TaskStackListener { + @Override + public void onTaskStackChanged() throws RemoteException { + synchronized (mLock) { + if (!shouldCancelOverrideRequestWhenRequesterNotOnTop()) { + return; + } + + OverrideRequest request = mActiveOverride.get(); + if (!isTopApp(request.getPid())) { + mOverrideRequestController.cancelRequest(request); + } + } + } + } + + private class OverrideRequestScreenObserver implements + ActivityTaskManagerInternal.ScreenObserver { + + @Override + public void onAwakeStateChanged(boolean isAwake) { + synchronized (mLock) { + if (!isAwake && shouldCancelOverrideRequestWhenRequesterNotOnTop()) { + mOverrideRequestController.cancelRequest(mActiveOverride.get()); + } + } + } + + @Override + public void onKeyguardStateChanged(boolean isShowing) { + synchronized (mLock) { + if (isShowing && shouldCancelOverrideRequestWhenRequesterNotOnTop()) { + mOverrideRequestController.cancelRequest(mActiveOverride.get()); + } + } + } + } } diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java index 91bb677524cf..f6497d7f8091 100644 --- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java +++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java @@ -97,6 +97,8 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, private static final String FLAG_CANCEL_OVERRIDE_REQUESTS = "FLAG_CANCEL_OVERRIDE_REQUESTS"; private static final String FLAG_APP_INACCESSIBLE = "FLAG_APP_INACCESSIBLE"; private static final String FLAG_EMULATED_ONLY = "FLAG_EMULATED_ONLY"; + private static final String FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP = + "FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP"; /** Interface that allows reading the device state configuration. */ interface ReadableConfig { @@ -152,6 +154,10 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, break; case FLAG_EMULATED_ONLY: flags |= DeviceState.FLAG_EMULATED_ONLY; + break; + case FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP: + flags |= DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP; + break; default: Slog.w(TAG, "Parsed unknown flag with name: " + configFlagString); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index bd4f1a6894ec..2bd905230b69 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -24,6 +24,7 @@ import android.app.AppProtoEnums; import android.app.BackgroundStartPrivileges; import android.app.IActivityManager; import android.app.IApplicationThread; +import android.app.ITaskStackListener; import android.app.ProfilerInfo; import android.content.ComponentName; import android.content.IIntentSender; @@ -740,4 +741,10 @@ public abstract class ActivityTaskManagerInternal { */ public abstract void restartTaskActivityProcessIfVisible( int taskId, @NonNull String packageName); + + /** Sets the task stack listener that gets callbacks when a task stack changes. */ + public abstract void registerTaskStackListener(ITaskStackListener listener); + + /** Unregister a task stack listener so that it stops receiving callbacks. */; + public abstract void unregisterTaskStackListener(ITaskStackListener listener); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 6fe77b7614dc..a927ed3e9e23 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -6916,5 +6916,17 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { activity.restartProcessIfVisible(); } } + + /** Sets the task stack listener that gets callbacks when a task stack changes. */ + @Override + public void registerTaskStackListener(ITaskStackListener listener) { + ActivityTaskManagerService.this.registerTaskStackListener(listener); + } + + /** Unregister a task stack listener so that it stops receiving callbacks. */ + @Override + public void unregisterTaskStackListener(ITaskStackListener listener) { + ActivityTaskManagerService.this.unregisterTaskStackListener(listener); + } } } diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java index a45144e23c17..82f64933c282 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java @@ -67,6 +67,9 @@ public final class DeviceStateManagerServiceTest { new DeviceState(0, "DEFAULT", 0 /* flags */); private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(1, "OTHER", 0 /* flags */); + private static final DeviceState DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP = + new DeviceState(2, "DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP", + DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP /* flags */); // A device state that is not reported as being supported for the default test provider. private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(255, "UNSUPPORTED", 0 /* flags */); @@ -77,6 +80,7 @@ public final class DeviceStateManagerServiceTest { private TestDeviceStateProvider mProvider; private DeviceStateManagerService mService; private TestSystemPropertySetter mSysPropSetter; + private WindowProcessController mWindowProcessController; @Before public void setup() { @@ -88,10 +92,10 @@ public final class DeviceStateManagerServiceTest { // Necessary to allow us to check for top app process id in tests mService.mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class); - WindowProcessController windowProcessController = mock(WindowProcessController.class); + mWindowProcessController = mock(WindowProcessController.class); when(mService.mActivityTaskManagerInternal.getTopApp()) - .thenReturn(windowProcessController); - when(windowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID); + .thenReturn(mWindowProcessController); + when(mWindowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID); flushHandler(); // Flush the handler to ensure the initial values are committed. } @@ -201,7 +205,7 @@ public final class DeviceStateManagerServiceTest { DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName()); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE, - OTHER_DEVICE_STATE); + OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP); mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE}); flushHandler(); @@ -234,10 +238,10 @@ public final class DeviceStateManagerServiceTest { DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName()); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE, - OTHER_DEVICE_STATE); + OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP); mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE, - OTHER_DEVICE_STATE}); + OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP}); flushHandler(); // The current committed and requests states do not change because the current state remains @@ -248,7 +252,7 @@ public final class DeviceStateManagerServiceTest { DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName()); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE, - OTHER_DEVICE_STATE); + OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP); // The callback wasn't notified about a change in supported states as the states have not // changed. @@ -261,7 +265,8 @@ public final class DeviceStateManagerServiceTest { assertNotNull(info); assertArrayEquals(info.supportedStates, new int[] { DEFAULT_DEVICE_STATE.getIdentifier(), - OTHER_DEVICE_STATE.getIdentifier() }); + OTHER_DEVICE_STATE.getIdentifier(), + DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP.getIdentifier()}); assertEquals(info.baseState, DEFAULT_DEVICE_STATE.getIdentifier()); assertEquals(info.currentState, DEFAULT_DEVICE_STATE.getIdentifier()); } @@ -513,6 +518,54 @@ public final class DeviceStateManagerServiceTest { OTHER_DEVICE_STATE.getIdentifier()); } + @Test + public void requestState_flagCancelWhenRequesterNotOnTop_onDeviceSleep() + throws RemoteException { + requestState_flagCancelWhenRequesterNotOnTop_common( + // When the device is awake, the state should not change + () -> mService.mOverrideRequestScreenObserver.onAwakeStateChanged(true), + // When the device is in sleep mode, the state should be canceled + () -> mService.mOverrideRequestScreenObserver.onAwakeStateChanged(false) + ); + } + + @Test + public void requestState_flagCancelWhenRequesterNotOnTop_onKeyguardShow() + throws RemoteException { + requestState_flagCancelWhenRequesterNotOnTop_common( + // When the keyguard is not showing, the state should not change + () -> mService.mOverrideRequestScreenObserver.onKeyguardStateChanged(false), + // When the keyguard is showing, the state should be canceled + () -> mService.mOverrideRequestScreenObserver.onKeyguardStateChanged(true) + ); + } + + @Test + public void requestState_flagCancelWhenRequesterNotOnTop_onTaskStackChanged() + throws RemoteException { + requestState_flagCancelWhenRequesterNotOnTop_common( + // When the app is foreground, the state should not change + () -> { + int pid = Binder.getCallingPid(); + when(mWindowProcessController.getPid()).thenReturn(pid); + try { + mService.mOverrideRequestTaskStackListener.onTaskStackChanged(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + }, + // When the app is not foreground, the state should change + () -> { + when(mWindowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID); + try { + mService.mOverrideRequestTaskStackListener.onTaskStackChanged(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + ); + } + @FlakyTest(bugId = 200332057) @Test public void requestState_becomesUnsupported() throws RemoteException { @@ -743,6 +796,84 @@ public final class DeviceStateManagerServiceTest { Assert.assertTrue(Arrays.equals(expected, actual)); } + /** + * Common code to verify the handling of FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP flag. + * + * The device state with FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP should be automatically canceled + * when certain events happen, e.g. when the top activity belongs to another app or when the + * device goes into the sleep mode. + * + * @param noChangeEvent an event that should not trigger auto cancellation of the state. + * @param autoCancelEvent an event that should trigger auto cancellation of the state. + * @throws RemoteException when the service throws exceptions. + */ + private void requestState_flagCancelWhenRequesterNotOnTop_common( + Runnable noChangeEvent, + Runnable autoCancelEvent + ) throws RemoteException { + TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); + mService.getBinderService().registerCallback(callback); + flushHandler(); + + final IBinder token = new Binder(); + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_UNKNOWN); + + mService.getBinderService().requestState(token, + DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP.getIdentifier(), + 0 /* flags */); + flushHandler(2 /* count */); + + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_ACTIVE); + + // Committed state changes as there is a requested override. + assertDeviceStateConditions( + DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP, + DEFAULT_DEVICE_STATE, /* base state */ + true /* isOverrideState */); + + noChangeEvent.run(); + flushHandler(); + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_ACTIVE); + assertDeviceStateConditions( + DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP, + DEFAULT_DEVICE_STATE, /* base state */ + true /* isOverrideState */); + + autoCancelEvent.run(); + flushHandler(); + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_CANCELED); + assertDeviceStateConditions(DEFAULT_DEVICE_STATE, DEFAULT_DEVICE_STATE, + false /* isOverrideState */); + } + + /** + * Verify that the current device state and base state match the expected values. + * + * @param state the expected committed state. + * @param baseState the expected base state. + * @param isOverrideState whether a state override is active. + */ + private void assertDeviceStateConditions( + DeviceState state, DeviceState baseState, boolean isOverrideState) { + assertEquals(mService.getCommittedState(), Optional.of(state)); + assertEquals(mService.getBaseState(), Optional.of(baseState)); + assertEquals(mSysPropSetter.getValue(), + state.getIdentifier() + ":" + state.getName()); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + state.getIdentifier()); + if (isOverrideState) { + // When a state override is active, the committed state should batch the override state. + assertEquals(mService.getOverrideState().get(), state); + } else { + // When there is no state override, the override state should be empty. + assertFalse(mService.getOverrideState().isPresent()); + } + } + private static final class TestDeviceStatePolicy extends DeviceStatePolicy { private final DeviceStateProvider mProvider; private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE; @@ -801,8 +932,10 @@ public final class DeviceStateManagerServiceTest { } private static final class TestDeviceStateProvider implements DeviceStateProvider { - private DeviceState[] mSupportedDeviceStates = new DeviceState[]{ DEFAULT_DEVICE_STATE, - OTHER_DEVICE_STATE }; + private DeviceState[] mSupportedDeviceStates = new DeviceState[]{ + DEFAULT_DEVICE_STATE, + OTHER_DEVICE_STATE, + DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP}; private Listener mListener; @Override |