summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceState.java12
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStateManagerService.java70
-rw-r--r--services/core/java/com/android/server/policy/DeviceStateProviderImpl.java6
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java153
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