Merge "Add device state request APIs to DeviceStateManager." into sc-dev

This commit is contained in:
TreeHugger Robot
2021-02-06 04:37:38 +00:00
committed by Android (Google) Code Review
14 changed files with 1342 additions and 240 deletions

View File

@@ -12,6 +12,7 @@ package android {
field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
field public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS";
field public static final String CONTROL_DEVICE_STATE = "android.permission.CONTROL_DEVICE_STATE";
field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
field @Deprecated public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
field public static final String MANAGE_ACTIVITY_TASKS = "android.permission.MANAGE_ACTIVITY_TASKS";
@@ -900,6 +901,40 @@ package android.hardware.camera2 {
}
package android.hardware.devicestate {
public final class DeviceStateManager {
method public void addDeviceStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateListener);
method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest);
method @NonNull public int[] getSupportedStates();
method public void removeDeviceStateListener(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateListener);
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);
}
public static interface DeviceStateManager.DeviceStateListener {
method public void onDeviceStateChanged(int);
}
public final class DeviceStateRequest {
method public int getFlags();
method public int getState();
method @NonNull public static android.hardware.devicestate.DeviceStateRequest.Builder newBuilder(int);
field public static final int FLAG_CANCEL_WHEN_BASE_CHANGES = 1; // 0x1
}
public static final class DeviceStateRequest.Builder {
method @NonNull public android.hardware.devicestate.DeviceStateRequest build();
method @NonNull public android.hardware.devicestate.DeviceStateRequest.Builder setFlags(int);
}
public static interface DeviceStateRequest.Callback {
method public default void onRequestActivated(@NonNull android.hardware.devicestate.DeviceStateRequest);
method public default void onRequestCanceled(@NonNull android.hardware.devicestate.DeviceStateRequest);
method public default void onRequestSuspended(@NonNull android.hardware.devicestate.DeviceStateRequest);
}
}
package android.hardware.display {
public class AmbientDisplayConfiguration {

View File

@@ -16,8 +16,12 @@
package android.hardware.devicestate;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.content.Context;
import java.util.concurrent.Executor;
@@ -28,13 +32,19 @@ import java.util.concurrent.Executor;
*
* @hide
*/
@TestApi
@SystemService(Context.DEVICE_STATE_SERVICE)
public final class DeviceStateManager {
/** Invalid device state. */
/**
* Invalid device state.
*
* @hide
*/
public static final int INVALID_DEVICE_STATE = -1;
private DeviceStateManagerGlobal mGlobal;
private final DeviceStateManagerGlobal mGlobal;
/** @hide */
public DeviceStateManager() {
DeviceStateManagerGlobal global = DeviceStateManagerGlobal.getInstance();
if (global == null) {
@@ -44,24 +54,74 @@ public final class DeviceStateManager {
mGlobal = global;
}
/**
* Returns the list of device states that are supported and can be requested with
* {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
*/
@NonNull
public int[] getSupportedStates() {
return mGlobal.getSupportedStates();
}
/**
* Submits a {@link DeviceStateRequest request} to modify the device state.
* <p>
* By default, the request is kept active until a call to
* {@link #cancelRequest(DeviceStateRequest)} or until one of the following occurs:
* <ul>
* <li>Another processes submits a request succeeding this request in which case the request
* will be suspended until the interrupting request is canceled.
* <li>The requested state has become unsupported.
* <li>The process submitting the request dies.
* </ul>
* 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.
*
* @see DeviceStateRequest
*/
@RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
public void requestState(@NonNull DeviceStateRequest request,
@Nullable @CallbackExecutor Executor executor,
@Nullable DeviceStateRequest.Callback callback) {
mGlobal.requestState(request, callback, executor);
}
/**
* Cancels a {@link DeviceStateRequest request} previously submitted with a call to
* {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
* <p>
* 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.
*/
@RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
public void cancelRequest(@NonNull DeviceStateRequest request) {
mGlobal.cancelRequest(request);
}
/**
* Registers a listener to receive notifications about changes in device state.
*
* @param listener the listener to register.
* @param executor the executor to process notifications.
* @param listener the listener to register.
*
* @see DeviceStateListener
*/
public void registerDeviceStateListener(@NonNull DeviceStateListener listener,
@NonNull Executor executor) {
public void addDeviceStateListener(@NonNull @CallbackExecutor Executor executor,
@NonNull DeviceStateListener listener) {
mGlobal.registerDeviceStateListener(listener, executor);
}
/**
* Unregisters a listener previously registered with
* {@link #registerDeviceStateListener(DeviceStateListener, Executor)}.
* {@link #addDeviceStateListener(Executor, DeviceStateListener)}.
*/
public void unregisterDeviceStateListener(@NonNull DeviceStateListener listener) {
public void removeDeviceStateListener(@NonNull DeviceStateListener listener) {
mGlobal.unregisterDeviceStateListener(listener);
}

View File

@@ -20,9 +20,11 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager.DeviceStateListener;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -67,6 +69,9 @@ public final class DeviceStateManagerGlobal {
@GuardedBy("mLock")
private final ArrayList<DeviceStateListenerWrapper> mListeners = new ArrayList<>();
@GuardedBy("mLock")
private final ArrayMap<IBinder, DeviceStateRequestWrapper> mRequests = new ArrayMap<>();
@Nullable
@GuardedBy("mLock")
private Integer mLastReceivedState;
@@ -76,10 +81,85 @@ public final class DeviceStateManagerGlobal {
mDeviceStateManager = deviceStateManager;
}
/**
* Returns the set of supported device states.
*
* @see DeviceStateManager#getSupportedStates()
*/
public int[] getSupportedStates() {
try {
return mDeviceStateManager.getSupportedDeviceStates();
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
/**
* Submits a {@link DeviceStateRequest request} to modify the device state.
*
* @see DeviceStateManager#requestState(DeviceStateRequest,
* Executor, DeviceStateRequest.Callback)
* @see DeviceStateRequest
*/
public void requestState(@NonNull DeviceStateRequest request,
@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.");
}
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));
try {
mDeviceStateManager.requestState(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 #requestState(DeviceStateRequest, DeviceStateRequest.Callback, Executor)}.
*
* @see DeviceStateManager#cancelRequest(DeviceStateRequest)
*/
public void cancelRequest(@NonNull DeviceStateRequest request) {
synchronized (mLock) {
registerCallbackIfNeededLocked();
final IBinder token = findRequestTokenLocked(request);
if (token == null) {
// This request has not been submitted.
return;
}
try {
mDeviceStateManager.cancelRequest(token);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}
/**
* Registers a listener to receive notifications about changes in device state.
*
* @see DeviceStateManager#registerDeviceStateListener(DeviceStateListener, Executor)
* @see DeviceStateManager#addDeviceStateListener(Executor, DeviceStateListener)
*/
@VisibleForTesting(visibility = Visibility.PACKAGE)
public void registerDeviceStateListener(@NonNull DeviceStateListener listener,
@@ -112,7 +192,7 @@ public final class DeviceStateManagerGlobal {
* Unregisters a listener previously registered with
* {@link #registerDeviceStateListener(DeviceStateListener, Executor)}.
*
* @see DeviceStateManager#registerDeviceStateListener(DeviceStateListener, Executor)
* @see DeviceStateManager#addDeviceStateListener(Executor, DeviceStateListener)
*/
@VisibleForTesting(visibility = Visibility.PACKAGE)
public void unregisterDeviceStateListener(DeviceStateListener listener) {
@@ -144,6 +224,17 @@ public final class DeviceStateManagerGlobal {
return -1;
}
@Nullable
private IBinder findRequestTokenLocked(@NonNull DeviceStateRequest request) {
for (int i = 0; i < mRequests.size(); i++) {
if (mRequests.valueAt(i).mRequest.equals(request)) {
return mRequests.keyAt(i);
}
}
return null;
}
/** Handles a call from the server that the device state has changed. */
private void handleDeviceStateChanged(int newDeviceState) {
ArrayList<DeviceStateListenerWrapper> listeners;
synchronized (mLock) {
@@ -156,11 +247,68 @@ public final class DeviceStateManagerGlobal {
}
}
/**
* Handles a call from the server that a request for the supplied {@code token} has become
* active.
*/
private void handleRequestActive(IBinder token) {
DeviceStateRequestWrapper request;
synchronized (mLock) {
request = mRequests.get(token);
}
if (request != null) {
request.notifyRequestActive();
}
}
/**
* Handles a call from the server that a request for the supplied {@code token} has become
* suspended.
*/
private void handleRequestSuspended(IBinder token) {
DeviceStateRequestWrapper request;
synchronized (mLock) {
request = mRequests.get(token);
}
if (request != null) {
request.notifyRequestSuspended();
}
}
/**
* Handles a call from the server that a request for the supplied {@code token} has become
* canceled.
*/
private void handleRequestCanceled(IBinder token) {
DeviceStateRequestWrapper request;
synchronized (mLock) {
request = mRequests.remove(token);
}
if (request != null) {
request.notifyRequestCanceled();
}
}
private final class DeviceStateManagerCallback extends IDeviceStateManagerCallback.Stub {
@Override
public void onDeviceStateChanged(int deviceState) {
handleDeviceStateChanged(deviceState);
}
@Override
public void onRequestActive(IBinder token) {
handleRequestActive(token);
}
@Override
public void onRequestSuspended(IBinder token) {
handleRequestSuspended(token);
}
@Override
public void onRequestCanceled(IBinder token) {
handleRequestCanceled(token);
}
}
private static final class DeviceStateListenerWrapper {
@@ -176,4 +324,43 @@ public final class DeviceStateManagerGlobal {
mExecutor.execute(() -> mDeviceStateListener.onDeviceStateChanged(newDeviceState));
}
}
private static final class DeviceStateRequestWrapper {
private final DeviceStateRequest mRequest;
@Nullable
private final DeviceStateRequest.Callback mCallback;
@Nullable
private final Executor mExecutor;
DeviceStateRequestWrapper(@NonNull DeviceStateRequest request,
@Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) {
mRequest = request;
mCallback = callback;
mExecutor = executor;
}
void notifyRequestActive() {
if (mCallback == null) {
return;
}
mExecutor.execute(() -> mCallback.onRequestActivated(mRequest));
}
void notifyRequestSuspended() {
if (mCallback == null) {
return;
}
mExecutor.execute(() -> mCallback.onRequestSuspended(mRequest));
}
void notifyRequestCanceled() {
if (mCallback == null) {
return;
}
mExecutor.execute(() -> mCallback.onRequestSuspended(mRequest));
}
}
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.hardware.devicestate;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.TestApi;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.Executor;
/**
* A request to alter the state of the device managed by {@link DeviceStateManager}.
* <p>
* Once constructed, a {@link DeviceStateRequest request} can be submitted with a call to
* {@link DeviceStateManager#requestState(DeviceStateRequest, Executor,
* DeviceStateRequest.Callback)}.
* <p>
* By default, the request is kept active until a call to
* {@link DeviceStateManager#cancelRequest(DeviceStateRequest)} or until one of the following
* occurs:
* <ul>
* <li>Another processes submits a request succeeding this request in which case the request
* will be suspended until the interrupting request is canceled.
* <li>The requested state has become unsupported.
* <li>The process submitting the request dies.
* </ul>
* However, this behavior can be changed by setting flags on the request. For example, the
* {@link #FLAG_CANCEL_WHEN_BASE_CHANGES} flag will extend this behavior to also cancel the
* request whenever the base (non-override) device state changes.
*
* @see DeviceStateManager
*
* @hide
*/
@TestApi
public final class DeviceStateRequest {
/**
* Flag that indicates the request should be canceled automatically when the base
* (non-override) device state changes. Useful when the requestor only wants the request to
* remain active while the base state remains constant and automatically cancel when the user
* manipulates the device into a different state.
*/
public static final int FLAG_CANCEL_WHEN_BASE_CHANGES = 1 << 0;
/** @hide */
@IntDef(prefix = {"FLAG_"}, flag = true, value = {
FLAG_CANCEL_WHEN_BASE_CHANGES,
})
@Retention(RetentionPolicy.SOURCE)
public @interface RequestFlags {}
/**
* Creates a new {@link Builder} for a {@link DeviceStateRequest}. Must be one of the supported
* states for the device which can be queried with a call to
* {@link DeviceStateManager#getSupportedStates()}.
*
* @param requestedState the device state being requested.
*/
@NonNull
public static Builder newBuilder(int requestedState) {
return new Builder(requestedState);
}
/**
* Builder for {@link DeviceStateRequest}. An instance can be obtained through
* {@link #newBuilder(int)}.
*/
public static final class Builder {
private final int mRequestedState;
private int mFlags;
private Builder(int requestedState) {
mRequestedState = requestedState;
}
/**
* Sets the flag bits provided within {@code flags} with all other bits remaining
* unchanged.
*/
@NonNull
public Builder setFlags(@RequestFlags int flags) {
mFlags |= flags;
return this;
}
/**
* Returns a new {@link DeviceStateRequest} object whose state matches the state set on the
* builder.
*/
@NonNull
public DeviceStateRequest build() {
return new DeviceStateRequest(mRequestedState, mFlags);
}
}
/** Callback to track the status of a request. */
public interface Callback {
/**
* Called to indicate the request has become active and the device state will match the
* requested state.
* <p>
* Guaranteed to be called after a call to
* {@link DeviceStateManager.DeviceStateListener#onDeviceStateChanged(int)} with a state
* matching the requested state.
*/
default void onRequestActivated(@NonNull DeviceStateRequest request) {}
/**
* Called to indicate the request has been temporarily suspended.
* <p>
* Guaranteed to be called before a call to
* {@link DeviceStateManager.DeviceStateListener#onDeviceStateChanged(int)}.
*/
default void onRequestSuspended(@NonNull DeviceStateRequest request) {}
/**
* Called to indicate the request has been canceled. The request can be resubmitted with
* another call to {@link DeviceStateManager#requestState(DeviceStateRequest, Executor,
* DeviceStateRequest.Callback)}.
* <p>
* Guaranteed to be called before a call to
* {@link DeviceStateManager.DeviceStateListener#onDeviceStateChanged(int)}.
* <p>
* Note: A call to {@link #onRequestSuspended(DeviceStateRequest)} is not guaranteed to
* occur before this method.
*/
default void onRequestCanceled(@NonNull DeviceStateRequest request) {}
}
private final int mRequestedState;
@RequestFlags
private final int mFlags;
private DeviceStateRequest(int requestedState, @RequestFlags int flags) {
mRequestedState = requestedState;
mFlags = flags;
}
public int getState() {
return mRequestedState;
}
@RequestFlags
public int getFlags() {
return mFlags;
}
}

View File

@@ -20,5 +20,45 @@ import android.hardware.devicestate.IDeviceStateManagerCallback;
/** @hide */
interface IDeviceStateManager {
/**
* Registers a callback to receive notifications from the device state manager. Only one
* callback can be registered per-process.
* <p>
* As the callback mechanism is used to alert the caller of changes to request status a callback
* <b>MUST</b> be registered before calling {@link #requestState(IBinder, int, int)} or
* {@link #cancelRequest(IBinder)}. Otherwise an exception will be thrown.
*
* @throws SecurityException if a callback is already registered for the calling process.
*/
void registerCallback(in IDeviceStateManagerCallback callback);
/** Returns the array of supported device state identifiers. */
int[] getSupportedDeviceStates();
/**
* Requests that the device enter the supplied {@code state}. A callback <b>MUST</b> have been
* previously registered with {@link #registerCallback(IDeviceStateManagerCallback)} before a
* call to this method.
*
* @param token the request token previously registered with
* {@link #requestState(IBinder, int, int)}
*
* @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.
*/
void requestState(IBinder token, int state, int flags);
/**
* Cancels a request previously submitted with a call to
* {@link #requestState(IBinder, int, int)}.
*
* @param token the request token previously registered with
* {@link #requestState(IBinder, int, int)}
*
* @throws IllegalStateException if the supplied {@code token} has not been previously
* requested.
*/
void cancelRequest(IBinder token);
}

View File

@@ -18,5 +18,42 @@ package android.hardware.devicestate;
/** @hide */
interface IDeviceStateManagerCallback {
/**
* Called in response to a change in device state. Guaranteed to be called once with the initial
* value on registration of the callback.
*
* @param deviceState the new state of the device.
*/
oneway void onDeviceStateChanged(int deviceState);
/**
* Called to notify the callback that a request has become active. Guaranteed to be called
* after a subsequent call to {@link #onDeviceStateChanged(int)} if the request becoming active
* resulted in a device state change.
*
* @param token the request token previously registered with
* {@link IDeviceStateManager#requestState(IBinder, int, int)}
*/
oneway void onRequestActive(IBinder token);
/**
* Called to notify the callback that a request has become suspended. Guaranteed to be called
* before a subsequent call to {@link #onDeviceStateChanged(int)} if the request becoming
* suspended resulted in a device state change.
*
* @param token the request token previously registered with
* {@link IDeviceStateManager#requestState(IBinder, int, int)}
*/
oneway void onRequestSuspended(IBinder token);
/**
* Called to notify the callback that a request has become canceled. No further callbacks will
* be triggered for this request. Guaranteed to be called before a subsequent call to
* {@link #onDeviceStateChanged(int)} if the request becoming canceled resulted in a device
* state change.
*
* @param token the request token previously registered with
* {@link IDeviceStateManager#requestState(IBinder, int, int)}
*/
oneway void onRequestCanceled(IBinder token);
}

View File

@@ -5427,7 +5427,7 @@
<permission android:name="android.permission.INPUT_CONSUMER"
android:protectionLevel="signature" />
<!-- @hide Allows an application to control the system's device state managed by the
<!-- @hide @TestApi Allows an application to control the system's device state managed by the
{@link android.service.devicestate.DeviceStateManagerService}. For example, on foldable
devices this would grant access to toggle between the folded and unfolded states. -->
<permission android:name="android.permission.CONTROL_DEVICE_STATE"

View File

@@ -17,8 +17,11 @@
package android.hardware.devicestate;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import android.annotation.Nullable;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.test.filters.SmallTest;
@@ -30,6 +33,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
@@ -41,6 +45,9 @@ import java.util.Set;
@RunWith(JUnit4.class)
@SmallTest
public final class DeviceStateManagerGlobalTest {
private static final int DEFAULT_DEVICE_STATE = 0;
private static final int OTHER_DEVICE_STATE = 1;
private TestDeviceStateManagerService mService;
private DeviceStateManagerGlobal mDeviceStateManagerGlobal;
@@ -52,7 +59,7 @@ public final class DeviceStateManagerGlobalTest {
@Test
public void registerListener() {
mService.setDeviceState(0);
mService.setBaseState(DEFAULT_DEVICE_STATE);
TestDeviceStateListener listener1 = new TestDeviceStateListener();
TestDeviceStateListener listener2 = new TestDeviceStateListener();
@@ -61,28 +68,58 @@ public final class DeviceStateManagerGlobalTest {
ConcurrentUtils.DIRECT_EXECUTOR);
mDeviceStateManagerGlobal.registerDeviceStateListener(listener2,
ConcurrentUtils.DIRECT_EXECUTOR);
assertEquals(0, listener1.getLastReportedState().intValue());
assertEquals(0, listener2.getLastReportedState().intValue());
assertEquals(DEFAULT_DEVICE_STATE, listener1.getLastReportedState().intValue());
assertEquals(DEFAULT_DEVICE_STATE, listener2.getLastReportedState().intValue());
mService.setDeviceState(1);
assertEquals(1, listener1.getLastReportedState().intValue());
assertEquals(1, listener2.getLastReportedState().intValue());
mService.setBaseState(OTHER_DEVICE_STATE);
assertEquals(OTHER_DEVICE_STATE, listener1.getLastReportedState().intValue());
assertEquals(OTHER_DEVICE_STATE, listener2.getLastReportedState().intValue());
}
@Test
public void unregisterListener() {
mService.setDeviceState(0);
mService.setBaseState(DEFAULT_DEVICE_STATE);
TestDeviceStateListener listener = new TestDeviceStateListener();
mDeviceStateManagerGlobal.registerDeviceStateListener(listener,
ConcurrentUtils.DIRECT_EXECUTOR);
assertEquals(0, listener.getLastReportedState().intValue());
assertEquals(DEFAULT_DEVICE_STATE, listener.getLastReportedState().intValue());
mDeviceStateManagerGlobal.unregisterDeviceStateListener(listener);
mService.setDeviceState(1);
assertEquals(0, listener.getLastReportedState().intValue());
mService.setBaseState(OTHER_DEVICE_STATE);
assertEquals(DEFAULT_DEVICE_STATE, listener.getLastReportedState().intValue());
}
@Test
public void submittingRequestRegisteredCallback() {
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() {
mService.setBaseState(DEFAULT_DEVICE_STATE);
TestDeviceStateListener listener = new TestDeviceStateListener();
mDeviceStateManagerGlobal.registerDeviceStateListener(listener,
ConcurrentUtils.DIRECT_EXECUTOR);
assertEquals(DEFAULT_DEVICE_STATE, listener.getLastReportedState().intValue());
DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */);
assertEquals(OTHER_DEVICE_STATE, listener.getLastReportedState().intValue());
mDeviceStateManagerGlobal.cancelRequest(request);
assertEquals(DEFAULT_DEVICE_STATE, listener.getLastReportedState().intValue());
}
private final class TestDeviceStateListener implements DeviceStateManager.DeviceStateListener {
@@ -100,8 +137,23 @@ public final class DeviceStateManagerGlobalTest {
}
}
private final class TestDeviceStateManagerService extends IDeviceStateManager.Stub {
private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
private static final class TestDeviceStateManagerService extends IDeviceStateManager.Stub {
public static final class Request {
public final IBinder token;
public final int state;
public final int flags;
private Request(IBinder token, int state, int flags) {
this.token = token;
this.state = state;
this.flags = flags;
}
}
private int mBaseState = DEFAULT_DEVICE_STATE;
private int mMergedState = DEFAULT_DEVICE_STATE;
private ArrayList<Request> mRequests = new ArrayList<>();
private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>();
@Override
@@ -112,19 +164,86 @@ public final class DeviceStateManagerGlobalTest {
mCallbacks.add(callback);
try {
callback.onDeviceStateChanged(mDeviceState);
callback.onDeviceStateChanged(mMergedState);
} catch (RemoteException e) {
// Do nothing. Should never happen.
}
}
public void setDeviceState(int deviceState) {
boolean stateChanged = mDeviceState != deviceState;
mDeviceState = deviceState;
if (stateChanged) {
@Override
public int[] getSupportedDeviceStates() {
return new int[] { DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE };
}
@Override
public void requestState(IBinder token, int state, int flags) {
if (!mRequests.isEmpty()) {
final Request topRequest = mRequests.get(mRequests.size() - 1);
for (IDeviceStateManagerCallback callback : mCallbacks) {
try {
callback.onDeviceStateChanged(mDeviceState);
callback.onRequestSuspended(topRequest.token);
} catch (RemoteException e) {
// Do nothing. Should never happen.
}
}
}
final Request request = new Request(token, state, flags);
mRequests.add(request);
notifyStateChangedIfNeeded();
for (IDeviceStateManagerCallback callback : mCallbacks) {
try {
callback.onRequestActive(token);
} catch (RemoteException e) {
// Do nothing. Should never happen.
}
}
}
@Override
public void cancelRequest(IBinder token) {
int index = -1;
for (int i = 0; i < mRequests.size(); i++) {
if (mRequests.get(i).token.equals(token)) {
index = i;
break;
}
}
if (index == -1) {
throw new IllegalArgumentException("Unknown request: " + token);
}
mRequests.remove(index);
for (IDeviceStateManagerCallback callback : mCallbacks) {
try {
callback.onRequestCanceled(token);
} catch (RemoteException e) {
// Do nothing. Should never happen.
}
}
notifyStateChangedIfNeeded();
}
public void setBaseState(int state) {
mBaseState = state;
notifyStateChangedIfNeeded();
}
private void notifyStateChangedIfNeeded() {
final int originalMergedState = mMergedState;
if (!mRequests.isEmpty()) {
mMergedState = mRequests.get(mRequests.size() - 1).state;
} else {
mMergedState = mBaseState;
}
if (mMergedState != originalMergedState) {
for (IDeviceStateManagerCallback callback : mCallbacks) {
try {
callback.onDeviceStateChanged(mMergedState);
} catch (RemoteException e) {
// Do nothing. Should never happen.
}

View File

@@ -16,8 +16,6 @@
package com.android.server.devicestate;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -37,16 +35,16 @@ import java.util.Objects;
*/
public final class DeviceState {
/** Unique identifier for the device state. */
@IntRange(from = INVALID_DEVICE_STATE)
@IntRange(from = 0)
private final int mIdentifier;
/** String description of the device state. */
@NonNull
private final String mName;
public DeviceState(@IntRange(from = INVALID_DEVICE_STATE) int identifier,
public DeviceState(@IntRange(from = 0) int identifier,
@NonNull String name) {
if (identifier != INVALID_DEVICE_STATE && identifier < 0) {
if (identifier < 0) {
throw new IllegalArgumentException("Identifier must be greater than or equal to zero.");
}
mIdentifier = identifier;
@@ -54,7 +52,7 @@ public final class DeviceState {
}
/** Returns the unique identifier for the device state. */
@IntRange(from = INVALID_DEVICE_STATE)
@IntRange(from = 0)
public int getIdentifier() {
return mIdentifier;
}

View File

@@ -17,13 +17,13 @@
package com.android.server.devicestate;
import static android.Manifest.permission.CONTROL_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.IDeviceStateManager;
import android.hardware.devicestate.IDeviceStateManagerCallback;
import android.os.Binder;
@@ -31,6 +31,8 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
@@ -62,8 +64,12 @@ import java.util.Optional;
* the {@link DeviceStateProvider} to modify the current device state and communicating with the
* {@link DeviceStatePolicy policy} to ensure the system is configured to match the requested state.
* </p>
* The service also provides the {@link DeviceStateManager} API allowing clients to listen for
* changes in device state and submit requests to override the device state provided by the
* {@link DeviceStateProvider}.
*
* @see DeviceStatePolicy
* @see DeviceStateManager
*/
public final class DeviceStateManagerService extends SystemService {
private static final String TAG = "DeviceStateManagerService";
@@ -79,11 +85,11 @@ public final class DeviceStateManagerService extends SystemService {
@GuardedBy("mLock")
private SparseArray<DeviceState> mDeviceStates = new SparseArray<>();
// The current committed device state. The default of INVALID_DEVICE_STATE will be replaced by
// The current committed device state. The default of UNSET will be replaced by
// the current state after the initial callback from the DeviceStateProvider.
@GuardedBy("mLock")
@NonNull
private DeviceState mCommittedState = new DeviceState(INVALID_DEVICE_STATE, "INVALID");
private DeviceState mCommittedState = new DeviceState(0, "UNSET");
// The device state that is currently awaiting callback from the policy to be committed.
@GuardedBy("mLock")
@NonNull
@@ -91,19 +97,23 @@ public final class DeviceStateManagerService extends SystemService {
// Whether or not the policy is currently waiting to be notified of the current pending state.
@GuardedBy("mLock")
private boolean mIsPolicyWaitingForState = false;
// The device state that is currently requested and is next to be configured and committed.
// Can be overwritten by an override state value if requested.
@GuardedBy("mLock")
@NonNull
private Optional<DeviceState> mRequestedState = Optional.empty();
// The most recently requested override state, or empty if no override is requested.
@GuardedBy("mLock")
@NonNull
private Optional<DeviceState> mRequestedOverrideState = Optional.empty();
// List of registered callbacks indexed by process id.
// The device state that is set by the device state provider.
@GuardedBy("mLock")
private final SparseArray<CallbackRecord> mCallbacks = new SparseArray<>();
@NonNull
private Optional<DeviceState> mBaseState = Optional.empty();
// List of processes registered to receive notifications about changes to device state and
// request status indexed by process id.
@GuardedBy("mLock")
private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>();
// List of override requests with the highest precedence request at the end.
@GuardedBy("mLock")
private final ArrayList<OverrideRequestRecord> mRequestRecords = new ArrayList<>();
// Set of override requests that are pending a call to notifyStatusIfNeeded() to be notified
// of a change in status.
@GuardedBy("mLock")
private final ArraySet<OverrideRequestRecord> mRequestsPendingStatusChange = new ArraySet<>();
public DeviceStateManagerService(@NonNull Context context) {
this(context, new DeviceStatePolicyImpl(context));
@@ -148,55 +158,32 @@ public final class DeviceStateManagerService extends SystemService {
}
/**
* Returns the requested state. The service will configure the device to match the requested
* state when possible.
* Returns the base state. The service will configure the device to match the base state when
* there is no active request to override the base state.
*
* @see #getOverrideState()
*/
@NonNull
Optional<DeviceState> getRequestedState() {
Optional<DeviceState> getBaseState() {
synchronized (mLock) {
return mRequestedState;
return mBaseState;
}
}
/**
* Overrides the current device state with the provided state.
*
* @return {@code true} if the override state is valid and supported, {@code false} otherwise.
*/
boolean setOverrideState(int overrideState) {
if (getContext().checkCallingOrSelfPermission(CONTROL_DEVICE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Must hold permission " + CONTROL_DEVICE_STATE);
}
synchronized (mLock) {
if (overrideState != INVALID_DEVICE_STATE && !isSupportedStateLocked(overrideState)) {
return false;
}
mRequestedOverrideState = getStateLocked(overrideState);
updatePendingStateLocked();
}
notifyPolicyIfNeeded();
return true;
}
/**
* Clears an override state set with {@link #setOverrideState(int)}.
*/
void clearOverrideState() {
setOverrideState(INVALID_DEVICE_STATE);
}
/**
* Returns the current requested override state, or {@link Optional#empty()} if no override
* state is requested.
* Returns the current override state, or {@link Optional#empty()} if no override state is
* requested. If an override states is present, the returned state will take precedence over
* the base state returned from {@link #getBaseState()}.
*/
@NonNull
Optional<DeviceState> getOverrideState() {
synchronized (mLock) {
return mRequestedOverrideState;
if (mRequestRecords.isEmpty()) {
return Optional.empty();
}
OverrideRequestRecord topRequest = mRequestRecords.get(mRequestRecords.size() - 1);
return Optional.of(topRequest.mRequestedState);
}
}
@@ -211,6 +198,17 @@ public final class DeviceStateManagerService extends SystemService {
}
}
/** Returns the list of currently supported device state identifiers. */
private int[] getSupportedStateIdentifiers() {
synchronized (mLock) {
int[] supportedStates = new int[mDeviceStates.size()];
for (int i = 0; i < supportedStates.length; i++) {
supportedStates[i] = mDeviceStates.valueAt(i).getIdentifier();
}
return supportedStates;
}
}
@VisibleForTesting
IDeviceStateManager getBinderService() {
return mBinderService;
@@ -224,22 +222,26 @@ public final class DeviceStateManagerService extends SystemService {
mDeviceStates.put(state.getIdentifier(), state);
}
if (mRequestedState.isPresent()
&& !isSupportedStateLocked(mRequestedState.get().getIdentifier())) {
// The current requested state is no longer valid. We'll clear it here, though
if (mBaseState.isPresent()
&& !isSupportedStateLocked(mBaseState.get().getIdentifier())) {
// The current base state is no longer valid. We'll clear it here, though
// we won't actually update the current state until a callback comes from the
// provider with the most recent state.
mRequestedState = Optional.empty();
mBaseState = Optional.empty();
}
if (mRequestedOverrideState.isPresent()
&& !isSupportedStateLocked(mRequestedOverrideState.get().getIdentifier())) {
// The current override state is no longer valid. We'll clear it here and update
// the committed state if necessary.
mRequestedOverrideState = Optional.empty();
final int requestSize = mRequestRecords.size();
for (int i = 0; i < requestSize; i++) {
OverrideRequestRecord request = mRequestRecords.get(i);
if (!isSupportedStateLocked(request.mRequestedState.getIdentifier())) {
request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED);
}
}
updatePendingStateLocked();
}
notifyRequestsOfStatusChangeIfNeeded();
notifyPolicyIfNeeded();
}
@@ -261,20 +263,37 @@ public final class DeviceStateManagerService extends SystemService {
}
/**
* Requests that the system enter the provided {@code state}. The request may not be honored
* under certain conditions, for example if the provided state is not supported.
* Requests to set the base state. The request may not be honored under certain conditions, for
* example if the provided state is not supported.
*
* @see #isSupportedStateLocked(int)
*/
private void requestState(int identifier) {
private void setBaseState(int identifier) {
synchronized (mLock) {
final Optional<DeviceState> requestedState = getStateLocked(identifier);
if (requestedState.isPresent()) {
mRequestedState = requestedState;
if (mBaseState.isPresent() && mBaseState.get().getIdentifier() == identifier) {
// Base state hasn't changed. Nothing to do.
return;
}
final Optional<DeviceState> baseState = getStateLocked(identifier);
if (!baseState.isPresent()) {
throw new IllegalArgumentException("Base state is not supported");
}
mBaseState = baseState;
final int requestSize = mRequestRecords.size();
for (int i = 0; i < requestSize; i++) {
OverrideRequestRecord request = mRequestRecords.get(i);
if ((request.mFlags & FLAG_CANCEL_WHEN_BASE_CHANGES) > 0) {
request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED);
}
}
updatePendingStateLocked();
}
notifyRequestsOfStatusChangeIfNeeded();
notifyPolicyIfNeeded();
}
@@ -290,10 +309,10 @@ public final class DeviceStateManagerService extends SystemService {
}
final DeviceState stateToConfigure;
if (mRequestedOverrideState.isPresent()) {
stateToConfigure = mRequestedOverrideState.get();
if (!mRequestRecords.isEmpty()) {
stateToConfigure = mRequestRecords.get(mRequestRecords.size() - 1).mRequestedState;
} else {
stateToConfigure = mRequestedState.orElse(null);
stateToConfigure = mBaseState.orElse(null);
}
if (stateToConfigure == null) {
@@ -360,6 +379,13 @@ public final class DeviceStateManagerService extends SystemService {
}
mCommittedState = mPendingState.get();
newState = mCommittedState.getIdentifier();
if (!mRequestRecords.isEmpty()) {
final OverrideRequestRecord topRequest =
mRequestRecords.get(mRequestRecords.size() - 1);
topRequest.setStatusLocked(OverrideRequestRecord.STATUS_ACTIVE);
}
mPendingState = Optional.empty();
updatePendingStateLocked();
}
@@ -367,6 +393,9 @@ public final class DeviceStateManagerService extends SystemService {
// Notify callbacks of a change.
notifyDeviceStateChanged(newState);
// Notify the top request that it's active.
notifyRequestsOfStatusChangeIfNeeded();
// Try to configure the next state if needed.
notifyPolicyIfNeeded();
}
@@ -377,43 +406,69 @@ public final class DeviceStateManagerService extends SystemService {
"Attempting to notify callbacks with service lock held.");
}
// Grab the lock and copy the callbacks.
ArrayList<CallbackRecord> callbacks;
// Grab the lock and copy the process records.
ArrayList<ProcessRecord> registeredProcesses;
synchronized (mLock) {
if (mCallbacks.size() == 0) {
if (mProcessRecords.size() == 0) {
return;
}
callbacks = new ArrayList<>();
for (int i = 0; i < mCallbacks.size(); i++) {
callbacks.add(mCallbacks.valueAt(i));
registeredProcesses = new ArrayList<>();
for (int i = 0; i < mProcessRecords.size(); i++) {
registeredProcesses.add(mProcessRecords.valueAt(i));
}
}
// After releasing the lock, send the notifications out.
for (int i = 0; i < callbacks.size(); i++) {
callbacks.get(i).notifyDeviceStateAsync(deviceState);
for (int i = 0; i < registeredProcesses.size(); i++) {
registeredProcesses.get(i).notifyDeviceStateAsync(deviceState);
}
}
private void registerCallbackInternal(IDeviceStateManagerCallback callback, int callingPid) {
/**
* Notifies all dirty requests (requests that have a change in status, but have not yet been
* notified) that their status has changed.
*/
private void notifyRequestsOfStatusChangeIfNeeded() {
if (Thread.holdsLock(mLock)) {
throw new IllegalStateException(
"Attempting to notify requests with service lock held.");
}
ArraySet<OverrideRequestRecord> dirtyRequests;
synchronized (mLock) {
if (mRequestsPendingStatusChange.isEmpty()) {
return;
}
dirtyRequests = new ArraySet<>(mRequestsPendingStatusChange);
mRequestsPendingStatusChange.clear();
}
// After releasing the lock, send the notifications out.
for (int i = 0; i < dirtyRequests.size(); i++) {
dirtyRequests.valueAt(i).notifyStatusIfNeeded();
}
}
private void registerProcess(int pid, IDeviceStateManagerCallback callback) {
int currentState;
CallbackRecord record;
ProcessRecord record;
// Grab the lock to register the callback and get the current state.
synchronized (mLock) {
if (mCallbacks.contains(callingPid)) {
if (mProcessRecords.contains(pid)) {
throw new SecurityException("The calling process has already registered an"
+ " IDeviceStateManagerCallback.");
}
record = new CallbackRecord(callback, callingPid);
record = new ProcessRecord(callback, pid);
try {
callback.asBinder().linkToDeath(record, 0);
} catch (RemoteException ex) {
throw new RuntimeException(ex);
}
mCallbacks.put(callingPid, record);
mProcessRecords.put(pid, record);
currentState = mCommittedState.getIdentifier();
}
@@ -421,10 +476,86 @@ public final class DeviceStateManagerService extends SystemService {
record.notifyDeviceStateAsync(currentState);
}
private void unregisterCallbackInternal(CallbackRecord record) {
private void handleProcessDied(ProcessRecord processRecord) {
synchronized (mLock) {
mCallbacks.remove(record.mPid);
// Cancel all requests from this process.
final int requestCount = processRecord.mRequestRecords.size();
for (int i = 0; i < requestCount; i++) {
final OverrideRequestRecord request = processRecord.mRequestRecords.valueAt(i);
// Cancel the request but don't mark it as dirty since there's no need to send
// notifications if the process has died.
request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED,
false /* markDirty */);
}
mProcessRecords.remove(processRecord.mPid);
updatePendingStateLocked();
}
notifyPolicyIfNeeded();
}
private void requestStateInternal(int state, int flags, int callingPid,
@NonNull IBinder token) {
synchronized (mLock) {
final ProcessRecord processRecord = mProcessRecords.get(callingPid);
if (processRecord == null) {
throw new IllegalStateException("Process " + callingPid
+ " has no registered callback.");
}
if (processRecord.mRequestRecords.get(token) != null) {
throw new IllegalStateException("Request has already been made for the supplied"
+ " token: " + token);
}
final Optional<DeviceState> deviceState = getStateLocked(state);
if (!deviceState.isPresent()) {
throw new IllegalArgumentException("Requested state: " + state
+ " is not supported.");
}
OverrideRequestRecord topRecord = mRequestRecords.isEmpty()
? null : mRequestRecords.get(mRequestRecords.size() - 1);
if (topRecord != null) {
topRecord.setStatusLocked(OverrideRequestRecord.STATUS_SUSPENDED);
}
final OverrideRequestRecord request =
new OverrideRequestRecord(processRecord, token, deviceState.get(), flags);
mRequestRecords.add(request);
processRecord.mRequestRecords.put(request.mToken, request);
// We don't set the status of the new request to ACTIVE here as it will be set in
// commitPendingState().
updatePendingStateLocked();
}
notifyRequestsOfStatusChangeIfNeeded();
notifyPolicyIfNeeded();
}
private void cancelRequestInternal(int callingPid, @NonNull IBinder token) {
synchronized (mLock) {
final ProcessRecord processRecord = mProcessRecords.get(callingPid);
if (processRecord == null) {
throw new IllegalStateException("Process " + callingPid
+ " has no registered callback.");
}
OverrideRequestRecord request = processRecord.mRequestRecords.get(token);
if (request == null) {
throw new IllegalStateException("No known request for the given token");
}
request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED);
updatePendingStateLocked();
}
notifyRequestsOfStatusChangeIfNeeded();
notifyPolicyIfNeeded();
}
private void dumpInternal(PrintWriter pw) {
@@ -433,15 +564,26 @@ public final class DeviceStateManagerService extends SystemService {
synchronized (mLock) {
pw.println(" mCommittedState=" + mCommittedState);
pw.println(" mPendingState=" + mPendingState);
pw.println(" mRequestedState=" + mRequestedState);
pw.println(" mRequestedOverrideState=" + mRequestedOverrideState);
pw.println(" mBaseState=" + mBaseState);
pw.println(" mOverrideState=" + getOverrideState());
final int callbackCount = mCallbacks.size();
final int processCount = mProcessRecords.size();
pw.println();
pw.println("Callbacks: size=" + callbackCount);
for (int i = 0; i < callbackCount; i++) {
CallbackRecord callback = mCallbacks.valueAt(i);
pw.println(" " + i + ": mPid=" + callback.mPid);
pw.println("Registered processes: size=" + processCount);
for (int i = 0; i < processCount; i++) {
ProcessRecord processRecord = mProcessRecords.valueAt(i);
pw.println(" " + i + ": mPid=" + processRecord.mPid);
}
final int requestCount = mRequestRecords.size();
pw.println();
pw.println("Override requests: size=" + requestCount);
for (int i = 0; i < requestCount; i++) {
OverrideRequestRecord requestRecord = mRequestRecords.get(i);
pw.println(" " + i + ": mPid=" + requestRecord.mProcessRecord.mPid
+ ", mRequestedState=" + requestRecord.mRequestedState
+ ", mFlags=" + requestRecord.mFlags
+ ", mStatus=" + requestRecord.statusToString(requestRecord.mStatus));
}
}
}
@@ -452,12 +594,6 @@ public final class DeviceStateManagerService extends SystemService {
if (newDeviceStates.length == 0) {
throw new IllegalArgumentException("Supported device states must not be empty");
}
for (int i = 0; i < newDeviceStates.length; i++) {
if (newDeviceStates[i].getIdentifier() == INVALID_DEVICE_STATE) {
throw new IllegalArgumentException(
"Supported device states includes INVALID_DEVICE_STATE identifier");
}
}
updateSupportedStates(newDeviceStates);
}
@@ -467,22 +603,24 @@ public final class DeviceStateManagerService extends SystemService {
throw new IllegalArgumentException("Invalid identifier: " + identifier);
}
requestState(identifier);
setBaseState(identifier);
}
}
private final class CallbackRecord implements IBinder.DeathRecipient {
private final class ProcessRecord implements IBinder.DeathRecipient {
private final IDeviceStateManagerCallback mCallback;
private final int mPid;
CallbackRecord(IDeviceStateManagerCallback callback, int pid) {
private final ArrayMap<IBinder, OverrideRequestRecord> mRequestRecords = new ArrayMap<>();
ProcessRecord(IDeviceStateManagerCallback callback, int pid) {
mCallback = callback;
mPid = pid;
}
@Override
public void binderDied() {
unregisterCallbackInternal(this);
handleProcessDied(this);
}
public void notifyDeviceStateAsync(int devicestate) {
@@ -493,6 +631,119 @@ public final class DeviceStateManagerService extends SystemService {
ex);
}
}
public void notifyRequestActiveAsync(OverrideRequestRecord request) {
try {
mCallback.onRequestActive(request.mToken);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
ex);
}
}
public void notifyRequestSuspendedAsync(OverrideRequestRecord request) {
try {
mCallback.onRequestSuspended(request.mToken);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
ex);
}
}
public void notifyRequestCanceledAsync(OverrideRequestRecord request) {
try {
mCallback.onRequestCanceled(request.mToken);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
ex);
}
}
}
/** A record describing a request to override the state of the device. */
private final class OverrideRequestRecord {
public static final int STATUS_UNKNOWN = 0;
public static final int STATUS_ACTIVE = 1;
public static final int STATUS_SUSPENDED = 2;
public static final int STATUS_CANCELED = 3;
@Nullable
public String statusToString(int status) {
switch (status) {
case STATUS_ACTIVE:
return "ACTIVE";
case STATUS_SUSPENDED:
return "SUSPENDED";
case STATUS_CANCELED:
return "CANCELED";
case STATUS_UNKNOWN:
return "UNKNOWN";
default:
return null;
}
}
private final ProcessRecord mProcessRecord;
@NonNull
private final IBinder mToken;
@NonNull
private final DeviceState mRequestedState;
private final int mFlags;
private int mStatus = STATUS_UNKNOWN;
private int mLastNotifiedStatus = STATUS_UNKNOWN;
OverrideRequestRecord(@NonNull ProcessRecord processRecord, @NonNull IBinder token,
@NonNull DeviceState requestedState, int flags) {
mProcessRecord = processRecord;
mToken = token;
mRequestedState = requestedState;
mFlags = flags;
}
public void setStatusLocked(int status) {
setStatusLocked(status, true /* markDirty */);
}
public void setStatusLocked(int status, boolean markDirty) {
if (mStatus != status) {
if (mStatus == STATUS_CANCELED) {
throw new IllegalStateException(
"Can not alter the status of a request after set to CANCELED.");
}
mStatus = status;
if (mStatus == STATUS_CANCELED) {
mRequestRecords.remove(this);
mProcessRecord.mRequestRecords.remove(mToken);
}
if (markDirty) {
mRequestsPendingStatusChange.add(this);
}
}
}
public void notifyStatusIfNeeded() {
int stateToReport;
synchronized (mLock) {
if (mLastNotifiedStatus == mStatus) {
return;
}
stateToReport = mStatus;
mLastNotifiedStatus = mStatus;
}
if (stateToReport == STATUS_ACTIVE) {
mProcessRecord.notifyRequestActiveAsync(this);
} else if (stateToReport == STATUS_SUSPENDED) {
mProcessRecord.notifyRequestSuspendedAsync(this);
} else if (stateToReport == STATUS_CANCELED) {
mProcessRecord.notifyRequestCanceledAsync(this);
}
}
}
/** Implementation of {@link IDeviceStateManager} published as a binder service. */
@@ -506,12 +757,58 @@ public final class DeviceStateManagerService extends SystemService {
final int callingPid = Binder.getCallingPid();
final long token = Binder.clearCallingIdentity();
try {
registerCallbackInternal(callback, callingPid);
registerProcess(callingPid, callback);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override // Binder call
public int[] getSupportedDeviceStates() {
final long token = Binder.clearCallingIdentity();
try {
return getSupportedStateIdentifiers();
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override // Binder call
public void requestState(IBinder token, int state, int flags) {
getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
"Permission required to request device state.");
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);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
}
@Override // Binder call
public void cancelRequest(IBinder token) {
getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
"Permission required to clear requested device state.");
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);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
}
@Override // Binder call
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver result) {

View File

@@ -16,6 +16,13 @@
package com.android.server.devicestate;
import static android.Manifest.permission.CONTROL_DEVICE_STATE;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateRequest;
import android.os.Binder;
import android.os.ShellCommand;
import java.io.PrintWriter;
@@ -27,10 +34,15 @@ import java.util.Optional;
* Use with {@code adb shell cmd device_state ...}.
*/
public class DeviceStateManagerShellCommand extends ShellCommand {
private final DeviceStateManagerService mInternal;
@Nullable
private static DeviceStateRequest sLastRequest;
private final DeviceStateManagerService mService;
private final DeviceStateManager mClient;
public DeviceStateManagerShellCommand(DeviceStateManagerService service) {
mInternal = service;
mService = service;
mClient = service.getContext().getSystemService(DeviceStateManager.class);
}
@Override
@@ -51,15 +63,15 @@ public class DeviceStateManagerShellCommand extends ShellCommand {
}
private void printState(PrintWriter pw) {
DeviceState committedState = mInternal.getCommittedState();
Optional<DeviceState> requestedState = mInternal.getRequestedState();
Optional<DeviceState> requestedOverrideState = mInternal.getOverrideState();
DeviceState committedState = mService.getCommittedState();
Optional<DeviceState> baseState = mService.getBaseState();
Optional<DeviceState> overrideState = mService.getOverrideState();
pw.println("Committed state: " + committedState);
if (requestedOverrideState.isPresent()) {
if (overrideState.isPresent()) {
pw.println("----------------------");
pw.println("Base state: " + requestedState.orElse(null));
pw.println("Override state: " + requestedOverrideState.get());
pw.println("Base state: " + baseState.orElse(null));
pw.println("Override state: " + overrideState.get());
}
}
@@ -67,32 +79,51 @@ public class DeviceStateManagerShellCommand extends ShellCommand {
final String nextArg = getNextArg();
if (nextArg == null) {
printState(pw);
} else if ("reset".equals(nextArg)) {
mInternal.clearOverrideState();
} else {
int requestedState;
try {
requestedState = Integer.parseInt(nextArg);
} catch (NumberFormatException e) {
getErrPrintWriter().println("Error: requested state should be an integer");
return -1;
}
boolean success = mInternal.setOverrideState(requestedState);
if (!success) {
getErrPrintWriter().println("Error: failed to set override state. Run:");
getErrPrintWriter().println("");
getErrPrintWriter().println(" print-states");
getErrPrintWriter().println("");
getErrPrintWriter().println("to get the list of currently supported device states");
return -1;
}
}
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)) {
if (sLastRequest != null) {
mClient.cancelRequest(sLastRequest);
sLastRequest = null;
}
} else {
int requestedState = Integer.parseInt(nextArg);
DeviceStateRequest request = DeviceStateRequest.newBuilder(requestedState).build();
mClient.requestState(request, null /* executor */, null /* callback */);
if (sLastRequest != null) {
mClient.cancelRequest(sLastRequest);
}
sLastRequest = 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 runPrintStates(PrintWriter pw) {
DeviceState[] states = mInternal.getSupportedStates();
DeviceState[] states = mService.getSupportedStates();
pw.print("Supported states: [\n");
for (int i = 0; i < states.length; i++) {
pw.print(" " + states[i] + ",\n");

View File

@@ -514,8 +514,8 @@ public final class DisplayManagerService extends SystemService {
DeviceStateManager deviceStateManager =
mContext.getSystemService(DeviceStateManager.class);
deviceStateManager.registerDeviceStateListener(new DeviceStateListener(),
new HandlerExecutor(mHandler));
deviceStateManager.addDeviceStateListener(new HandlerExecutor(mHandler),
new DeviceStateListener());
scheduleTraversalLocked(false);
}

View File

@@ -74,8 +74,8 @@ class DisplayFoldController {
mHandler = handler;
DeviceStateManager deviceStateManager = context.getSystemService(DeviceStateManager.class);
deviceStateManager.registerDeviceStateListener(new DeviceStateListener(context),
new HandlerExecutor(handler));
deviceStateManager.addDeviceStateListener(new HandlerExecutor(handler),
new DeviceStateListener(context));
}
void finishedGoingToSleep() {

View File

@@ -19,10 +19,14 @@ package com.android.server.devicestate;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertThrows;
import android.hardware.devicestate.DeviceStateRequest;
import android.hardware.devicestate.IDeviceStateManagerCallback;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
@@ -33,6 +37,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.HashMap;
import java.util.Optional;
import javax.annotation.Nullable;
@@ -61,87 +66,69 @@ public final class DeviceStateManagerServiceTest {
}
@Test
public void requestStateChange() {
public void baseStateChanged() {
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
DEFAULT_DEVICE_STATE.getIdentifier());
mProvider.notifyRequestState(OTHER_DEVICE_STATE.getIdentifier());
mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getRequestedState().get(), OTHER_DEVICE_STATE);
assertEquals(mService.getBaseState().get(), OTHER_DEVICE_STATE);
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
OTHER_DEVICE_STATE.getIdentifier());
}
@Test
public void requestStateChange_pendingState() {
public void baseStateChanged_withStatePendingPolicyCallback() {
mPolicy.blockConfigure();
mProvider.notifyRequestState(OTHER_DEVICE_STATE.getIdentifier());
mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getPendingState().get(), OTHER_DEVICE_STATE);
assertEquals(mService.getRequestedState().get(), OTHER_DEVICE_STATE);
assertEquals(mService.getBaseState().get(), OTHER_DEVICE_STATE);
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
OTHER_DEVICE_STATE.getIdentifier());
mProvider.notifyRequestState(DEFAULT_DEVICE_STATE.getIdentifier());
mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier());
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getPendingState().get(), OTHER_DEVICE_STATE);
assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
OTHER_DEVICE_STATE.getIdentifier());
mPolicy.resumeConfigure();
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
DEFAULT_DEVICE_STATE.getIdentifier());
}
@Test
public void requestStateChange_unsupportedState() {
mProvider.notifyRequestState(UNSUPPORTED_DEVICE_STATE.getIdentifier());
public void baseStateChanged_unsupportedState() {
assertThrows(IllegalArgumentException.class, () -> {
mProvider.setState(UNSUPPORTED_DEVICE_STATE.getIdentifier());
});
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
DEFAULT_DEVICE_STATE.getIdentifier());
}
@Test
public void requestStateChange_invalidState() {
public void baseStateChanged_invalidState() {
assertThrows(IllegalArgumentException.class, () -> {
mProvider.notifyRequestState(INVALID_DEVICE_STATE);
mProvider.setState(INVALID_DEVICE_STATE);
});
}
@Test
public void requestOverrideState() {
mService.setOverrideState(OTHER_DEVICE_STATE.getIdentifier());
// Committed state changes as there is a requested override.
assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
OTHER_DEVICE_STATE.getIdentifier());
// Committed state is set back to the requested state once the override is cleared.
mService.clearOverrideState();
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
DEFAULT_DEVICE_STATE.getIdentifier());
}
@Test
public void requestOverrideState_unsupportedState() {
mService.setOverrideState(UNSUPPORTED_DEVICE_STATE.getIdentifier());
// Committed state remains the same as the override state is unsupported.
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
DEFAULT_DEVICE_STATE.getIdentifier());
}
@@ -150,7 +137,7 @@ public final class DeviceStateManagerServiceTest {
public void supportedStatesChanged() {
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE });
@@ -158,46 +145,27 @@ public final class DeviceStateManagerServiceTest {
// supported.
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
}
@Test
public void supportedStatesChanged_unsupportedRequestedState() {
public void supportedStatesChanged_unsupportedBaseState() {
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
mProvider.notifySupportedDeviceStates(new DeviceState[]{ OTHER_DEVICE_STATE });
// The current requested state is cleared because it is no longer supported.
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getRequestedState(), Optional.empty());
assertEquals(mService.getBaseState(), Optional.empty());
mProvider.notifyRequestState(OTHER_DEVICE_STATE.getIdentifier());
mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getRequestedState().get(), OTHER_DEVICE_STATE);
}
@Test
public void supportedStatesChanged_unsupportedOverrideState() {
mService.setOverrideState(OTHER_DEVICE_STATE.getIdentifier());
// Committed state changes as there is a requested override.
assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
OTHER_DEVICE_STATE.getIdentifier());
mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE });
// Committed state is set back to the requested state as the override state is no longer
// supported.
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
DEFAULT_DEVICE_STATE.getIdentifier());
assertEquals(mService.getBaseState().get(), OTHER_DEVICE_STATE);
}
@Test
@@ -205,17 +173,17 @@ public final class DeviceStateManagerServiceTest {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
mProvider.notifyRequestState(OTHER_DEVICE_STATE.getIdentifier());
mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
assertNotNull(callback.getLastNotifiedValue());
assertEquals(callback.getLastNotifiedValue().intValue(),
OTHER_DEVICE_STATE.getIdentifier());
mProvider.notifyRequestState(DEFAULT_DEVICE_STATE.getIdentifier());
mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier());
assertEquals(callback.getLastNotifiedValue().intValue(),
DEFAULT_DEVICE_STATE.getIdentifier());
mPolicy.blockConfigure();
mProvider.notifyRequestState(OTHER_DEVICE_STATE.getIdentifier());
mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
// The callback should not have been notified of the state change as the policy is still
// pending callback.
assertEquals(callback.getLastNotifiedValue().intValue(),
@@ -237,6 +205,148 @@ public final class DeviceStateManagerServiceTest {
DEFAULT_DEVICE_STATE.getIdentifier());
}
@Test
public void getSupportedDeviceStates() throws RemoteException {
final int[] expectedStates = new int[] { 0, 1 };
assertEquals(mService.getBinderService().getSupportedDeviceStates(), expectedStates);
}
@Test
public void requestState() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
final IBinder token = new Binder();
assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_UNKNOWN);
mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
0 /* flags */);
assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_ACTIVE);
// Committed state changes as there is a requested override.
assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getOverrideState().get(), OTHER_DEVICE_STATE);
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
OTHER_DEVICE_STATE.getIdentifier());
mService.getBinderService().cancelRequest(token);
assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_CANCELED);
// Committed state is set back to the requested state once the override is cleared.
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
assertFalse(mService.getOverrideState().isPresent());
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
DEFAULT_DEVICE_STATE.getIdentifier());
}
@Test
public void requestState_flagCancelWhenBaseChanges() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
final IBinder token = new Binder();
assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_UNKNOWN);
mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES);
assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_ACTIVE);
// Committed state changes as there is a requested override.
assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getOverrideState().get(), OTHER_DEVICE_STATE);
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
OTHER_DEVICE_STATE.getIdentifier());
mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
// Request is canceled because the base state changed.
assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_CANCELED);
// Committed state is set back to the requested state once the override is cleared.
assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
assertEquals(mService.getBaseState().get(), OTHER_DEVICE_STATE);
assertFalse(mService.getOverrideState().isPresent());
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
OTHER_DEVICE_STATE.getIdentifier());
}
@Test
public void requestState_becomesUnsupported() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
final IBinder token = new Binder();
assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_UNKNOWN);
mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
0 /* flags */);
assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_ACTIVE);
// Committed state changes as there is a requested override.
assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getOverrideState().get(), OTHER_DEVICE_STATE);
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
OTHER_DEVICE_STATE.getIdentifier());
mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE });
// Request is canceled because the state is no longer supported.
assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_CANCELED);
// Committed state is set back to the requested state as the override state is no longer
// supported.
assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
assertFalse(mService.getOverrideState().isPresent());
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
DEFAULT_DEVICE_STATE.getIdentifier());
}
@Test
public void requestState_unsupportedState() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
assertThrows(IllegalArgumentException.class, () -> {
final IBinder token = new Binder();
mService.getBinderService().requestState(token,
UNSUPPORTED_DEVICE_STATE.getIdentifier(), 0 /* flags */);
});
}
@Test
public void requestState_invalidState() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
assertThrows(IllegalArgumentException.class, () -> {
final IBinder token = new Binder();
mService.getBinderService().requestState(token, INVALID_DEVICE_STATE, 0 /* flags */);
});
}
@Test
public void requestState_beforeRegisteringCallback() {
assertThrows(IllegalStateException.class, () -> {
final IBinder token = new Binder();
mService.getBinderService().requestState(token, DEFAULT_DEVICE_STATE.getIdentifier(),
0 /* flags */);
});
}
private static final class TestDeviceStatePolicy implements DeviceStatePolicy {
private final DeviceStateProvider mProvider;
private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE;
@@ -306,23 +416,48 @@ public final class DeviceStateManagerServiceTest {
mListener.onSupportedDeviceStatesChanged(supportedDeviceStates);
}
public void notifyRequestState(int identifier) {
public void setState(int identifier) {
mListener.onStateChanged(identifier);
}
}
private static final class TestDeviceStateManagerCallback extends
IDeviceStateManagerCallback.Stub {
Integer mLastNotifiedValue;
public static final int STATUS_UNKNOWN = 0;
public static final int STATUS_ACTIVE = 1;
public static final int STATUS_SUSPENDED = 2;
public static final int STATUS_CANCELED = 3;
private Integer mLastNotifiedValue;
private final HashMap<IBinder, Integer> mLastNotifiedStatus = new HashMap<>();
@Override
public void onDeviceStateChanged(int deviceState) {
mLastNotifiedValue = deviceState;
}
@Override
public void onRequestActive(IBinder token) {
mLastNotifiedStatus.put(token, STATUS_ACTIVE);
}
@Override
public void onRequestSuspended(IBinder token) {
mLastNotifiedStatus.put(token, STATUS_SUSPENDED);
}
@Override
public void onRequestCanceled(IBinder token) {
mLastNotifiedStatus.put(token, STATUS_CANCELED);
}
@Nullable
Integer getLastNotifiedValue() {
return mLastNotifiedValue;
}
int getLastNotifiedStatus(IBinder requestToken) {
return mLastNotifiedStatus.getOrDefault(requestToken, STATUS_UNKNOWN);
}
}
}