Merge "Add callbacks for change in supported device states and non-override state." into sc-dev

This commit is contained in:
Darryl Johnson
2021-03-01 22:22:06 +00:00
committed by Android (Google) Code Review
16 changed files with 773 additions and 283 deletions

View File

@@ -0,0 +1,19 @@
/*
* 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;
parcelable DeviceStateInfo;

View File

@@ -0,0 +1,162 @@
/*
* 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.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
* Information about the state of the device.
*
* @hide
*/
public final class DeviceStateInfo implements Parcelable {
/** Bit that indicates the {@link #supportedStates} field has changed. */
public static final int CHANGED_SUPPORTED_STATES = 1 << 0;
/** Bit that indicates the {@link #baseState} field has changed. */
public static final int CHANGED_BASE_STATE = 1 << 1;
/** Bit that indicates the {@link #currentState} field has changed. */
public static final int CHANGED_CURRENT_STATE = 1 << 2;
@IntDef(prefix = {"CHANGED_"}, flag = true, value = {
CHANGED_SUPPORTED_STATES,
CHANGED_BASE_STATE,
CHANGED_CURRENT_STATE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ChangeFlags {}
/**
* The list of states supported by the device.
*/
@NonNull
public final int[] supportedStates;
/**
* The base (non-override) state of the device. The base state is the state of the device
* ignoring any override requests made through a call to {@link DeviceStateManager#requestState(
* DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
*/
public final int baseState;
/**
* The state of the device.
*/
public final int currentState;
/**
* Creates a new instance of {@link DeviceStateInfo}.
* <p>
* NOTE: Unlike {@link #DeviceStateInfo(DeviceStateInfo)}, this constructor does not copy the
* supplied parameters.
*/
public DeviceStateInfo(@NonNull int[] supportedStates, int baseState, int state) {
this.supportedStates = supportedStates;
this.baseState = baseState;
this.currentState = state;
}
/**
* Creates a new instance of {@link DeviceStateInfo} copying the fields of {@code info} into
* the fields of the returned instance.
*/
public DeviceStateInfo(@NonNull DeviceStateInfo info) {
this(Arrays.copyOf(info.supportedStates, info.supportedStates.length),
info.baseState, info.currentState);
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
DeviceStateInfo that = (DeviceStateInfo) other;
return baseState == that.baseState
&& currentState == that.currentState
&& Arrays.equals(supportedStates, that.supportedStates);
}
@Override
public int hashCode() {
int result = Objects.hash(baseState, currentState);
result = 31 * result + Arrays.hashCode(supportedStates);
return result;
}
/** Returns a bitmask of the differences between this instance and {@code other}. */
@ChangeFlags
public int diff(@NonNull DeviceStateInfo other) {
int diff = 0;
if (!Arrays.equals(supportedStates, other.supportedStates)) {
diff |= CHANGED_SUPPORTED_STATES;
}
if (baseState != other.baseState) {
diff |= CHANGED_BASE_STATE;
}
if (currentState != other.currentState) {
diff |= CHANGED_CURRENT_STATE;
}
return diff;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(supportedStates.length);
for (int i = 0; i < supportedStates.length; i++) {
dest.writeInt(supportedStates[i]);
}
dest.writeInt(baseState);
dest.writeInt(currentState);
}
@Override
public int describeContents() {
return 0;
}
public static final @NonNull Creator<DeviceStateInfo> CREATOR = new Creator<DeviceStateInfo>() {
@Override
public DeviceStateInfo createFromParcel(Parcel source) {
final int numberOfSupportedStates = source.readInt();
final int[] supportedStates = new int[numberOfSupportedStates];
for (int i = 0; i < numberOfSupportedStates; i++) {
supportedStates[i] = source.readInt();
}
final int baseState = source.readInt();
final int currentState = source.readInt();
return new DeviceStateInfo(supportedStates, baseState, currentState);
}
@Override
public DeviceStateInfo[] newArray(int size) {
return new DeviceStateInfo[size];
}
};
}

View File

@@ -111,38 +111,63 @@ public final class DeviceStateManager {
}
/**
* Registers a listener to receive notifications about changes in device state.
* Registers a callback to receive notifications about changes in device state.
*
* @param executor the executor to process notifications.
* @param listener the listener to register.
* @param callback the callback to register.
*
* @see DeviceStateListener
* @see DeviceStateCallback
*/
public void addDeviceStateListener(@NonNull @CallbackExecutor Executor executor,
@NonNull DeviceStateListener listener) {
mGlobal.registerDeviceStateListener(listener, executor);
public void registerCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull DeviceStateCallback callback) {
mGlobal.registerDeviceStateCallback(callback, executor);
}
/**
* Unregisters a listener previously registered with
* {@link #addDeviceStateListener(Executor, DeviceStateListener)}.
* Unregisters a callback previously registered with
* {@link #registerCallback(Executor, DeviceStateCallback)}.
*/
public void removeDeviceStateListener(@NonNull DeviceStateListener listener) {
mGlobal.unregisterDeviceStateListener(listener);
public void unregisterCallback(@NonNull DeviceStateCallback callback) {
mGlobal.unregisterDeviceStateCallback(callback);
}
/**
* Listens for changes in device states.
*/
public interface DeviceStateListener {
/** Callback to receive notifications about changes in device state. */
public interface DeviceStateCallback {
/**
* Called in response to a change in the states supported by the device.
* <p>
* Guaranteed to be called once on registration of the callback with the initial value and
* then on every subsequent change in the supported states.
*
* @param supportedStates the new supported states.
*
* @see DeviceStateManager#getSupportedStates()
*/
default void onSupportedStatesChanged(@NonNull int[] supportedStates) {}
/**
* Called in response to a change in the base device state.
* <p>
* The base state is the state of the device without considering any requests made through
* calls to {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}
* from any client process. The base state is guaranteed to match the state provided with a
* call to {@link #onStateChanged(int)} when there are no active requests from any process.
* <p>
* Guaranteed to be called once on registration of the callback with the initial value and
* then on every subsequent change in the non-override state.
*
* @param state the new base device state.
*/
default void onBaseStateChanged(int state) {}
/**
* Called in response to device state changes.
* <p>
* Guaranteed to be called once on registration of the listener with the
* initial value and then on every subsequent change in device state.
* Guaranteed to be called once on registration of the callback with the initial value and
* then on every subsequent change in device state.
*
* @param deviceState the new device state.
* @param state the new device state.
*/
void onDeviceStateChanged(int deviceState);
void onStateChanged(int state);
}
}

View File

@@ -19,7 +19,7 @@ package android.hardware.devicestate;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager.DeviceStateListener;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -31,12 +31,14 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.Executor;
/**
* Provides communication with the device state system service on behalf of applications.
*
* @see DeviceStateManager
*
* @hide
*/
@VisibleForTesting(visibility = Visibility.PACKAGE)
@@ -68,13 +70,13 @@ public final class DeviceStateManagerGlobal {
private DeviceStateManagerCallback mCallback;
@GuardedBy("mLock")
private final ArrayList<DeviceStateListenerWrapper> mListeners = new ArrayList<>();
private final ArrayList<DeviceStateCallbackWrapper> mCallbacks = new ArrayList<>();
@GuardedBy("mLock")
private final ArrayMap<IBinder, DeviceStateRequestWrapper> mRequests = new ArrayMap<>();
@Nullable
@GuardedBy("mLock")
private Integer mLastReceivedState;
private DeviceStateInfo mLastReceivedInfo;
@VisibleForTesting
public DeviceStateManagerGlobal(@NonNull IDeviceStateManager deviceStateManager) {
@@ -87,18 +89,31 @@ public final class DeviceStateManagerGlobal {
* @see DeviceStateManager#getSupportedStates()
*/
public int[] getSupportedStates() {
try {
return mDeviceStateManager.getSupportedDeviceStates();
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
synchronized (mLock) {
final DeviceStateInfo currentInfo;
if (mLastReceivedInfo != null) {
// If we have mLastReceivedInfo a callback is registered for this instance and it
// is receiving the most recent info from the server. Use that info here.
currentInfo = mLastReceivedInfo;
} else {
// If mLastReceivedInfo is null there is no registered callback so we manually
// fetch the current info.
try {
currentInfo = mDeviceStateManager.getDeviceStateInfo();
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
return Arrays.copyOf(currentInfo.supportedStates, currentInfo.supportedStates.length);
}
}
/**
* Submits a {@link DeviceStateRequest request} to modify the device state.
*
* @see DeviceStateManager#requestState(DeviceStateRequest,
* Executor, DeviceStateRequest.Callback)
* @see DeviceStateManager#requestState(DeviceStateRequest, Executor,
* DeviceStateRequest.Callback)
* @see DeviceStateRequest
*/
public void requestState(@NonNull DeviceStateRequest request,
@@ -157,49 +172,56 @@ public final class DeviceStateManagerGlobal {
}
/**
* Registers a listener to receive notifications about changes in device state.
* Registers a callback to receive notifications about changes in device state.
*
* @see DeviceStateManager#addDeviceStateListener(Executor, DeviceStateListener)
* @see DeviceStateManager#registerCallback(Executor, DeviceStateCallback)
*/
@VisibleForTesting(visibility = Visibility.PACKAGE)
public void registerDeviceStateListener(@NonNull DeviceStateListener listener,
public void registerDeviceStateCallback(@NonNull DeviceStateCallback callback,
@NonNull Executor executor) {
Integer stateToReport;
DeviceStateListenerWrapper wrapper;
DeviceStateCallbackWrapper wrapper;
DeviceStateInfo currentInfo;
synchronized (mLock) {
registerCallbackIfNeededLocked();
int index = findListenerLocked(listener);
int index = findCallbackLocked(callback);
if (index != -1) {
// This listener is already registered.
// This callback is already registered.
return;
}
wrapper = new DeviceStateListenerWrapper(listener, executor);
mListeners.add(wrapper);
stateToReport = mLastReceivedState;
registerCallbackIfNeededLocked();
if (mLastReceivedInfo == null) {
// Initialize the last received info with the current info if this is the first
// callback being registered.
try {
mLastReceivedInfo = mDeviceStateManager.getDeviceStateInfo();
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
currentInfo = new DeviceStateInfo(mLastReceivedInfo);
wrapper = new DeviceStateCallbackWrapper(callback, executor);
mCallbacks.add(wrapper);
}
if (stateToReport != null) {
// Notify the listener with the most recent device state from the server. If the state
// to report is null this is likely the first listener added and we're still waiting
// from the callback from the server.
wrapper.notifyDeviceStateChanged(stateToReport);
}
wrapper.notifySupportedStatesChanged(currentInfo.supportedStates);
wrapper.notifyBaseStateChanged(currentInfo.baseState);
wrapper.notifyStateChanged(currentInfo.currentState);
}
/**
* Unregisters a listener previously registered with
* {@link #registerDeviceStateListener(DeviceStateListener, Executor)}.
* Unregisters a callback previously registered with
* {@link #registerDeviceStateCallback(DeviceStateCallback, Executor)}}.
*
* @see DeviceStateManager#addDeviceStateListener(Executor, DeviceStateListener)
* @see DeviceStateManager#unregisterCallback(DeviceStateCallback)
*/
@VisibleForTesting(visibility = Visibility.PACKAGE)
public void unregisterDeviceStateListener(DeviceStateListener listener) {
public void unregisterDeviceStateCallback(@NonNull DeviceStateCallback callback) {
synchronized (mLock) {
int indexToRemove = findListenerLocked(listener);
int indexToRemove = findCallbackLocked(callback);
if (indexToRemove != -1) {
mListeners.remove(indexToRemove);
mCallbacks.remove(indexToRemove);
}
}
}
@@ -210,14 +232,15 @@ public final class DeviceStateManagerGlobal {
try {
mDeviceStateManager.registerCallback(mCallback);
} catch (RemoteException ex) {
mCallback = null;
throw ex.rethrowFromSystemServer();
}
}
}
private int findListenerLocked(DeviceStateListener listener) {
for (int i = 0; i < mListeners.size(); i++) {
if (mListeners.get(i).mDeviceStateListener.equals(listener)) {
private int findCallbackLocked(DeviceStateCallback callback) {
for (int i = 0; i < mCallbacks.size(); i++) {
if (mCallbacks.get(i).mDeviceStateCallback.equals(callback)) {
return i;
}
}
@@ -234,16 +257,34 @@ public final class DeviceStateManagerGlobal {
return null;
}
/** Handles a call from the server that the device state has changed. */
private void handleDeviceStateChanged(int newDeviceState) {
ArrayList<DeviceStateListenerWrapper> listeners;
/** Handles a call from the server that the device state info has changed. */
private void handleDeviceStateInfoChanged(@NonNull DeviceStateInfo info) {
ArrayList<DeviceStateCallbackWrapper> callbacks;
DeviceStateInfo oldInfo;
synchronized (mLock) {
mLastReceivedState = newDeviceState;
listeners = new ArrayList<>(mListeners);
oldInfo = mLastReceivedInfo;
mLastReceivedInfo = info;
callbacks = new ArrayList<>(mCallbacks);
}
for (int i = 0; i < listeners.size(); i++) {
listeners.get(i).notifyDeviceStateChanged(newDeviceState);
final int diff = oldInfo == null ? 1 : info.diff(oldInfo);
if ((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0) {
for (int i = 0; i < callbacks.size(); i++) {
// Copy the array to prevent callbacks from modifying the internal state.
final int[] supportedStates = Arrays.copyOf(info.supportedStates,
info.supportedStates.length);
callbacks.get(i).notifySupportedStatesChanged(supportedStates);
}
}
if ((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0) {
for (int i = 0; i < callbacks.size(); i++) {
callbacks.get(i).notifyBaseStateChanged(info.baseState);
}
}
if ((diff & DeviceStateInfo.CHANGED_CURRENT_STATE) > 0) {
for (int i = 0; i < callbacks.size(); i++) {
callbacks.get(i).notifyStateChanged(info.currentState);
}
}
}
@@ -291,8 +332,8 @@ public final class DeviceStateManagerGlobal {
private final class DeviceStateManagerCallback extends IDeviceStateManagerCallback.Stub {
@Override
public void onDeviceStateChanged(int deviceState) {
handleDeviceStateChanged(deviceState);
public void onDeviceStateInfoChanged(DeviceStateInfo info) {
handleDeviceStateInfoChanged(info);
}
@Override
@@ -311,17 +352,29 @@ public final class DeviceStateManagerGlobal {
}
}
private static final class DeviceStateListenerWrapper {
private final DeviceStateListener mDeviceStateListener;
private static final class DeviceStateCallbackWrapper {
@NonNull
private final DeviceStateCallback mDeviceStateCallback;
@NonNull
private final Executor mExecutor;
DeviceStateListenerWrapper(DeviceStateListener listener, Executor executor) {
mDeviceStateListener = listener;
DeviceStateCallbackWrapper(@NonNull DeviceStateCallback callback,
@NonNull Executor executor) {
mDeviceStateCallback = callback;
mExecutor = executor;
}
void notifyDeviceStateChanged(int newDeviceState) {
mExecutor.execute(() -> mDeviceStateListener.onDeviceStateChanged(newDeviceState));
void notifySupportedStatesChanged(int[] newSupportedStates) {
mExecutor.execute(() ->
mDeviceStateCallback.onSupportedStatesChanged(newSupportedStates));
}
void notifyBaseStateChanged(int newBaseState) {
mExecutor.execute(() -> mDeviceStateCallback.onBaseStateChanged(newBaseState));
}
void notifyStateChanged(int newDeviceState) {
mExecutor.execute(() -> mDeviceStateCallback.onStateChanged(newDeviceState));
}
}

View File

@@ -116,7 +116,7 @@ public final class DeviceStateRequest {
* requested state.
* <p>
* Guaranteed to be called after a call to
* {@link DeviceStateManager.DeviceStateListener#onDeviceStateChanged(int)} with a state
* {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)} with a state
* matching the requested state.
*/
default void onRequestActivated(@NonNull DeviceStateRequest request) {}
@@ -125,7 +125,7 @@ public final class DeviceStateRequest {
* Called to indicate the request has been temporarily suspended.
* <p>
* Guaranteed to be called before a call to
* {@link DeviceStateManager.DeviceStateListener#onDeviceStateChanged(int)}.
* {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)}.
*/
default void onRequestSuspended(@NonNull DeviceStateRequest request) {}
@@ -135,7 +135,7 @@ public final class DeviceStateRequest {
* DeviceStateRequest.Callback)}.
* <p>
* Guaranteed to be called before a call to
* {@link DeviceStateManager.DeviceStateListener#onDeviceStateChanged(int)}.
* {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)}.
* <p>
* Note: A call to {@link #onRequestSuspended(DeviceStateRequest)} is not guaranteed to
* occur before this method.

View File

@@ -16,25 +16,26 @@
package android.hardware.devicestate;
import android.hardware.devicestate.DeviceStateInfo;
import android.hardware.devicestate.IDeviceStateManagerCallback;
/** @hide */
interface IDeviceStateManager {
/** Returns the current device state info. */
DeviceStateInfo getDeviceStateInfo();
/**
* 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.
* {@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

View File

@@ -16,20 +16,23 @@
package android.hardware.devicestate;
import android.hardware.devicestate.DeviceStateInfo;
/** @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.
* Called in response to a change in {@link DeviceStateInfo}.
*
* @param deviceState the new state of the device.
* @param info the new device state info.
*
* @see DeviceStateInfo
*/
oneway void onDeviceStateChanged(int deviceState);
oneway void onDeviceStateInfoChanged(in DeviceStateInfo info);
/**
* 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.
* after a subsequent call to {@link #onDeviceStateInfoChanged(DeviceStateInfo)} if the request
* becoming active resulted in a change of device state info.
*
* @param token the request token previously registered with
* {@link IDeviceStateManager#requestState(IBinder, int, int)}
@@ -38,8 +41,8 @@ interface IDeviceStateManagerCallback {
/**
* 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.
* before a subsequent call to {@link #onDeviceStateInfoChanged(DeviceStateInfo)} if the request
* becoming suspended resulted in a change of device state info.
*
* @param token the request token previously registered with
* {@link IDeviceStateManager#requestState(IBinder, int, int)}
@@ -49,8 +52,8 @@ interface IDeviceStateManagerCallback {
/**
* 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.
* {@link #onDeviceStateInfoChanged(DeviceStateInfo)} if the request becoming canceled resulted
* in a change of device state info.
*
* @param token the request token previously registered with
* {@link IDeviceStateManager#requestState(IBinder, int, int)}