Introduces requestBaseStateOverride test API

Adds new requestBaseStateOverride on DeviceStateManager
to be used for testing, to provide a similar behavior
to changing the physical configuration of the device.

This is necessary now that the requestState API is being
used for features now, we need a test API that will
be a closer simulation to physical device changes.

Bug: 234336979
Test: DeviceStateManagerGlobalTest
Change-Id: I6ad9e799329f16521aa3c13b248eed762fe0b121
This commit is contained in:
Kenneth Ford
2022-08-30 22:40:01 +00:00
parent d5fe5cc1ca
commit 7f8d5fd451
4 changed files with 244 additions and 44 deletions

View File

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

View File

@@ -16,6 +16,7 @@
package android.hardware.devicestate;
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -114,6 +115,52 @@ public final class DeviceStateManager {
mGlobal.cancelStateRequest();
}
/**
* 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.
*

View File

@@ -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) {
@@ -165,6 +162,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.
*
@@ -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.");
}
}
}
}

View File

@@ -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);
@@ -129,16 +130,6 @@ public final class DeviceStateManagerGlobalTest {
verifyZeroInteractions(callback);
}
@Test
public void submittingRequestRegistersCallback() {
assertTrue(mService.mCallbacks.isEmpty());
DeviceStateRequest request = DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE).build();
mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */);
assertFalse(mService.mCallbacks.isEmpty());
}
@Test
public void submitRequest() {
DeviceStateCallback callback = mock(DeviceStateCallback.class);
@@ -146,19 +137,82 @@ public final class DeviceStateManagerGlobalTest {
ConcurrentUtils.DIRECT_EXECUTOR);
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 */);
verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
Mockito.reset(callback);
reset(callback);
mDeviceStateManagerGlobal.cancelStateRequest();
verify(callback).onStateChanged(eq(mService.getBaseState()));
}
@Test
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()));
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));
reset(callback);
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
public void verifyDeviceStateRequestCallbacksCalled() {
DeviceStateRequest.Callback callback = mock(TestDeviceStateRequestCallback.class);
@@ -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() {