diff --git a/core/api/test-current.txt b/core/api/test-current.txt index f6b437405049c..0732c61364016 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1133,10 +1133,10 @@ package android.hardware.camera2 { package android.hardware.devicestate { public final class DeviceStateManager { - method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest); + method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest); method @NonNull public int[] getSupportedStates(); method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback); - method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback); + method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback); method public void unregisterCallback(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback); field public static final int MAXIMUM_DEVICE_STATE = 255; // 0xff field public static final int MINIMUM_DEVICE_STATE = 0; // 0x0 diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java index bb3bffdbe6783..95892aa7af813 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManager.java +++ b/core/java/android/hardware/devicestate/DeviceStateManager.java @@ -86,12 +86,13 @@ public final class DeviceStateManager { * However, this behavior can be changed by setting flags on the {@link DeviceStateRequest}. * * @throws IllegalArgumentException if the requested state is unsupported. - * @throws SecurityException if the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} - * permission is not held. + * @throws SecurityException if the caller is neither the current top-focused activity nor if + * the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held. * * @see DeviceStateRequest */ - @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) + @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE, + conditional = true) public void requestState(@NonNull DeviceStateRequest request, @Nullable @CallbackExecutor Executor executor, @Nullable DeviceStateRequest.Callback callback) { @@ -105,10 +106,11 @@ public final class DeviceStateManager { * This method is noop if the {@code request} has not been submitted with a call to * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. * - * @throws SecurityException if the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} - * permission is not held. + * @throws SecurityException if the caller is neither the current top-focused activity nor if + * the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held. */ - @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) + @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE, + conditional = true) public void cancelRequest(@NonNull DeviceStateRequest request) { mGlobal.cancelRequest(request); } diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index b0c36aac73d80..383b3928e5e60 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -48,9 +48,12 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; +import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemService; import com.android.server.policy.DeviceStatePolicyImpl; +import com.android.server.wm.ActivityTaskManagerInternal; +import com.android.server.wm.WindowProcessController; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -101,6 +104,9 @@ public final class DeviceStateManagerService extends SystemService { private final BinderService mBinderService; @NonNull private final OverrideRequestController mOverrideRequestController; + @VisibleForTesting + @NonNull + public ActivityTaskManagerInternal mActivityTaskManagerInternal; // All supported device states keyed by identifier. @GuardedBy("mLock") @@ -153,6 +159,7 @@ public final class DeviceStateManagerService extends SystemService { mDeviceStatePolicy = policy; mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener()); mBinderService = new BinderService(); + mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); } @Override @@ -778,14 +785,21 @@ public final class DeviceStateManagerService extends SystemService { @Override // Binder call public void requestState(IBinder token, int state, int flags) { - getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, - "Permission required to request device state."); + final int callingPid = Binder.getCallingPid(); + // 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 + final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp(); + if (topApp.getPid() != callingPid) { + getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, + "Permission required to request device state, " + + "or the call must come from the top focused app."); + } if (token == null) { throw new IllegalArgumentException("Request token must not be null."); } - final int callingPid = Binder.getCallingPid(); final long callingIdentity = Binder.clearCallingIdentity(); try { requestStateInternal(state, flags, callingPid, token); @@ -796,14 +810,21 @@ public final class DeviceStateManagerService extends SystemService { @Override // Binder call public void cancelRequest(IBinder token) { - getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, - "Permission required to clear requested device state."); + final int callingPid = Binder.getCallingPid(); + // 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 + final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp(); + if (topApp.getPid() != callingPid) { + getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, + "Permission required to cancel device state, " + + "or the call must come from the top focused app."); + } if (token == null) { throw new IllegalArgumentException("Request token must not be null."); } - final int callingPid = Binder.getCallingPid(); final long callingIdentity = Binder.clearCallingIdentity(); try { cancelRequestInternal(callingPid, token); 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 c8f81742822f6..b1b6e5341f38e 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java @@ -18,6 +18,7 @@ package com.android.server.devicestate; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; +import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; @@ -35,6 +36,11 @@ import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import static org.mockito.Mockito.mock; + +import com.android.server.wm.ActivityTaskManagerInternal; +import com.android.server.wm.WindowProcessController; + import junit.framework.Assert; import org.junit.Before; @@ -63,6 +69,8 @@ public final class DeviceStateManagerServiceTest { private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(255, "UNSUPPORTED", 0 /* flags */); + private static final int FAKE_PROCESS_ID = 100; + private TestDeviceStatePolicy mPolicy; private TestDeviceStateProvider mProvider; private DeviceStateManagerService mService; @@ -72,6 +80,14 @@ public final class DeviceStateManagerServiceTest { mProvider = new TestDeviceStateProvider(); mPolicy = new TestDeviceStatePolicy(mProvider); mService = new DeviceStateManagerService(InstrumentationRegistry.getContext(), mPolicy); + + // Necessary to allow us to check for top app process id in tests + mService.mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class); + WindowProcessController windowProcessController = mock(WindowProcessController.class); + when(mService.mActivityTaskManagerInternal.getTopApp()) + .thenReturn(windowProcessController); + when(windowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID); + flushHandler(); // Flush the handler to ensure the initial values are committed. }