Merge "Cancel state override request when app goes into background"

This commit is contained in:
Jiaming Liu
2023-01-19 04:11:27 +00:00
committed by Android (Google) Code Review
6 changed files with 244 additions and 16 deletions

View File

@@ -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 {}

View File

@@ -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());
}
}
}
}
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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