Allows an app to cancel a device state request they made at any point.

Removes the requirement for an application to have to be foregrounded
to cancel a device state request, provided that the process and user
id match the id's that made the initial request.

Bug: 350930205
Bug: 354772125
Test: DeviceStateManagerTests
Test: ExtensionRearDisplayPresentationTest
Flag: android.hardware.devicestate.feature.flags.device_state_requester_cancel_state
Change-Id: I86ab874f8d5d14bbdbd8cf30310b9896d41a8246
diff --git a/core/java/android/hardware/devicestate/feature/flags.aconfig b/core/java/android/hardware/devicestate/feature/flags.aconfig
index a09c84d..98ba9192 100644
--- a/core/java/android/hardware/devicestate/feature/flags.aconfig
+++ b/core/java/android/hardware/devicestate/feature/flags.aconfig
@@ -17,4 +17,16 @@
     description: "Client migration to updated DeviceStateManager API's"
     bug: "336640888"
     is_fixed_read_only: true
+}
+
+flag {
+    name: "device_state_requester_cancel_state"
+    is_exported: true
+    namespace: "windowing_sdk"
+    description: "Removes foreground requirement if process attempting to cancel a state request is the requester"
+    bug: "354772125"
+    is_fixed_read_only: true
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 619aecf..38e6d82 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -50,8 +50,7 @@
 import android.hardware.devicestate.DeviceStateManagerInternal;
 import android.hardware.devicestate.IDeviceStateManager;
 import android.hardware.devicestate.IDeviceStateManagerCallback;
-import android.hardware.devicestate.feature.flags.FeatureFlags;
-import android.hardware.devicestate.feature.flags.FeatureFlagsImpl;
+import android.hardware.devicestate.feature.flags.Flags;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -190,9 +189,6 @@
     @Nullable
     private OverrideRequest mRearDisplayPendingOverrideRequest;
 
-    @NonNull
-    private final FeatureFlags mFlags;
-
     @VisibleForTesting
     interface SystemPropertySetter {
         void setDebugTracingDeviceStateProperty(String value);
@@ -253,7 +249,6 @@
             @NonNull SystemPropertySetter systemPropertySetter) {
         super(context);
         mSystemPropertySetter = systemPropertySetter;
-        mFlags = new FeatureFlagsImpl();
         // We use the DisplayThread because this service indirectly drives
         // display (on/off) and window (position) events through its callbacks.
         DisplayThread displayThread = DisplayThread.get();
@@ -279,7 +274,7 @@
         publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService);
         publishLocalService(DeviceStateManagerInternal.class, new LocalService());
 
-        if (!mFlags.deviceStatePropertyMigration()) {
+        if (!Flags.deviceStatePropertyMigration()) {
             synchronized (mLock) {
                 readStatesAvailableForRequestFromApps();
                 mFoldedDeviceStates = readFoldedStates();
@@ -848,7 +843,7 @@
             OverrideRequest request = new OverrideRequest(token, callingPid, callingUid,
                     deviceState.get(), flags, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
-            if (mFlags.deviceStatePropertyMigration()) {
+            if (Flags.deviceStatePropertyMigration()) {
                 // If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay
                 if (!hasControlDeviceStatePermission && deviceState.get().hasProperty(
                         PROPERTY_FEATURE_REAR_DISPLAY)) {
@@ -988,16 +983,16 @@
      * @param callingPid Process ID that is requesting this state change
      * @param state state that is being requested.
      */
-    private void assertCanRequestDeviceState(int callingPid, int callingUid, int state) {
+    private void enforceRequestDeviceStatePermitted(int callingPid, int callingUid, int state) {
         final boolean isTopApp = isTopApp(callingPid);
         final boolean isForegroundApp = isForegroundApp(callingPid, callingUid);
         final boolean isStateAvailableForAppRequests = isStateAvailableForAppRequests(state);
 
-        final boolean canRequestState = isTopApp
+        final boolean isAllowedToRequestState = isTopApp
                 && isForegroundApp
                 && isStateAvailableForAppRequests;
 
-        if (!canRequestState) {
+        if (!isAllowedToRequestState) {
             getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
                     "Permission required to request device state, "
                             + "or the call must come from the top app "
@@ -1006,19 +1001,29 @@
     }
 
     /**
-     * Checks if the process can control the device state. If the calling process ID is
-     * not the top app, then check if this process holds the CONTROL_DEVICE_STATE permission.
+     * Checks if the process can cancel a device state request. If the calling process ID is not
+     * both the top app and foregrounded nor does the process ID and userID match the IDs that made
+     * the device state request, then check if this process holds the CONTROL_DEVICE_STATE
+     * permission.
      *
      * @param callingPid Process ID that is requesting this state change
      * @param callingUid UID that is requesting this state change
      */
-    private void assertCanControlDeviceState(int callingPid, int callingUid) {
+    private void enforceCancelDeviceStatePermitted(int callingPid, int callingUid) {
         final boolean isTopApp = isTopApp(callingPid);
         final boolean isForegroundApp = isForegroundApp(callingPid, callingUid);
 
-        final boolean canControlState = isTopApp && isForegroundApp;
+        boolean isAllowedToControlState = isTopApp && isForegroundApp;
 
-        if (!canControlState) {
+        if (Flags.deviceStateRequesterCancelState()) {
+            synchronized (mLock) {
+                isAllowedToControlState =
+                        isAllowedToControlState || doCallingIdsMatchOverrideRequestIdsLocked(
+                                callingPid, callingUid);
+            }
+        }
+
+        if (!isAllowedToControlState) {
             getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
                     "Permission required to request device state, "
                             + "or the call must come from the top app.");
@@ -1052,9 +1057,19 @@
         return topApp != null && topApp.getPid() == callingPid;
     }
 
+    /**
+     * Returns if the provided {@code callingPid} and {@code callingUid} match the same id's that
+     * requested the current device state override.
+     */
+    @GuardedBy("mLock")
+    private boolean doCallingIdsMatchOverrideRequestIdsLocked(int callingPid, int callingUid) {
+        OverrideRequest request = mActiveOverride.orElse(null);
+        return request != null && request.getPid() == callingPid && request.getUid() == callingUid;
+    }
+
     private boolean isStateAvailableForAppRequests(int state) {
         synchronized (mLock) {
-            if (mFlags.deviceStatePropertyMigration()) {
+            if (Flags.deviceStatePropertyMigration()) {
                 Optional<DeviceState> deviceState =  getStateLocked(state);
                 return deviceState.isPresent() && deviceState.get().hasProperty(
                         PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST);
@@ -1122,7 +1137,7 @@
      */
     @GuardedBy("mLock")
     private boolean isDeviceOpeningLocked(int newBaseState) {
-        if (mFlags.deviceStatePropertyMigration()) {
+        if (Flags.deviceStatePropertyMigration()) {
             final DeviceState currentBaseState = mBaseState.orElse(INVALID_DEVICE_STATE);
             final DeviceState newDeviceBaseState = getStateLocked(newBaseState).orElse(
                     INVALID_DEVICE_STATE);
@@ -1293,7 +1308,7 @@
             // Allow top processes to request a device state change
             // If the calling process ID is not the top app, then we check if this process
             // holds a permission to CONTROL_DEVICE_STATE
-            assertCanRequestDeviceState(callingPid, callingUid, state);
+            enforceRequestDeviceStatePermitted(callingPid, callingUid, state);
 
             if (token == null) {
                 throw new IllegalArgumentException("Request token must not be null.");
@@ -1318,7 +1333,7 @@
             // Allow top processes to cancel a device state change
             // If the calling process ID is not the top app, then we check if this process
             // holds a permission to CONTROL_DEVICE_STATE
-            assertCanControlDeviceState(callingPid, callingUid);
+            enforceCancelDeviceStatePermitted(callingPid, callingUid);
 
             final long callingIdentity = Binder.clearCallingIdentity();
             try {