diff options
11 files changed, 818 insertions, 84 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index d18a9c7da5a1..f5ee4672b560 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1155,9 +1155,11 @@ package android.hardware.camera2 { package android.hardware.devicestate { public final class DeviceStateManager { + method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelBaseStateOverride(); method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelStateRequest(); 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 requestBaseStateOverride(@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 diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java index 30aa4db938da..bdd45e6df448 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManager.java +++ b/core/java/android/hardware/devicestate/DeviceStateManager.java @@ -16,6 +16,7 @@ package android.hardware.devicestate; +import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; @@ -115,6 +116,52 @@ public final class DeviceStateManager { } /** + * Submits a {@link DeviceStateRequest request} to override the base state of the device. This + * should only be used for testing, where you want to simulate the physical change to the + * device state. + * <p> + * By default, the request is kept active until one of the following occurs: + * <ul> + * <li>The physical state of the device changes</li> + * <li>The system deems the request can no longer be honored, for example if the requested + * state becomes unsupported. + * <li>A call to {@link #cancelBaseStateOverride}. + * <li>Another processes submits a request succeeding this request in which case the request + * will be canceled. + * </ul> + * + * Submitting a base state override request may not cause any change in the presentation + * of the system if there is an emulated request made through {@link #requestState}, as the + * emulated override requests take priority. + * + * @throws IllegalArgumentException if the requested state is unsupported. + * @throws SecurityException if the caller does not hold the + * {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission. + * + * @see DeviceStateRequest + */ + @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) + public void requestBaseStateOverride(@NonNull DeviceStateRequest request, + @Nullable @CallbackExecutor Executor executor, + @Nullable DeviceStateRequest.Callback callback) { + mGlobal.requestBaseStateOverride(request, executor, callback); + } + + /** + * Cancels the active {@link DeviceStateRequest} previously submitted with a call to + * {@link #requestBaseStateOverride(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. + * <p> + * This method is noop if there is no base state request currently active. + * + * @throws SecurityException if the caller does not hold the + * {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission. + */ + @RequiresPermission(Manifest.permission.CONTROL_DEVICE_STATE) + public void cancelBaseStateOverride() { + mGlobal.cancelBaseStateOverride(); + } + + /** * Registers a callback to receive notifications about changes in device state. * * @param executor the executor to process notifications. diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java index aba538f51043..738045dafdf1 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java +++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java @@ -18,6 +18,7 @@ package android.hardware.devicestate; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.content.Context; import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; import android.os.Binder; @@ -81,6 +82,7 @@ public final class DeviceStateManagerGlobal { @VisibleForTesting public DeviceStateManagerGlobal(@NonNull IDeviceStateManager deviceStateManager) { mDeviceStateManager = deviceStateManager; + registerCallbackIfNeededLocked(); } /** @@ -116,27 +118,22 @@ public final class DeviceStateManagerGlobal { * DeviceStateRequest.Callback) * @see DeviceStateRequest */ + @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE, + conditional = true) public void requestState(@NonNull DeviceStateRequest request, @Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) { - if (callback == null && executor != null) { - throw new IllegalArgumentException("Callback must be supplied with executor."); - } else if (executor == null && callback != null) { - throw new IllegalArgumentException("Executor must be supplied with callback."); - } - + DeviceStateRequestWrapper requestWrapper = new DeviceStateRequestWrapper(request, callback, + executor); synchronized (mLock) { - registerCallbackIfNeededLocked(); - if (findRequestTokenLocked(request) != null) { // This request has already been submitted. return; } - // Add the request wrapper to the mRequests array before requesting the state as the // callback could be triggered immediately if the mDeviceStateManager IBinder is in the // same process as this instance. IBinder token = new Binder(); - mRequests.put(token, new DeviceStateRequestWrapper(request, callback, executor)); + mRequests.put(token, requestWrapper); try { mDeviceStateManager.requestState(token, request.getState(), request.getFlags()); @@ -153,10 +150,10 @@ public final class DeviceStateManagerGlobal { * * @see DeviceStateManager#cancelStateRequest */ + @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE, + conditional = true) public void cancelStateRequest() { synchronized (mLock) { - registerCallbackIfNeededLocked(); - try { mDeviceStateManager.cancelStateRequest(); } catch (RemoteException ex) { @@ -166,6 +163,56 @@ public final class DeviceStateManagerGlobal { } /** + * Submits a {@link DeviceStateRequest request} to modify the base state of the device. + * + * @see DeviceStateManager#requestBaseStateOverride(DeviceStateRequest, Executor, + * DeviceStateRequest.Callback) + * @see DeviceStateRequest + */ + @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) + public void requestBaseStateOverride(@NonNull DeviceStateRequest request, + @Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) { + DeviceStateRequestWrapper requestWrapper = new DeviceStateRequestWrapper(request, callback, + executor); + synchronized (mLock) { + if (findRequestTokenLocked(request) != null) { + // This request has already been submitted. + return; + } + // Add the request wrapper to the mRequests array before requesting the state as the + // callback could be triggered immediately if the mDeviceStateManager IBinder is in the + // same process as this instance. + IBinder token = new Binder(); + mRequests.put(token, requestWrapper); + + try { + mDeviceStateManager.requestBaseStateOverride(token, request.getState(), + request.getFlags()); + } catch (RemoteException ex) { + mRequests.remove(token); + throw ex.rethrowFromSystemServer(); + } + } + } + + /** + * Cancels a {@link DeviceStateRequest request} previously submitted with a call to + * {@link #requestBaseStateOverride(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. + * + * @see DeviceStateManager#cancelBaseStateOverride + */ + @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) + public void cancelBaseStateOverride() { + synchronized (mLock) { + try { + mDeviceStateManager.cancelBaseStateOverride(); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + } + + /** * Registers a callback to receive notifications about changes in device state. * * @see DeviceStateManager#registerCallback(Executor, DeviceStateCallback) @@ -179,9 +226,6 @@ public final class DeviceStateManagerGlobal { // This callback is already registered. return; } - - registerCallbackIfNeededLocked(); - // Add the callback wrapper to the mCallbacks array after registering the callback as // the callback could be triggered immediately if the mDeviceStateManager IBinder is in // the same process as this instance. @@ -357,6 +401,8 @@ public final class DeviceStateManagerGlobal { DeviceStateRequestWrapper(@NonNull DeviceStateRequest request, @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) { + validateRequestWrapperParameters(callback, executor); + mRequest = request; mCallback = callback; mExecutor = executor; @@ -377,5 +423,14 @@ public final class DeviceStateManagerGlobal { mExecutor.execute(() -> mCallback.onRequestCanceled(mRequest)); } + + private void validateRequestWrapperParameters( + @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) { + if (callback == null && executor != null) { + throw new IllegalArgumentException("Callback must be supplied with executor."); + } else if (executor == null && callback != null) { + throw new IllegalArgumentException("Executor must be supplied with callback."); + } + } } } diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl index e450e42497a0..7175eae58a26 100644 --- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl +++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl @@ -41,6 +41,10 @@ interface IDeviceStateManager { * previously registered with {@link #registerCallback(IDeviceStateManagerCallback)} before a * call to this method. * + * Requesting a state does not cancel a base state override made through + * {@link #requestBaseStateOverride}, but will still attempt to put the device into the + * supplied {@code state}. + * * @param token the request token provided * @param state the state of device the request is asking for * @param flags any flags that correspond to the request @@ -50,14 +54,53 @@ interface IDeviceStateManager { * @throws IllegalStateException if the supplied {@code token} has already been registered. * @throws IllegalArgumentException if the supplied {@code state} is not supported. */ + @JavaPassthrough(annotation= + "@android.annotation.RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true)") void requestState(IBinder token, int state, int flags); /** * Cancels the active request previously submitted with a call to - * {@link #requestState(IBinder, int, int)}. + * {@link #requestState(IBinder, int, int)}. Will have no effect on any base state override that + * was previously requested with {@link #requestBaseStateOverride}. * * @throws IllegalStateException if a callback has not yet been registered for the calling * process. */ + @JavaPassthrough(annotation= + "@android.annotation.RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true)") void cancelStateRequest(); + + /** + * Requests that the device's base state be overridden to the supplied {@code state}. A callback + * <b>MUST</b> have been previously registered with + * {@link #registerCallback(IDeviceStateManagerCallback)} before a call to this method. + * + * This method should only be used for testing, when you want to simulate the device physically + * changing states. If you are looking to change device state for a feature, where the system + * should still be aware that the physical state is different than the emulated state, use + * {@link #requestState}. + * + * @param token the request token provided + * @param state the state of device the request is asking for + * @param flags any flags that correspond to the request + * + * @throws IllegalStateException if a callback has not yet been registered for the calling + * process. + * @throws IllegalStateException if the supplied {@code token} has already been registered. + * @throws IllegalArgumentException if the supplied {@code state} is not supported. + */ + @JavaPassthrough(annotation= + "@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)") + void requestBaseStateOverride(IBinder token, int state, int flags); + + /** + * Cancels the active base state request previously submitted with a call to + * {@link #overrideBaseState(IBinder, int, int)}. + * + * @throws IllegalStateException if a callback has not yet been registered for the calling + * process. + */ + @JavaPassthrough(annotation= + "@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)") + void cancelBaseStateOverride(); } diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java index 4c247427ef8f..9e39e13265bd 100644 --- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java +++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java @@ -16,11 +16,12 @@ package android.hardware.devicestate; +import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; @@ -36,7 +37,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import org.mockito.Mockito; import java.util.HashSet; import java.util.Set; @@ -59,6 +59,7 @@ public final class DeviceStateManagerGlobalTest { public void setUp() { mService = new TestDeviceStateManagerService(); mDeviceStateManagerGlobal = new DeviceStateManagerGlobal(mService); + assertFalse(mService.mCallbacks.isEmpty()); } @Test @@ -79,8 +80,8 @@ public final class DeviceStateManagerGlobalTest { verify(callback2).onBaseStateChanged(eq(mService.getBaseState())); verify(callback2).onStateChanged(eq(mService.getMergedState())); - Mockito.reset(callback1); - Mockito.reset(callback2); + reset(callback1); + reset(callback2); // Change the supported states and verify callback mService.setSupportedStates(new int[]{ DEFAULT_DEVICE_STATE }); @@ -88,8 +89,8 @@ public final class DeviceStateManagerGlobalTest { verify(callback2).onSupportedStatesChanged(eq(mService.getSupportedStates())); mService.setSupportedStates(new int[]{ DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE }); - Mockito.reset(callback1); - Mockito.reset(callback2); + reset(callback1); + reset(callback2); // Change the base state and verify callback mService.setBaseState(OTHER_DEVICE_STATE); @@ -98,8 +99,8 @@ public final class DeviceStateManagerGlobalTest { verify(callback2).onBaseStateChanged(eq(mService.getBaseState())); verify(callback2).onStateChanged(eq(mService.getMergedState())); - Mockito.reset(callback1); - Mockito.reset(callback2); + reset(callback1); + reset(callback2); // Change the requested state and verify callback DeviceStateRequest request = DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE).build(); @@ -120,7 +121,7 @@ public final class DeviceStateManagerGlobalTest { verify(callback).onSupportedStatesChanged(eq(mService.getSupportedStates())); verify(callback).onBaseStateChanged(eq(mService.getBaseState())); verify(callback).onStateChanged(eq(mService.getMergedState())); - Mockito.reset(callback); + reset(callback); mDeviceStateManagerGlobal.unregisterDeviceStateCallback(callback); @@ -130,33 +131,86 @@ public final class DeviceStateManagerGlobalTest { } @Test - public void submittingRequestRegistersCallback() { - assertTrue(mService.mCallbacks.isEmpty()); + public void submitRequest() { + DeviceStateCallback callback = mock(DeviceStateCallback.class); + mDeviceStateManagerGlobal.registerDeviceStateCallback(callback, + ConcurrentUtils.DIRECT_EXECUTOR); - DeviceStateRequest request = DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE).build(); + verify(callback).onStateChanged(eq(mService.getBaseState())); + reset(callback); + + DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build(); mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */); - assertFalse(mService.mCallbacks.isEmpty()); + verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE)); + reset(callback); + + mDeviceStateManagerGlobal.cancelStateRequest(); + + verify(callback).onStateChanged(eq(mService.getBaseState())); } @Test - public void submitRequest() { + public void submitBaseStateOverrideRequest() { DeviceStateCallback callback = mock(DeviceStateCallback.class); mDeviceStateManagerGlobal.registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR); + verify(callback).onBaseStateChanged(eq(mService.getBaseState())); verify(callback).onStateChanged(eq(mService.getBaseState())); - Mockito.reset(callback); + reset(callback); DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build(); - mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */); + mDeviceStateManagerGlobal.requestBaseStateOverride(request, null /* executor */, + null /* callback */); + verify(callback).onBaseStateChanged(eq(OTHER_DEVICE_STATE)); verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE)); - Mockito.reset(callback); + reset(callback); - mDeviceStateManagerGlobal.cancelStateRequest(); + mDeviceStateManagerGlobal.cancelBaseStateOverride(); + + verify(callback).onBaseStateChanged(eq(mService.getBaseState())); + verify(callback).onStateChanged(eq(mService.getBaseState())); + } + + @Test + public void submitBaseAndEmulatedStateOverride() { + DeviceStateCallback callback = mock(DeviceStateCallback.class); + mDeviceStateManagerGlobal.registerDeviceStateCallback(callback, + ConcurrentUtils.DIRECT_EXECUTOR); + verify(callback).onBaseStateChanged(eq(mService.getBaseState())); verify(callback).onStateChanged(eq(mService.getBaseState())); + reset(callback); + + DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build(); + mDeviceStateManagerGlobal.requestBaseStateOverride(request, null /* executor */, + null /* callback */); + + verify(callback).onBaseStateChanged(eq(OTHER_DEVICE_STATE)); + verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE)); + assertEquals(OTHER_DEVICE_STATE, mService.getBaseState()); + reset(callback); + + DeviceStateRequest secondRequest = DeviceStateRequest.newBuilder( + DEFAULT_DEVICE_STATE).build(); + + mDeviceStateManagerGlobal.requestState(secondRequest, null, null); + + assertEquals(OTHER_DEVICE_STATE, mService.getBaseState()); + verify(callback).onStateChanged(eq(DEFAULT_DEVICE_STATE)); + reset(callback); + + mDeviceStateManagerGlobal.cancelStateRequest(); + + verify(callback).onStateChanged(OTHER_DEVICE_STATE); + reset(callback); + + mDeviceStateManagerGlobal.cancelBaseStateOverride(); + + verify(callback).onBaseStateChanged(DEFAULT_DEVICE_STATE); + verify(callback).onStateChanged(DEFAULT_DEVICE_STATE); } @Test @@ -169,7 +223,7 @@ public final class DeviceStateManagerGlobalTest { callback /* callback */); verify(callback).onRequestActivated(eq(request)); - Mockito.reset(callback); + reset(callback); mDeviceStateManagerGlobal.cancelStateRequest(); @@ -203,13 +257,16 @@ public final class DeviceStateManagerGlobalTest { private int[] mSupportedStates = new int[] { DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE }; private int mBaseState = DEFAULT_DEVICE_STATE; private Request mRequest; + private Request mBaseStateRequest; private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>(); private DeviceStateInfo getInfo() { + final int mergedBaseState = mBaseStateRequest == null + ? mBaseState : mBaseStateRequest.state; final int mergedState = mRequest == null - ? mBaseState : mRequest.state; - return new DeviceStateInfo(mSupportedStates, mBaseState, mergedState); + ? mergedBaseState : mRequest.state; + return new DeviceStateInfo(mSupportedStates, mergedBaseState, mergedState); } private void notifyDeviceStateInfoChanged() { @@ -238,7 +295,7 @@ public final class DeviceStateManagerGlobalTest { try { callback.onDeviceStateInfoChanged(getInfo()); } catch (RemoteException e) { - // Do nothing. Should never happen. + e.rethrowFromSystemServer(); } } @@ -249,7 +306,7 @@ public final class DeviceStateManagerGlobalTest { try { callback.onRequestCanceled(mRequest.token); } catch (RemoteException e) { - // Do nothing. Should never happen. + e.rethrowFromSystemServer(); } } } @@ -262,7 +319,7 @@ public final class DeviceStateManagerGlobalTest { try { callback.onRequestActive(token); } catch (RemoteException e) { - // Do nothing. Should never happen. + e.rethrowFromSystemServer(); } } } @@ -275,7 +332,46 @@ public final class DeviceStateManagerGlobalTest { try { callback.onRequestCanceled(token); } catch (RemoteException e) { - // Do nothing. Should never happen. + e.rethrowFromSystemServer(); + } + } + notifyDeviceStateInfoChanged(); + } + + @Override + public void requestBaseStateOverride(IBinder token, int state, int flags) { + if (mBaseStateRequest != null) { + for (IDeviceStateManagerCallback callback : mCallbacks) { + try { + callback.onRequestCanceled(mBaseStateRequest.token); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + + final Request request = new Request(token, state, flags); + mBaseStateRequest = request; + notifyDeviceStateInfoChanged(); + + for (IDeviceStateManagerCallback callback : mCallbacks) { + try { + callback.onRequestActive(token); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + + @Override + public void cancelBaseStateOverride() throws RemoteException { + IBinder token = mBaseStateRequest.token; + mBaseStateRequest = null; + for (IDeviceStateManagerCallback callback : mCallbacks) { + try { + callback.onRequestCanceled(token); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); } } notifyDeviceStateInfoChanged(); @@ -296,7 +392,7 @@ public final class DeviceStateManagerGlobalTest { } public int getBaseState() { - return mBaseState; + return getInfo().baseState; } public int getMergedState() { diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 8e00ccf68fb1..44c8e18a22cf 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -21,8 +21,11 @@ import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STA import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS; +import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE; +import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE; import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE; import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED; +import static com.android.server.devicestate.OverrideRequestController.STATUS_UNKNOWN; import android.annotation.IntDef; import android.annotation.IntRange; @@ -106,6 +109,8 @@ public final class DeviceStateManagerService extends SystemService { private final BinderService mBinderService; @NonNull private final OverrideRequestController mOverrideRequestController; + @NonNull + private final DeviceStateProviderListener mDeviceStateProviderListener; @VisibleForTesting @NonNull public ActivityTaskManagerInternal mActivityTaskManagerInternal; @@ -139,6 +144,12 @@ public final class DeviceStateManagerService extends SystemService { @NonNull private Optional<OverrideRequest> mActiveOverride = Optional.empty(); + // The current active base state override request. When set the device state specified here will + // replace the value in mBaseState. + @GuardedBy("mLock") + @NonNull + private Optional<OverrideRequest> mActiveBaseStateOverride = Optional.empty(); + // List of processes registered to receive notifications about changes to device state and // request status indexed by process id. @GuardedBy("mLock") @@ -177,7 +188,8 @@ public final class DeviceStateManagerService extends SystemService { mOverrideRequestController = new OverrideRequestController( this::onOverrideRequestStatusChangedLocked); mDeviceStatePolicy = policy; - mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener()); + mDeviceStateProviderListener = new DeviceStateProviderListener(); + mDeviceStatePolicy.getDeviceStateProvider().setListener(mDeviceStateProviderListener); mBinderService = new BinderService(); mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); } @@ -257,6 +269,21 @@ public final class DeviceStateManagerService extends SystemService { } } + /** + * Returns the current override base state, or {@link Optional#empty()} if no override state is + * requested. If an override base state is present, the returned state will be the same as + * the base state returned from {@link #getBaseState()}. + */ + @NonNull + Optional<DeviceState> getOverrideBaseState() { + synchronized (mLock) { + if (mActiveBaseStateOverride.isPresent()) { + return getStateLocked(mActiveBaseStateOverride.get().getRequestedState()); + } + return Optional.empty(); + } + } + /** Returns the list of currently supported device states. */ DeviceState[] getSupportedStates() { synchronized (mLock) { @@ -366,6 +393,7 @@ public final class DeviceStateManagerService extends SystemService { } final DeviceState baseState = baseStateOptional.get(); + if (mBaseState.isPresent() && mBaseState.get().equals(baseState)) { // Base state hasn't changed. Nothing to do. return; @@ -375,7 +403,7 @@ public final class DeviceStateManagerService extends SystemService { if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) { mOverrideRequestController.cancelOverrideRequest(); } - mOverrideRequestController.handleBaseStateChanged(); + mOverrideRequestController.handleBaseStateChanged(identifier); updatePendingStateLocked(); if (!mPendingState.isPresent()) { @@ -528,16 +556,41 @@ public final class DeviceStateManagerService extends SystemService { } } + @GuardedBy("mLock") private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request, @OverrideRequestController.RequestStatus int status) { - if (status == STATUS_ACTIVE) { - mActiveOverride = Optional.of(request); - } else if (status == STATUS_CANCELED) { - if (mActiveOverride.isPresent() && mActiveOverride.get() == request) { - mActiveOverride = Optional.empty(); + if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_BASE_STATE) { + switch (status) { + case STATUS_ACTIVE: + enableBaseStateRequestLocked(request); + return; + case STATUS_CANCELED: + if (mActiveBaseStateOverride.isPresent() + && mActiveBaseStateOverride.get() == request) { + mActiveBaseStateOverride = Optional.empty(); + } + break; + case STATUS_UNKNOWN: // same as default + default: + throw new IllegalArgumentException("Unknown request status: " + status); + } + } else if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_EMULATED_STATE) { + switch (status) { + case STATUS_ACTIVE: + mActiveOverride = Optional.of(request); + break; + case STATUS_CANCELED: + if (mActiveOverride.isPresent() && mActiveOverride.get() == request) { + mActiveOverride = Optional.empty(); + } + break; + case STATUS_UNKNOWN: // same as default + default: + throw new IllegalArgumentException("Unknown request status: " + status); } } else { - throw new IllegalArgumentException("Unknown request status: " + status); + throw new IllegalArgumentException( + "Unknown OverrideRest type: " + request.getRequestType()); } boolean updatedPendingState = updatePendingStateLocked(); @@ -564,6 +617,18 @@ public final class DeviceStateManagerService extends SystemService { mHandler.post(this::notifyPolicyIfNeeded); } + /** + * Sets the new base state of the device and notifies the process that made the base state + * override request that the request is now active. + */ + @GuardedBy("mLock") + private void enableBaseStateRequestLocked(OverrideRequest request) { + setBaseState(request.getRequestedState()); + mActiveBaseStateOverride = Optional.of(request); + ProcessRecord processRecord = mProcessRecords.get(request.getPid()); + processRecord.notifyRequestActiveAsync(request.getToken()); + } + private void registerProcess(int pid, IDeviceStateManagerCallback callback) { synchronized (mLock) { if (mProcessRecords.contains(pid)) { @@ -606,7 +671,8 @@ public final class DeviceStateManagerService extends SystemService { + " has no registered callback."); } - if (mOverrideRequestController.hasRequest(token)) { + if (mOverrideRequestController.hasRequest(token, + OVERRIDE_REQUEST_TYPE_EMULATED_STATE)) { throw new IllegalStateException("Request has already been made for the supplied" + " token: " + token); } @@ -617,7 +683,8 @@ public final class DeviceStateManagerService extends SystemService { + " is not supported."); } - OverrideRequest request = new OverrideRequest(token, callingPid, state, flags); + OverrideRequest request = new OverrideRequest(token, callingPid, state, flags, + OVERRIDE_REQUEST_TYPE_EMULATED_STATE); mOverrideRequestController.addRequest(request); } } @@ -633,6 +700,44 @@ public final class DeviceStateManagerService extends SystemService { } } + private void requestBaseStateOverrideInternal(int state, int flags, int callingPid, + @NonNull IBinder token) { + synchronized (mLock) { + final Optional<DeviceState> deviceState = getStateLocked(state); + if (!deviceState.isPresent()) { + throw new IllegalArgumentException("Requested state: " + state + + " is not supported."); + } + + final ProcessRecord processRecord = mProcessRecords.get(callingPid); + if (processRecord == null) { + throw new IllegalStateException("Process " + callingPid + + " has no registered callback."); + } + + if (mOverrideRequestController.hasRequest(token, + OVERRIDE_REQUEST_TYPE_BASE_STATE)) { + throw new IllegalStateException("Request has already been made for the supplied" + + " token: " + token); + } + + OverrideRequest request = new OverrideRequest(token, callingPid, state, flags, + OVERRIDE_REQUEST_TYPE_BASE_STATE); + mOverrideRequestController.addBaseStateRequest(request); + } + } + + private void cancelBaseStateOverrideInternal(int callingPid) { + synchronized (mLock) { + final ProcessRecord processRecord = mProcessRecords.get(callingPid); + if (processRecord == null) { + throw new IllegalStateException("Process " + callingPid + + " has no registered callback."); + } + setBaseState(mDeviceStateProviderListener.mCurrentBaseState); + } + } + private void dumpInternal(PrintWriter pw) { pw.println("DEVICE STATE MANAGER (dumpsys device_state)"); @@ -729,6 +834,8 @@ public final class DeviceStateManagerService extends SystemService { } private final class DeviceStateProviderListener implements DeviceStateProvider.Listener { + @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int mCurrentBaseState; + @Override public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) { if (newDeviceStates.length == 0) { @@ -743,7 +850,7 @@ public final class DeviceStateManagerService extends SystemService { if (identifier < MINIMUM_DEVICE_STATE || identifier > MAXIMUM_DEVICE_STATE) { throw new IllegalArgumentException("Invalid identifier: " + identifier); } - + mCurrentBaseState = identifier; setBaseState(identifier); } } @@ -895,6 +1002,38 @@ public final class DeviceStateManagerService extends SystemService { } @Override // Binder call + public void requestBaseStateOverride(IBinder token, int state, int flags) { + final int callingPid = Binder.getCallingPid(); + getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, + "Permission required to control base state of device."); + + if (token == null) { + throw new IllegalArgumentException("Request token must not be null."); + } + + final long callingIdentity = Binder.clearCallingIdentity(); + try { + requestBaseStateOverrideInternal(state, flags, callingPid, token); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + @Override // Binder call + public void cancelBaseStateOverride() { + final int callingPid = Binder.getCallingPid(); + getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, + "Permission required to control base state of device."); + + final long callingIdentity = Binder.clearCallingIdentity(); + try { + cancelBaseStateOverrideInternal(callingPid); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + @Override // Binder call public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver result) { new DeviceStateManagerShellCommand(DeviceStateManagerService.this) diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java index 659ee75de453..8c6068d89296 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java @@ -16,11 +16,8 @@ package com.android.server.devicestate; -import static android.Manifest.permission.CONTROL_DEVICE_STATE; - import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.Context; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateRequest; import android.os.Binder; @@ -39,6 +36,8 @@ import java.util.stream.Collectors; public class DeviceStateManagerShellCommand extends ShellCommand { @Nullable private static DeviceStateRequest sLastRequest; + @Nullable + private static DeviceStateRequest sLastBaseStateRequest; private final DeviceStateManagerService mService; private final DeviceStateManager mClient; @@ -58,6 +57,8 @@ public class DeviceStateManagerShellCommand extends ShellCommand { switch (cmd) { case "state": return runState(pw); + case "base-state": + return runBaseState(pw); case "print-state": return runPrintState(pw); case "print-states": @@ -89,10 +90,6 @@ public class DeviceStateManagerShellCommand extends ShellCommand { return 0; } - final Context context = mService.getContext(); - context.enforceCallingOrSelfPermission( - CONTROL_DEVICE_STATE, - "Permission required to request device state."); final long callingIdentity = Binder.clearCallingIdentity(); try { if ("reset".equals(nextArg)) { @@ -127,6 +124,47 @@ public class DeviceStateManagerShellCommand extends ShellCommand { return 0; } + private int runBaseState(PrintWriter pw) { + final String nextArg = getNextArg(); + if (nextArg == null) { + printAllStates(pw); + return 0; + } + + final long callingIdentity = Binder.clearCallingIdentity(); + try { + if ("reset".equals(nextArg)) { + if (sLastBaseStateRequest != null) { + mClient.cancelBaseStateOverride(); + sLastBaseStateRequest = null; + } + } else { + int requestedState = Integer.parseInt(nextArg); + DeviceStateRequest request = DeviceStateRequest.newBuilder(requestedState).build(); + + mClient.requestBaseStateOverride(request, null, null); + + sLastBaseStateRequest = request; + } + } catch (NumberFormatException e) { + getErrPrintWriter().println("Error: requested state should be an integer"); + return -1; + } catch (IllegalArgumentException e) { + getErrPrintWriter().println("Error: " + e.getMessage()); + getErrPrintWriter().println("-------------------"); + getErrPrintWriter().println("Run:"); + getErrPrintWriter().println(""); + getErrPrintWriter().println(" print-states"); + getErrPrintWriter().println(""); + getErrPrintWriter().println("to get the list of currently supported device states"); + return -1; + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + + return 0; + } + private int runPrintState(PrintWriter pw) { Optional<DeviceState> deviceState = mService.getCommittedState(); if (deviceState.isPresent()) { diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java index 35a4c844c710..325e384cb8ad 100644 --- a/services/core/java/com/android/server/devicestate/OverrideRequest.java +++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java @@ -16,9 +16,13 @@ package com.android.server.devicestate; +import android.annotation.IntDef; import android.hardware.devicestate.DeviceStateRequest; import android.os.IBinder; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A request to override the state managed by {@link DeviceStateManagerService}. * @@ -30,13 +34,47 @@ final class OverrideRequest { private final int mRequestedState; @DeviceStateRequest.RequestFlags private final int mFlags; + @OverrideRequestType + private final int mRequestType; + + /** + * Denotes that the request is meant to override the emulated state of the device. This will + * not change the base (physical) state of the device. + * + * This request type should be used if you are looking to emulate a device state for a feature + * but want the system to be aware of the physical state of the device. + */ + public static final int OVERRIDE_REQUEST_TYPE_EMULATED_STATE = 0; + + /** + * Denotes that the request is meant to override the base (physical) state of the device. + * Overriding the base state may not change the emulated state of the device if there is also an + * override request active for that property. + * + * This request type should only be used for testing, where you want to simulate the physical + * state of the device changing. + */ + public static final int OVERRIDE_REQUEST_TYPE_BASE_STATE = 1; + + /** + * Flags for signifying the type of {@link OverrideRequest}. + * + * @hide + */ + @IntDef(flag = true, prefix = { "REQUEST_TYPE_" }, value = { + OVERRIDE_REQUEST_TYPE_BASE_STATE, + OVERRIDE_REQUEST_TYPE_EMULATED_STATE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface OverrideRequestType {} OverrideRequest(IBinder token, int pid, int requestedState, - @DeviceStateRequest.RequestFlags int flags) { + @DeviceStateRequest.RequestFlags int flags, @OverrideRequestType int requestType) { mToken = token; mPid = pid; mRequestedState = requestedState; mFlags = flags; + mRequestType = requestType; } IBinder getToken() { @@ -55,4 +93,9 @@ final class OverrideRequest { int getFlags() { return mFlags; } + + @OverrideRequestType + int getRequestType() { + return mRequestType; + } } diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java index 01f5a09323cf..e39c7324d7bd 100644 --- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java +++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java @@ -75,6 +75,8 @@ final class OverrideRequestController { // Handle to the current override request, null if none. private OverrideRequest mRequest; + // Handle to the current base state override request, null if none. + private OverrideRequest mBaseStateRequest; private boolean mStickyRequestsAllowed; // The current request has outlived their process. @@ -111,13 +113,23 @@ final class OverrideRequestController { } } + void addBaseStateRequest(@NonNull OverrideRequest request) { + OverrideRequest previousRequest = mBaseStateRequest; + mBaseStateRequest = request; + mListener.onStatusChanged(request, STATUS_ACTIVE); + + if (previousRequest != null) { + cancelRequestLocked(previousRequest); + } + } + /** * Cancels the request with the specified {@code token} and notifies the listener of all changes * to request status as a result of this operation. */ void cancelRequest(@NonNull OverrideRequest request) { // Either don't have a current request or attempting to cancel an already cancelled request - if (!hasRequest(request.getToken())) { + if (!hasRequest(request.getToken(), request.getRequestType())) { return; } cancelCurrentRequestLocked(); @@ -144,11 +156,24 @@ final class OverrideRequestController { } /** + * Cancels the current base state override request, this could be due to the physical + * configuration of the device changing. + */ + void cancelBaseStateOverrideRequest() { + cancelCurrentBaseStateRequestLocked(); + } + + /** * Returns {@code true} if this controller is current managing a request with the specified * {@code token}, {@code false} otherwise. */ - boolean hasRequest(@NonNull IBinder token) { - return mRequest != null && token == mRequest.getToken(); + boolean hasRequest(@NonNull IBinder token, + @OverrideRequest.OverrideRequestType int requestType) { + if (requestType == OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE) { + return mBaseStateRequest != null && token == mBaseStateRequest.getToken(); + } else { + return mRequest != null && token == mRequest.getToken(); + } } /** @@ -157,11 +182,11 @@ final class OverrideRequestController { * operation. */ void handleProcessDied(int pid) { - if (mRequest == null) { - return; + if (mBaseStateRequest != null && mBaseStateRequest.getPid() == pid) { + cancelCurrentBaseStateRequestLocked(); } - if (mRequest.getPid() == pid) { + if (mRequest != null && mRequest.getPid() == pid) { if (mStickyRequestsAllowed) { // Do not cancel the requests now because sticky requests are allowed. These // requests will be cancelled on a call to cancelStickyRequests(). @@ -176,7 +201,10 @@ final class OverrideRequestController { * Notifies the controller that the base state has changed. The controller will notify the * listener of all changes to request status as a result of this change. */ - void handleBaseStateChanged() { + void handleBaseStateChanged(int state) { + if (mBaseStateRequest != null && state != mBaseStateRequest.getRequestedState()) { + cancelBaseStateOverrideRequest(); + } if (mRequest == null) { return; } @@ -192,11 +220,12 @@ final class OverrideRequestController { * notify the listener of all changes to request status as a result of this change. */ void handleNewSupportedStates(int[] newSupportedStates) { - if (mRequest == null) { - return; + if (mBaseStateRequest != null && !contains(newSupportedStates, + mBaseStateRequest.getRequestedState())) { + cancelCurrentBaseStateRequestLocked(); } - if (!contains(newSupportedStates, mRequest.getRequestedState())) { + if (mRequest != null && !contains(newSupportedStates, mRequest.getRequestedState())) { cancelCurrentRequestLocked(); } } @@ -228,10 +257,23 @@ final class OverrideRequestController { return; } mStickyRequest = false; - mListener.onStatusChanged(mRequest, STATUS_CANCELED); + cancelRequestLocked(mRequest); mRequest = null; } + /** + * Handles cancelling {@code mBaseStateRequest}. + * Notifies the listener of the canceled status as well. + */ + private void cancelCurrentBaseStateRequestLocked() { + if (mBaseStateRequest == null) { + Slog.w(TAG, "Attempted to cancel a null OverrideRequest"); + return; + } + cancelRequestLocked(mBaseStateRequest); + mBaseStateRequest = null; + } + private static boolean contains(int[] array, int value) { for (int i = 0; i < array.length; i++) { if (array[i] == value) { 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 038cbc032375..94f88abf7301 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java @@ -587,6 +587,157 @@ public final class DeviceStateManagerServiceTest { }); } + @Test + public void requestBaseStateOverride() 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().requestBaseStateOverride(token, + OTHER_DEVICE_STATE.getIdentifier(), + 0 /* flags */); + // Flush the handler twice. The first flush ensures the request is added and the policy is + // notified, while the second flush ensures the callback is notified once the change is + // committed. + flushHandler(2 /* count */); + + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_ACTIVE); + // Committed state changes as there is a requested override. + assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE)); + assertEquals(mSysPropSetter.getValue(), + OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName()); + assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE)); + assertEquals(mService.getOverrideBaseState().get(), OTHER_DEVICE_STATE); + assertFalse(mService.getOverrideState().isPresent()); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + OTHER_DEVICE_STATE.getIdentifier()); + + assertNotNull(callback.getLastNotifiedInfo()); + assertEquals(callback.getLastNotifiedInfo().baseState, + OTHER_DEVICE_STATE.getIdentifier()); + assertEquals(callback.getLastNotifiedInfo().currentState, + OTHER_DEVICE_STATE.getIdentifier()); + + mService.getBinderService().cancelBaseStateOverride(); + flushHandler(); + + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_CANCELED); + // Committed state is set back to the requested state once the override is cleared. + assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); + assertEquals(mSysPropSetter.getValue(), + DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName()); + assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); + assertFalse(mService.getOverrideBaseState().isPresent()); + assertFalse(mService.getOverrideState().isPresent()); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + DEFAULT_DEVICE_STATE.getIdentifier()); + + assertEquals(callback.getLastNotifiedInfo().baseState, + DEFAULT_DEVICE_STATE.getIdentifier()); + assertEquals(callback.getLastNotifiedInfo().currentState, + DEFAULT_DEVICE_STATE.getIdentifier()); + } + + @Test + public void requestBaseStateOverride_cancelledByBaseStateUpdate() throws RemoteException { + final DeviceState testDeviceState = new DeviceState(2, "TEST", 0); + TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); + mService.getBinderService().registerCallback(callback); + mProvider.notifySupportedDeviceStates( + new DeviceState[]{DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE, testDeviceState }); + flushHandler(); + + final IBinder token = new Binder(); + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_UNKNOWN); + + mService.getBinderService().requestBaseStateOverride(token, + OTHER_DEVICE_STATE.getIdentifier(), + 0 /* flags */); + // Flush the handler twice. The first flush ensures the request is added and the policy is + // notified, while the second flush ensures the callback is notified once the change is + // committed. + flushHandler(2 /* count */); + + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_ACTIVE); + // Committed state changes as there is a requested override. + assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE)); + assertEquals(mSysPropSetter.getValue(), + OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName()); + assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE)); + assertEquals(mService.getOverrideBaseState().get(), OTHER_DEVICE_STATE); + assertFalse(mService.getOverrideState().isPresent()); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + OTHER_DEVICE_STATE.getIdentifier()); + + assertNotNull(callback.getLastNotifiedInfo()); + assertEquals(callback.getLastNotifiedInfo().baseState, + OTHER_DEVICE_STATE.getIdentifier()); + assertEquals(callback.getLastNotifiedInfo().currentState, + OTHER_DEVICE_STATE.getIdentifier()); + + mProvider.setState(testDeviceState.getIdentifier()); + flushHandler(); + + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_CANCELED); + // Committed state is set to the new base state once the override is cleared. + assertEquals(mService.getCommittedState(), Optional.of(testDeviceState)); + assertEquals(mSysPropSetter.getValue(), + testDeviceState.getIdentifier() + ":" + testDeviceState.getName()); + assertEquals(mService.getBaseState(), Optional.of(testDeviceState)); + assertFalse(mService.getOverrideBaseState().isPresent()); + assertFalse(mService.getOverrideState().isPresent()); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + testDeviceState.getIdentifier()); + + assertEquals(callback.getLastNotifiedInfo().baseState, + testDeviceState.getIdentifier()); + assertEquals(callback.getLastNotifiedInfo().currentState, + testDeviceState.getIdentifier()); + } + + @Test + public void requestBaseState_unsupportedState() throws RemoteException { + TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); + mService.getBinderService().registerCallback(callback); + + assertThrows(IllegalArgumentException.class, () -> { + final IBinder token = new Binder(); + mService.getBinderService().requestBaseStateOverride(token, + UNSUPPORTED_DEVICE_STATE.getIdentifier(), 0 /* flags */); + }); + } + + @Test + public void requestBaseState_invalidState() throws RemoteException { + TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); + mService.getBinderService().registerCallback(callback); + + assertThrows(IllegalArgumentException.class, () -> { + final IBinder token = new Binder(); + mService.getBinderService().requestBaseStateOverride(token, INVALID_DEVICE_STATE, + 0 /* flags */); + }); + } + + @Test + public void requestBaseState_beforeRegisteringCallback() { + assertThrows(IllegalStateException.class, () -> { + final IBinder token = new Binder(); + mService.getBinderService().requestBaseStateOverride(token, + DEFAULT_DEVICE_STATE.getIdentifier(), + 0 /* flags */); + }); + } + private static void assertArrayEquals(int[] expected, int[] actual) { Assert.assertTrue(Arrays.equals(expected, actual)); } diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java index 2297c91818c0..430504ca2428 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java @@ -16,6 +16,8 @@ package com.android.server.devicestate; +import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE; +import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE; import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE; import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED; @@ -57,7 +59,7 @@ public final class OverrideRequestControllerTest { @Test public void addRequest() { OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, - 0 /* requestedState */, 0 /* flags */); + 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); assertNull(mStatusListener.getLastStatus(request)); mController.addRequest(request); @@ -67,14 +69,14 @@ public final class OverrideRequestControllerTest { @Test public void addRequest_cancelExistingRequestThroughNewRequest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 0 /* requestedState */, 0 /* flags */); + 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); assertNull(mStatusListener.getLastStatus(firstRequest)); mController.addRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 1 /* requestedState */, 0 /* flags */); + 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); assertNull(mStatusListener.getLastStatus(secondRequest)); mController.addRequest(secondRequest); @@ -85,7 +87,7 @@ public final class OverrideRequestControllerTest { @Test public void addRequest_cancelActiveRequest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 0 /* requestedState */, 0 /* flags */); + 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); mController.addRequest(firstRequest); @@ -97,30 +99,90 @@ public final class OverrideRequestControllerTest { } @Test + public void addBaseStateRequest() { + OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); + assertNull(mStatusListener.getLastStatus(request)); + + mController.addBaseStateRequest(request); + assertEquals(mStatusListener.getLastStatus(request).intValue(), STATUS_ACTIVE); + } + + @Test + public void addBaseStateRequest_cancelExistingBaseStateRequestThroughNewRequest() { + OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); + assertNull(mStatusListener.getLastStatus(firstRequest)); + + mController.addBaseStateRequest(firstRequest); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + + OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); + assertNull(mStatusListener.getLastStatus(secondRequest)); + + mController.addBaseStateRequest(secondRequest); + assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); + } + + @Test + public void addBaseStateRequest_cancelActiveBaseStateRequest() { + OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); + + mController.addBaseStateRequest(firstRequest); + + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + + mController.cancelBaseStateOverrideRequest(); + + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); + } + + @Test public void handleBaseStateChanged() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* requestedState */, - DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */); + DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */, + OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + + OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 0 /* requestedState */, + 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); mController.addRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); - mController.handleBaseStateChanged(); + mController.addBaseStateRequest(baseStateRequest); + + assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE); + + mController.handleBaseStateChanged(1); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); + assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED); } @Test public void handleProcessDied() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 0 /* requestedState */, 0 /* flags */); + 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + + OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 1 /* requestedState */, + 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); mController.addRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + mController.addBaseStateRequest(baseStateRequest); + assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE); + mController.handleProcessDied(0); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); + assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED); } @Test @@ -128,13 +190,20 @@ public final class OverrideRequestControllerTest { mController.setStickyRequestsAllowed(true); OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 0 /* requestedState */, 0 /* flags */); + 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + + OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); mController.addRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + mController.addBaseStateRequest(baseStateRequest); + assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE); + mController.handleProcessDied(0); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED); mController.cancelStickyRequest(); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); @@ -143,22 +212,31 @@ public final class OverrideRequestControllerTest { @Test public void handleNewSupportedStates() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 1 /* requestedState */, 0 /* flags */); + 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + + OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */, + 1 /* requestedState */, + 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); mController.addRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); - mController.handleNewSupportedStates(new int[]{ 0, 1 }); + mController.addBaseStateRequest(baseStateRequest); + assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE); + + mController.handleNewSupportedStates(new int[]{0, 1}); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE); - mController.handleNewSupportedStates(new int[]{ 0 }); + mController.handleNewSupportedStates(new int[]{0}); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); + assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED); } @Test public void cancelOverrideRequestsTest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 1 /* requestedState */, 0 /* flags */); + 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); mController.addRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); |