diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 79917d020198a..7674bfd8827de 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -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 {
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index 29a6ee278d9c3..f175e7b00b7e2 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -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.
+ *
+ * By default, the request is kept active until a call to
+ * {@link #cancelRequest(DeviceStateRequest)} or until one of the following occurs:
+ *
+ * - Another processes submits a request succeeding this request in which case the request
+ * will be suspended until the interrupting request is canceled.
+ *
- The requested state has become unsupported.
+ *
- The process submitting the request dies.
+ *
+ * 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)}.
+ *
+ * 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);
}
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index c8905038d0560..b9ae88ea840f2 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -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 mListeners = new ArrayList<>();
+ @GuardedBy("mLock")
+ private final ArrayMap 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 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));
+ }
+ }
}
diff --git a/core/java/android/hardware/devicestate/DeviceStateRequest.java b/core/java/android/hardware/devicestate/DeviceStateRequest.java
new file mode 100644
index 0000000000000..70f7002597ed4
--- /dev/null
+++ b/core/java/android/hardware/devicestate/DeviceStateRequest.java
@@ -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}.
+ *
+ * Once constructed, a {@link DeviceStateRequest request} can be submitted with a call to
+ * {@link DeviceStateManager#requestState(DeviceStateRequest, Executor,
+ * DeviceStateRequest.Callback)}.
+ *
+ * By default, the request is kept active until a call to
+ * {@link DeviceStateManager#cancelRequest(DeviceStateRequest)} or until one of the following
+ * occurs:
+ *
+ * - Another processes submits a request succeeding this request in which case the request
+ * will be suspended until the interrupting request is canceled.
+ *
- The requested state has become unsupported.
+ *
- The process submitting the request dies.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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)}.
+ *
+ * Guaranteed to be called before a call to
+ * {@link DeviceStateManager.DeviceStateListener#onDeviceStateChanged(int)}.
+ *
+ * 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;
+ }
+}
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
index a157b3311ca57..323ad21e4884f 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
@@ -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.
+ *
+ * As the callback mechanism is used to alert the caller of changes to request status a callback
+ * MUST 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 MUST 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);
}
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl b/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl
index d1c581361b621..ee2a071741ef9 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl
@@ -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);
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index dc81bc9d449d2..827bf7b70cbc9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5427,7 +5427,7 @@
-
mRequests = new ArrayList<>();
+
private Set 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.
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java
index 802472fcfba8d..e496d77deaf5a 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/services/core/java/com/android/server/devicestate/DeviceState.java
@@ -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;
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 375ec3a0f95f8..984a17694e07a 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -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.
*
+ * 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 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 mRequestedState = Optional.empty();
- // The most recently requested override state, or empty if no override is requested.
- @GuardedBy("mLock")
- @NonNull
- private Optional 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 mCallbacks = new SparseArray<>();
+ @NonNull
+ private Optional 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 mProcessRecords = new SparseArray<>();
+ // List of override requests with the highest precedence request at the end.
+ @GuardedBy("mLock")
+ private final ArrayList 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 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 getRequestedState() {
+ Optional 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 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 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 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 callbacks;
+ // Grab the lock and copy the process records.
+ ArrayList 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 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 = 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 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) {
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
index 7914531f99100..6cc55a6c4774f 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
@@ -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 requestedState = mInternal.getRequestedState();
- Optional requestedOverrideState = mInternal.getOverrideState();
+ DeviceState committedState = mService.getCommittedState();
+ Optional baseState = mService.getBaseState();
+ Optional 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");
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e5151d84c33e9..0950d5dd076f1 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -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);
}
diff --git a/services/core/java/com/android/server/policy/DisplayFoldController.java b/services/core/java/com/android/server/policy/DisplayFoldController.java
index c10e828d8c3dc..82fc22c51afc9 100644
--- a/services/core/java/com/android/server/policy/DisplayFoldController.java
+++ b/services/core/java/com/android/server/policy/DisplayFoldController.java
@@ -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() {
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 54da6436ad898..1a2266139405d 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -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 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);
+ }
}
}