Merge "Cancel state override request when app goes into background"
This commit is contained in:
@@ -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 {}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user