Merge changes from topic "top-focused-DevStMan" into sc-v2-dev

* changes:
  Allow top focused activities to call requestState
  Allow override requests to outlive the requesting process.
  Refactor request lifecycle logic into OverrideRequestController.
This commit is contained in:
Kenneth Ford
2021-08-21 03:00:21 +00:00
committed by Android (Google) Code Review
13 changed files with 1034 additions and 333 deletions

View File

@@ -1133,10 +1133,10 @@ package android.hardware.camera2 {
package android.hardware.devicestate { package android.hardware.devicestate {
public final class DeviceStateManager { public final class DeviceStateManager {
method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest); method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest);
method @NonNull public int[] getSupportedStates(); method @NonNull public int[] getSupportedStates();
method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback); method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback); method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
method public void unregisterCallback(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback); method public void unregisterCallback(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
field public static final int MAXIMUM_DEVICE_STATE = 255; // 0xff field public static final int MAXIMUM_DEVICE_STATE = 255; // 0xff
field public static final int MINIMUM_DEVICE_STATE = 0; // 0x0 field public static final int MINIMUM_DEVICE_STATE = 0; // 0x0

View File

@@ -75,23 +75,24 @@ public final class DeviceStateManager {
/** /**
* Submits a {@link DeviceStateRequest request} to modify the device state. * Submits a {@link DeviceStateRequest request} to modify the device state.
* <p> * <p>
* By default, the request is kept active until a call to * By default, the request is kept active until one of the following occurs:
* {@link #cancelRequest(DeviceStateRequest)} or until one of the following occurs:
* <ul> * <ul>
* <li>The system deems the request can no longer be honored, for example if the requested
* state becomes unsupported.
* <li>A call to {@link #cancelRequest(DeviceStateRequest)}.
* <li>Another processes submits a request succeeding this request in which case the request * <li>Another processes submits a request succeeding this request in which case the request
* will be suspended until the interrupting request is canceled. * will be suspended until the interrupting request is canceled.
* <li>The requested state has become unsupported.
* <li>The process submitting the request dies.
* </ul> * </ul>
* However, this behavior can be changed by setting flags on the {@link DeviceStateRequest}. * However, this behavior can be changed by setting flags on the {@link DeviceStateRequest}.
* *
* @throws IllegalArgumentException if the requested state is unsupported. * @throws IllegalArgumentException if the requested state is unsupported.
* @throws SecurityException if the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} * @throws SecurityException if the caller is neither the current top-focused activity nor if
* permission is not held. * the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held.
* *
* @see DeviceStateRequest * @see DeviceStateRequest
*/ */
@RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
conditional = true)
public void requestState(@NonNull DeviceStateRequest request, public void requestState(@NonNull DeviceStateRequest request,
@Nullable @CallbackExecutor Executor executor, @Nullable @CallbackExecutor Executor executor,
@Nullable DeviceStateRequest.Callback callback) { @Nullable DeviceStateRequest.Callback callback) {
@@ -105,10 +106,11 @@ public final class DeviceStateManager {
* This method is noop if the {@code request} has not been submitted with a call to * This method is noop if the {@code request} has not been submitted with a call to
* {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
* *
* @throws SecurityException if the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} * @throws SecurityException if the caller is neither the current top-focused activity nor if
* permission is not held. * the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held.
*/ */
@RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
conditional = true)
public void cancelRequest(@NonNull DeviceStateRequest request) { public void cancelRequest(@NonNull DeviceStateRequest request) {
mGlobal.cancelRequest(request); mGlobal.cancelRequest(request);
} }

View File

@@ -19,11 +19,14 @@ package com.android.server.devicestate;
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
import android.annotation.IntDef;
import android.annotation.IntRange; import android.annotation.IntRange;
import android.annotation.NonNull; import android.annotation.NonNull;
import com.android.internal.util.Preconditions; import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects; import java.util.Objects;
/** /**
@@ -39,6 +42,19 @@ import java.util.Objects;
* @see DeviceStateManagerService * @see DeviceStateManagerService
*/ */
public final class DeviceState { public final class DeviceState {
/**
* Flag that indicates sticky requests should be cancelled when this device state becomes the
* base device state.
*/
public static final int FLAG_CANCEL_STICKY_REQUESTS = 1 << 0;
/** @hide */
@IntDef(prefix = {"FLAG_"}, flag = true, value = {
FLAG_CANCEL_STICKY_REQUESTS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeviceStateFlags {}
/** Unique identifier for the device state. */ /** Unique identifier for the device state. */
@IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE)
private final int mIdentifier; private final int mIdentifier;
@@ -47,14 +63,19 @@ public final class DeviceState {
@NonNull @NonNull
private final String mName; private final String mName;
@DeviceStateFlags
private final int mFlags;
public DeviceState( public DeviceState(
@IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier, @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier,
@NonNull String name) { @NonNull String name,
@DeviceStateFlags int flags) {
Preconditions.checkArgumentInRange(identifier, MINIMUM_DEVICE_STATE, MAXIMUM_DEVICE_STATE, Preconditions.checkArgumentInRange(identifier, MINIMUM_DEVICE_STATE, MAXIMUM_DEVICE_STATE,
"identifier"); "identifier");
mIdentifier = identifier; mIdentifier = identifier;
mName = name; mName = name;
mFlags = flags;
} }
/** Returns the unique identifier for the device state. */ /** Returns the unique identifier for the device state. */
@@ -69,6 +90,11 @@ public final class DeviceState {
return mName; return mName;
} }
@DeviceStateFlags
public int getFlags() {
return mFlags;
}
@Override @Override
public String toString() { public String toString() {
return "DeviceState{" + "identifier=" + mIdentifier + ", name='" + mName + '\'' + '}'; return "DeviceState{" + "identifier=" + mIdentifier + ", name='" + mName + '\'' + '}';
@@ -80,11 +106,12 @@ public final class DeviceState {
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
DeviceState that = (DeviceState) o; DeviceState that = (DeviceState) o;
return mIdentifier == that.mIdentifier return mIdentifier == that.mIdentifier
&& Objects.equals(mName, that.mName); && Objects.equals(mName, that.mName)
&& mFlags == that.mFlags;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(mIdentifier, mName); return Objects.hash(mIdentifier, mName, mFlags);
} }
} }

View File

@@ -19,8 +19,13 @@ package com.android.server.devicestate;
import static android.Manifest.permission.CONTROL_DEVICE_STATE; import static android.Manifest.permission.CONTROL_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES; import static android.os.Process.THREAD_PRIORITY_DISPLAY;
import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
import static com.android.server.devicestate.OverrideRequestController.STATUS_SUSPENDED;
import android.annotation.IntDef;
import android.annotation.IntRange; import android.annotation.IntRange;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
@@ -30,12 +35,12 @@ import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.IDeviceStateManager; import android.hardware.devicestate.IDeviceStateManager;
import android.hardware.devicestate.IDeviceStateManagerCallback; import android.hardware.devicestate.IDeviceStateManagerCallback;
import android.os.Binder; import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder; import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ResultReceiver; import android.os.ResultReceiver;
import android.os.ShellCallback; import android.os.ShellCallback;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog; import android.util.Slog;
import android.util.SparseArray; import android.util.SparseArray;
@@ -43,14 +48,21 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils; import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemService; import com.android.server.SystemService;
import com.android.server.policy.DeviceStatePolicyImpl; import com.android.server.policy.DeviceStatePolicyImpl;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowProcessController;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import java.util.WeakHashMap;
/** /**
* A system service that manages the state of a device with user-configurable hardware like a * A system service that manages the state of a device with user-configurable hardware like a
@@ -81,10 +93,20 @@ public final class DeviceStateManagerService extends SystemService {
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
private final Object mLock = new Object(); private final Object mLock = new Object();
// Internal system service thread used to dispatch calls to the policy and to registered
// callbacks though its handler (mHandler). Provides a guarantee of callback order when
// leveraging mHandler and also enables posting messages with the service lock held.
private final HandlerThread mHandlerThread;
private final Handler mHandler;
@NonNull @NonNull
private final DeviceStatePolicy mDeviceStatePolicy; private final DeviceStatePolicy mDeviceStatePolicy;
@NonNull @NonNull
private final BinderService mBinderService; private final BinderService mBinderService;
@NonNull
private final OverrideRequestController mOverrideRequestController;
@VisibleForTesting
@NonNull
public ActivityTaskManagerInternal mActivityTaskManagerInternal;
// All supported device states keyed by identifier. // All supported device states keyed by identifier.
@GuardedBy("mLock") @GuardedBy("mLock")
@@ -109,17 +131,16 @@ public final class DeviceStateManagerService extends SystemService {
@NonNull @NonNull
private Optional<DeviceState> mBaseState = Optional.empty(); private Optional<DeviceState> mBaseState = Optional.empty();
// The current active override request. When set the device state specified here will take
// precedence over mBaseState.
@GuardedBy("mLock")
@NonNull
private Optional<OverrideRequest> mActiveOverride = Optional.empty();
// List of processes registered to receive notifications about changes to device state and // List of processes registered to receive notifications about changes to device state and
// request status indexed by process id. // request status indexed by process id.
@GuardedBy("mLock") @GuardedBy("mLock")
private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>(); private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>();
// List of override requests with the highest precedence request at the end.
@GuardedBy("mLock")
private final ArrayList<OverrideRequestRecord> mRequestRecords = new ArrayList<>();
// Set of override requests that are pending a call to notifyStatusIfNeeded() to be notified
// of a change in status.
@GuardedBy("mLock")
private final ArraySet<OverrideRequestRecord> mRequestsPendingStatusChange = new ArraySet<>();
public DeviceStateManagerService(@NonNull Context context) { public DeviceStateManagerService(@NonNull Context context) {
this(context, new DeviceStatePolicyImpl(context)); this(context, new DeviceStatePolicyImpl(context));
@@ -128,9 +149,17 @@ public final class DeviceStateManagerService extends SystemService {
@VisibleForTesting @VisibleForTesting
DeviceStateManagerService(@NonNull Context context, @NonNull DeviceStatePolicy policy) { DeviceStateManagerService(@NonNull Context context, @NonNull DeviceStatePolicy policy) {
super(context); super(context);
// Service thread assigned THREAD_PRIORITY_DISPLAY because this service indirectly drives
// display (on/off) and window (position) events through its callbacks.
mHandlerThread = new ServiceThread(TAG, THREAD_PRIORITY_DISPLAY, false /* allowIo */);
mHandlerThread.start();
mHandler = mHandlerThread.getThreadHandler();
mOverrideRequestController = new OverrideRequestController(
this::onOverrideRequestStatusChangedLocked);
mDeviceStatePolicy = policy; mDeviceStatePolicy = policy;
mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener()); mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener());
mBinderService = new BinderService(); mBinderService = new BinderService();
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
} }
@Override @Override
@@ -138,6 +167,11 @@ public final class DeviceStateManagerService extends SystemService {
publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService); publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService);
} }
@VisibleForTesting
Handler getHandler() {
return mHandler;
}
/** /**
* Returns the current state the system is in. Note that the system may be in the process of * Returns the current state the system is in. Note that the system may be in the process of
* configuring a different state. * configuring a different state.
@@ -191,12 +225,10 @@ public final class DeviceStateManagerService extends SystemService {
@NonNull @NonNull
Optional<DeviceState> getOverrideState() { Optional<DeviceState> getOverrideState() {
synchronized (mLock) { synchronized (mLock) {
if (mRequestRecords.isEmpty()) { if (mActiveOverride.isPresent()) {
return Optional.empty(); return getStateLocked(mActiveOverride.get().getRequestedState());
} }
return Optional.empty();
OverrideRequestRecord topRequest = mRequestRecords.get(mRequestRecords.size() - 1);
return Optional.of(topRequest.mRequestedState);
} }
} }
@@ -247,43 +279,41 @@ public final class DeviceStateManagerService extends SystemService {
} }
private void updateSupportedStates(DeviceState[] supportedDeviceStates) { private void updateSupportedStates(DeviceState[] supportedDeviceStates) {
boolean updatedPendingState;
boolean hasBaseState;
synchronized (mLock) { synchronized (mLock) {
final int[] oldStateIdentifiers = getSupportedStateIdentifiersLocked(); final int[] oldStateIdentifiers = getSupportedStateIdentifiersLocked();
// Whether or not at least one device state has the flag FLAG_CANCEL_STICKY_REQUESTS
// set. If set to true, the OverrideRequestController will be configured to allow sticky
// requests.
boolean hasTerminalDeviceState = false;
mDeviceStates.clear(); mDeviceStates.clear();
for (int i = 0; i < supportedDeviceStates.length; i++) { for (int i = 0; i < supportedDeviceStates.length; i++) {
DeviceState state = supportedDeviceStates[i]; DeviceState state = supportedDeviceStates[i];
if ((state.getFlags() & DeviceState.FLAG_CANCEL_STICKY_REQUESTS) != 0) {
hasTerminalDeviceState = true;
}
mDeviceStates.put(state.getIdentifier(), state); mDeviceStates.put(state.getIdentifier(), state);
} }
mOverrideRequestController.setStickyRequestsAllowed(hasTerminalDeviceState);
final int[] newStateIdentifiers = getSupportedStateIdentifiersLocked(); final int[] newStateIdentifiers = getSupportedStateIdentifiersLocked();
if (Arrays.equals(oldStateIdentifiers, newStateIdentifiers)) { if (Arrays.equals(oldStateIdentifiers, newStateIdentifiers)) {
return; return;
} }
final int requestSize = mRequestRecords.size(); mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers);
for (int i = 0; i < requestSize; i++) { updatePendingStateLocked();
OverrideRequestRecord request = mRequestRecords.get(i);
if (!isSupportedStateLocked(request.mRequestedState.getIdentifier())) { if (!mPendingState.isPresent()) {
request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED); // If the change in the supported states didn't result in a change of the pending
} // state commitPendingState() will never be called and the callbacks will never be
// notified of the change.
notifyDeviceStateInfoChangedAsync();
} }
updatedPendingState = updatePendingStateLocked(); mHandler.post(this::notifyPolicyIfNeeded);
hasBaseState = mBaseState.isPresent();
} }
if (hasBaseState && !updatedPendingState) {
// If the change in the supported states didn't result in a change of the pending state
// commitPendingState() will never be called and the callbacks will never be notified
// of the change.
notifyDeviceStateInfoChanged();
}
notifyRequestsOfStatusChangeIfNeeded();
notifyPolicyIfNeeded();
} }
/** /**
@@ -311,7 +341,6 @@ public final class DeviceStateManagerService extends SystemService {
* @see #isSupportedStateLocked(int) * @see #isSupportedStateLocked(int)
*/ */
private void setBaseState(int identifier) { private void setBaseState(int identifier) {
boolean updatedPendingState;
synchronized (mLock) { synchronized (mLock) {
final Optional<DeviceState> baseStateOptional = getStateLocked(identifier); final Optional<DeviceState> baseStateOptional = getStateLocked(identifier);
if (!baseStateOptional.isPresent()) { if (!baseStateOptional.isPresent()) {
@@ -325,26 +354,21 @@ public final class DeviceStateManagerService extends SystemService {
} }
mBaseState = Optional.of(baseState); mBaseState = Optional.of(baseState);
final int requestSize = mRequestRecords.size(); if ((baseState.getFlags() & DeviceState.FLAG_CANCEL_STICKY_REQUESTS) != 0) {
for (int i = 0; i < requestSize; i++) { mOverrideRequestController.cancelStickyRequests();
OverrideRequestRecord request = mRequestRecords.get(i); }
if ((request.mFlags & FLAG_CANCEL_WHEN_BASE_CHANGES) > 0) { mOverrideRequestController.handleBaseStateChanged();
request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED); updatePendingStateLocked();
}
if (!mPendingState.isPresent()) {
// If the change in base state didn't result in a change of the pending state
// commitPendingState() will never be called and the callbacks will never be
// notified of the change.
notifyDeviceStateInfoChangedAsync();
} }
updatedPendingState = updatePendingStateLocked(); mHandler.post(this::notifyPolicyIfNeeded);
} }
if (!updatedPendingState) {
// If the change in base state didn't result in a change of the pending state
// commitPendingState() will never be called and the callbacks will never be notified
// of the change.
notifyDeviceStateInfoChanged();
}
notifyRequestsOfStatusChangeIfNeeded();
notifyPolicyIfNeeded();
} }
/** /**
@@ -362,8 +386,8 @@ public final class DeviceStateManagerService extends SystemService {
} }
final DeviceState stateToConfigure; final DeviceState stateToConfigure;
if (!mRequestRecords.isEmpty()) { if (mActiveOverride.isPresent()) {
stateToConfigure = mRequestRecords.get(mRequestRecords.size() - 1).mRequestedState; stateToConfigure = getStateLocked(mActiveOverride.get().getRequestedState()).get();
} else if (mBaseState.isPresent() } else if (mBaseState.isPresent()
&& isSupportedStateLocked(mBaseState.get().getIdentifier())) { && isSupportedStateLocked(mBaseState.get().getIdentifier())) {
// Base state could have recently become unsupported after a change in supported states. // Base state could have recently become unsupported after a change in supported states.
@@ -429,108 +453,106 @@ public final class DeviceStateManagerService extends SystemService {
* </p> * </p>
*/ */
private void commitPendingState() { private void commitPendingState() {
// Update the current state.
synchronized (mLock) { synchronized (mLock) {
final DeviceState newState = mPendingState.get(); final DeviceState newState = mPendingState.get();
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "Committing state: " + newState); Slog.d(TAG, "Committing state: " + newState);
} }
if (!mRequestRecords.isEmpty()) {
final OverrideRequestRecord topRequest =
mRequestRecords.get(mRequestRecords.size() - 1);
if (topRequest.mRequestedState.getIdentifier() == newState.getIdentifier()) {
// The top request could have come in while the service was awaiting callback
// from the policy. In that case we only set it to active if it matches the
// current committed state, otherwise it will be set to active when its
// requested state is committed.
topRequest.setStatusLocked(OverrideRequestRecord.STATUS_ACTIVE);
}
}
FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_STATE_CHANGED, FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_STATE_CHANGED,
newState.getIdentifier(), !mCommittedState.isPresent()); newState.getIdentifier(), !mCommittedState.isPresent());
mCommittedState = Optional.of(newState); mCommittedState = Optional.of(newState);
mPendingState = Optional.empty(); mPendingState = Optional.empty();
updatePendingStateLocked(); updatePendingStateLocked();
// Notify callbacks of a change.
notifyDeviceStateInfoChangedAsync();
// The top request could have come in while the service was awaiting callback
// from the policy. In that case we only set it to active if it matches the
// current committed state, otherwise it will be set to active when its
// requested state is committed.
OverrideRequest activeRequest = mActiveOverride.orElse(null);
if (activeRequest != null
&& activeRequest.getRequestedState() == newState.getIdentifier()) {
ProcessRecord processRecord = mProcessRecords.get(activeRequest.getPid());
if (processRecord != null) {
processRecord.notifyRequestActiveAsync(activeRequest.getToken());
}
}
// Try to configure the next state if needed.
mHandler.post(this::notifyPolicyIfNeeded);
} }
// Notify callbacks of a change.
notifyDeviceStateInfoChanged();
// Notify the top request that it's active.
notifyRequestsOfStatusChangeIfNeeded();
// Try to configure the next state if needed.
notifyPolicyIfNeeded();
} }
private void notifyDeviceStateInfoChanged() { private void notifyDeviceStateInfoChangedAsync() {
if (Thread.holdsLock(mLock)) {
throw new IllegalStateException(
"Attempting to notify callbacks with service lock held.");
}
// Grab the lock and copy the process records and the current info.
ArrayList<ProcessRecord> registeredProcesses;
DeviceStateInfo info;
synchronized (mLock) { synchronized (mLock) {
if (mProcessRecords.size() == 0) { if (mProcessRecords.size() == 0) {
return; return;
} }
registeredProcesses = new ArrayList<>(); ArrayList<ProcessRecord> registeredProcesses = new ArrayList<>();
for (int i = 0; i < mProcessRecords.size(); i++) { for (int i = 0; i < mProcessRecords.size(); i++) {
registeredProcesses.add(mProcessRecords.valueAt(i)); registeredProcesses.add(mProcessRecords.valueAt(i));
} }
info = getDeviceStateInfoLocked(); DeviceStateInfo info = getDeviceStateInfoLocked();
}
// After releasing the lock, send the notifications out. for (int i = 0; i < registeredProcesses.size(); i++) {
for (int i = 0; i < registeredProcesses.size(); i++) { registeredProcesses.get(i).notifyDeviceStateInfoAsync(info);
registeredProcesses.get(i).notifyDeviceStateInfoAsync(info); }
} }
} }
/** private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request,
* Notifies all dirty requests (requests that have a change in status, but have not yet been @OverrideRequestController.RequestStatus int status) {
* notified) that their status has changed. if (status == STATUS_ACTIVE) {
*/ mActiveOverride = Optional.of(request);
private void notifyRequestsOfStatusChangeIfNeeded() { } else if (status == STATUS_SUSPENDED || status == STATUS_CANCELED) {
if (Thread.holdsLock(mLock)) { if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
throw new IllegalStateException( mActiveOverride = Optional.empty();
"Attempting to notify requests with service lock held.");
}
ArraySet<OverrideRequestRecord> dirtyRequests;
synchronized (mLock) {
if (mRequestsPendingStatusChange.isEmpty()) {
return;
} }
} else {
dirtyRequests = new ArraySet<>(mRequestsPendingStatusChange); throw new IllegalArgumentException("Unknown request status: " + status);
mRequestsPendingStatusChange.clear();
} }
// After releasing the lock, send the notifications out. boolean updatedPendingState = updatePendingStateLocked();
for (int i = 0; i < dirtyRequests.size(); i++) {
dirtyRequests.valueAt(i).notifyStatusIfNeeded(); ProcessRecord processRecord = mProcessRecords.get(request.getPid());
if (processRecord == null) {
// If the process is no longer registered with the service, for example if it has died,
// there is no need to notify it of a change in request status.
mHandler.post(this::notifyPolicyIfNeeded);
return;
} }
if (status == STATUS_ACTIVE) {
if (!updatedPendingState && !mPendingState.isPresent()) {
// If the pending state was not updated and there is not currently a pending state
// then this newly active request will never be notified of a change in state.
// Schedule the notification now.
processRecord.notifyRequestActiveAsync(request.getToken());
}
} else if (status == STATUS_SUSPENDED) {
processRecord.notifyRequestSuspendedAsync(request.getToken());
} else {
processRecord.notifyRequestCanceledAsync(request.getToken());
}
mHandler.post(this::notifyPolicyIfNeeded);
} }
private void registerProcess(int pid, IDeviceStateManagerCallback callback) { private void registerProcess(int pid, IDeviceStateManagerCallback callback) {
DeviceStateInfo currentInfo;
ProcessRecord record;
// Grab the lock to register the callback and get the current state.
synchronized (mLock) { synchronized (mLock) {
if (mProcessRecords.contains(pid)) { if (mProcessRecords.contains(pid)) {
throw new SecurityException("The calling process has already registered an" throw new SecurityException("The calling process has already registered an"
+ " IDeviceStateManagerCallback."); + " IDeviceStateManagerCallback.");
} }
record = new ProcessRecord(callback, pid); ProcessRecord record = new ProcessRecord(callback, pid, this::handleProcessDied,
mHandlerThread.getThreadHandler());
try { try {
callback.asBinder().linkToDeath(record, 0); callback.asBinder().linkToDeath(record, 0);
} catch (RemoteException ex) { } catch (RemoteException ex) {
@@ -538,34 +560,21 @@ public final class DeviceStateManagerService extends SystemService {
} }
mProcessRecords.put(pid, record); mProcessRecords.put(pid, record);
currentInfo = mCommittedState.isPresent() ? getDeviceStateInfoLocked() : null; DeviceStateInfo currentInfo = mCommittedState.isPresent()
} ? getDeviceStateInfoLocked() : null;
if (currentInfo != null) {
if (currentInfo != null) { // If there is not a committed state we'll wait to notify the process of the initial
// If there is not a committed state we'll wait to notify the process of the initial // value.
// value. record.notifyDeviceStateInfoAsync(currentInfo);
record.notifyDeviceStateInfoAsync(currentInfo); }
} }
} }
private void handleProcessDied(ProcessRecord processRecord) { private void handleProcessDied(ProcessRecord processRecord) {
synchronized (mLock) { synchronized (mLock) {
// 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); mProcessRecords.remove(processRecord.mPid);
mOverrideRequestController.handleProcessDied(processRecord.mPid);
updatePendingStateLocked();
} }
notifyPolicyIfNeeded();
} }
private void requestStateInternal(int state, int flags, int callingPid, private void requestStateInternal(int state, int flags, int callingPid,
@@ -577,7 +586,7 @@ public final class DeviceStateManagerService extends SystemService {
+ " has no registered callback."); + " has no registered callback.");
} }
if (processRecord.mRequestRecords.get(token) != null) { if (mOverrideRequestController.hasRequest(token)) {
throw new IllegalStateException("Request has already been made for the supplied" throw new IllegalStateException("Request has already been made for the supplied"
+ " token: " + token); + " token: " + token);
} }
@@ -588,27 +597,9 @@ public final class DeviceStateManagerService extends SystemService {
+ " is not supported."); + " is not supported.");
} }
OverrideRequestRecord topRecord = mRequestRecords.isEmpty() OverrideRequest request = new OverrideRequest(token, callingPid, state, flags);
? null : mRequestRecords.get(mRequestRecords.size() - 1); mOverrideRequestController.addRequest(request);
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);
final boolean updatedPendingState = updatePendingStateLocked();
if (!updatedPendingState && !mPendingState.isPresent()) {
// We don't set the status of the new request to ACTIVE if the request updated the
// pending state as it will be set in commitPendingState().
request.setStatusLocked(OverrideRequestRecord.STATUS_ACTIVE, true /* markDirty */);
}
} }
notifyRequestsOfStatusChangeIfNeeded();
notifyPolicyIfNeeded();
} }
private void cancelRequestInternal(int callingPid, @NonNull IBinder token) { private void cancelRequestInternal(int callingPid, @NonNull IBinder token) {
@@ -619,18 +610,8 @@ public final class DeviceStateManagerService extends SystemService {
+ " has no registered callback."); + " has no registered callback.");
} }
OverrideRequestRecord request = processRecord.mRequestRecords.get(token); mOverrideRequestController.cancelRequest(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) { private void dumpInternal(PrintWriter pw) {
@@ -650,16 +631,7 @@ public final class DeviceStateManagerService extends SystemService {
pw.println(" " + i + ": mPid=" + processRecord.mPid); pw.println(" " + i + ": mPid=" + processRecord.mPid);
} }
final int requestCount = mRequestRecords.size(); mOverrideRequestController.dumpInternal(pw);
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));
}
} }
} }
@@ -683,142 +655,107 @@ public final class DeviceStateManagerService extends SystemService {
} }
} }
private final class ProcessRecord implements IBinder.DeathRecipient { private static final class ProcessRecord implements IBinder.DeathRecipient {
public interface DeathListener {
void onProcessDied(ProcessRecord record);
}
private static final int STATUS_ACTIVE = 0;
private static final int STATUS_SUSPENDED = 1;
private static final int STATUS_CANCELED = 2;
@IntDef(prefix = {"STATUS_"}, value = {
STATUS_ACTIVE,
STATUS_SUSPENDED,
STATUS_CANCELED
})
@Retention(RetentionPolicy.SOURCE)
private @interface RequestStatus {}
private final IDeviceStateManagerCallback mCallback; private final IDeviceStateManagerCallback mCallback;
private final int mPid; private final int mPid;
private final DeathListener mDeathListener;
private final Handler mHandler;
private final ArrayMap<IBinder, OverrideRequestRecord> mRequestRecords = new ArrayMap<>(); private final WeakHashMap<IBinder, Integer> mLastNotifiedStatus = new WeakHashMap<>();
ProcessRecord(IDeviceStateManagerCallback callback, int pid) { ProcessRecord(IDeviceStateManagerCallback callback, int pid, DeathListener deathListener,
Handler handler) {
mCallback = callback; mCallback = callback;
mPid = pid; mPid = pid;
mDeathListener = deathListener;
mHandler = handler;
} }
@Override @Override
public void binderDied() { public void binderDied() {
handleProcessDied(this); mDeathListener.onProcessDied(this);
} }
public void notifyDeviceStateInfoAsync(@NonNull DeviceStateInfo info) { public void notifyDeviceStateInfoAsync(@NonNull DeviceStateInfo info) {
try { mHandler.post(() -> {
mCallback.onDeviceStateInfoChanged(info); try {
} catch (RemoteException ex) { mCallback.onDeviceStateInfoChanged(info);
Slog.w(TAG, "Failed to notify process " + mPid + " that device state changed.", } catch (RemoteException ex) {
ex); Slog.w(TAG, "Failed to notify process " + mPid + " that device state changed.",
} 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() { public void notifyRequestActiveAsync(IBinder token) {
int stateToReport; @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token);
synchronized (mLock) { if (lastStatus != null
if (mLastNotifiedStatus == mStatus) { && (lastStatus == STATUS_ACTIVE || lastStatus == STATUS_CANCELED)) {
return; return;
}
mLastNotifiedStatus.put(token, STATUS_ACTIVE);
mHandler.post(() -> {
try {
mCallback.onRequestActive(token);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
ex);
} }
});
}
stateToReport = mStatus; public void notifyRequestSuspendedAsync(IBinder token) {
mLastNotifiedStatus = mStatus; @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token);
if (lastStatus != null
&& (lastStatus == STATUS_SUSPENDED || lastStatus == STATUS_CANCELED)) {
return;
} }
if (stateToReport == STATUS_ACTIVE) { mLastNotifiedStatus.put(token, STATUS_SUSPENDED);
mProcessRecord.notifyRequestActiveAsync(this); mHandler.post(() -> {
} else if (stateToReport == STATUS_SUSPENDED) { try {
mProcessRecord.notifyRequestSuspendedAsync(this); mCallback.onRequestSuspended(token);
} else if (stateToReport == STATUS_CANCELED) { } catch (RemoteException ex) {
mProcessRecord.notifyRequestCanceledAsync(this); Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
ex);
}
});
}
public void notifyRequestCanceledAsync(IBinder token) {
@RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token);
if (lastStatus != null && lastStatus == STATUS_CANCELED) {
return;
} }
mLastNotifiedStatus.put(token, STATUS_CANCELED);
mHandler.post(() -> {
try {
mCallback.onRequestCanceled(token);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
ex);
}
});
} }
} }
@@ -848,14 +785,21 @@ public final class DeviceStateManagerService extends SystemService {
@Override // Binder call @Override // Binder call
public void requestState(IBinder token, int state, int flags) { public void requestState(IBinder token, int state, int flags) {
getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, final int callingPid = Binder.getCallingPid();
"Permission required to request device state."); // Allow top processes to request a device state change
// If the calling process ID is not the top app, then we check if this process
// holds a permission to CONTROL_DEVICE_STATE
final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
if (topApp.getPid() != callingPid) {
getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
"Permission required to request device state, "
+ "or the call must come from the top focused app.");
}
if (token == null) { if (token == null) {
throw new IllegalArgumentException("Request token must not be null."); throw new IllegalArgumentException("Request token must not be null.");
} }
final int callingPid = Binder.getCallingPid();
final long callingIdentity = Binder.clearCallingIdentity(); final long callingIdentity = Binder.clearCallingIdentity();
try { try {
requestStateInternal(state, flags, callingPid, token); requestStateInternal(state, flags, callingPid, token);
@@ -866,14 +810,21 @@ public final class DeviceStateManagerService extends SystemService {
@Override // Binder call @Override // Binder call
public void cancelRequest(IBinder token) { public void cancelRequest(IBinder token) {
getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, final int callingPid = Binder.getCallingPid();
"Permission required to clear requested device state."); // Allow top processes to cancel a device state change
// If the calling process ID is not the top app, then we check if this process
// holds a permission to CONTROL_DEVICE_STATE
final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
if (topApp.getPid() != callingPid) {
getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
"Permission required to cancel device state, "
+ "or the call must come from the top focused app.");
}
if (token == null) { if (token == null) {
throw new IllegalArgumentException("Request token must not be null."); throw new IllegalArgumentException("Request token must not be null.");
} }
final int callingPid = Binder.getCallingPid();
final long callingIdentity = Binder.clearCallingIdentity(); final long callingIdentity = Binder.clearCallingIdentity();
try { try {
cancelRequestInternal(callingPid, token); cancelRequestInternal(callingPid, token);

View File

@@ -0,0 +1,58 @@
/*
* 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 com.android.server.devicestate;
import android.hardware.devicestate.DeviceStateRequest;
import android.os.IBinder;
/**
* A request to override the state managed by {@link DeviceStateManagerService}.
*
* @see OverrideRequestController
*/
final class OverrideRequest {
private final IBinder mToken;
private final int mPid;
private final int mRequestedState;
@DeviceStateRequest.RequestFlags
private final int mFlags;
OverrideRequest(IBinder token, int pid, int requestedState,
@DeviceStateRequest.RequestFlags int flags) {
mToken = token;
mPid = pid;
mRequestedState = requestedState;
mFlags = flags;
}
IBinder getToken() {
return mToken;
}
int getPid() {
return mPid;
}
int getRequestedState() {
return mRequestedState;
}
@DeviceStateRequest.RequestFlags
int getFlags() {
return mFlags;
}
}

View File

@@ -0,0 +1,322 @@
/*
* 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 com.android.server.devicestate;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.devicestate.DeviceStateRequest;
import android.os.IBinder;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
/**
* Manages the lifecycle of override requests.
* <p>
* New requests are added with {@link #addRequest(OverrideRequest)} and are kept active until
* either:
* <ul>
* <li>A new request is added with {@link #addRequest(OverrideRequest)}, in which case the
* request will become suspended.</li>
* <li>The request is cancelled with {@link #cancelRequest(IBinder)} or as a side effect
* of other methods calls, such as {@link #handleProcessDied(int)}.</li>
* </ul>
*/
final class OverrideRequestController {
static final int STATUS_UNKNOWN = 0;
/**
* The request is the top-most request.
*/
static final int STATUS_ACTIVE = 1;
/**
* The request is still present but is being superseded by another request.
*/
static final int STATUS_SUSPENDED = 2;
/**
* The request is not longer valid.
*/
static final int STATUS_CANCELED = 3;
@IntDef(prefix = {"STATUS_"}, value = {
STATUS_UNKNOWN,
STATUS_ACTIVE,
STATUS_SUSPENDED,
STATUS_CANCELED
})
@Retention(RetentionPolicy.SOURCE)
@interface RequestStatus {}
static String statusToString(@RequestStatus int status) {
switch (status) {
case STATUS_ACTIVE:
return "ACTIVE";
case STATUS_SUSPENDED:
return "SUSPENDED";
case STATUS_CANCELED:
return "CANCELED";
case STATUS_UNKNOWN:
return "UNKNOWN";
}
throw new IllegalArgumentException("Unknown status: " + status);
}
private final StatusChangeListener mListener;
private final List<OverrideRequest> mTmpRequestsToCancel = new ArrayList<>();
// List of override requests with the most recent override request at the end.
private final ArrayList<OverrideRequest> mRequests = new ArrayList<>();
private boolean mStickyRequestsAllowed;
// List of override requests that have outlived their process and will only be cancelled through
// a call to cancelStickyRequests().
private final ArrayList<OverrideRequest> mStickyRequests = new ArrayList<>();
OverrideRequestController(@NonNull StatusChangeListener listener) {
mListener = listener;
}
/**
* Sets sticky requests as either allowed or disallowed. When sticky requests are allowed a call
* to {@link #handleProcessDied(int)} will not result in the request being cancelled
* immediately. Instead, the request will be marked sticky and must be cancelled with a call
* to {@link #cancelStickyRequests()}.
*/
void setStickyRequestsAllowed(boolean stickyRequestsAllowed) {
mStickyRequestsAllowed = stickyRequestsAllowed;
if (!mStickyRequestsAllowed) {
cancelStickyRequests();
}
}
/**
* Adds a request to the top of the stack and notifies the listener of all changes to request
* status as a result of this operation.
*/
void addRequest(@NonNull OverrideRequest request) {
mRequests.add(request);
mListener.onStatusChanged(request, STATUS_ACTIVE);
if (mRequests.size() > 1) {
OverrideRequest prevRequest = mRequests.get(mRequests.size() - 2);
mListener.onStatusChanged(prevRequest, STATUS_SUSPENDED);
}
}
/**
* Cancels the request with the specified {@code token} and notifies the listener of all changes
* to request status as a result of this operation.
*/
void cancelRequest(@NonNull IBinder token) {
int index = getRequestIndex(token);
if (index == -1) {
return;
}
OverrideRequest request = mRequests.remove(index);
if (index == mRequests.size() && mRequests.size() > 0) {
// We removed the current active request so we need to set the new active request
// before cancelling this request.
OverrideRequest newTop = getLast(mRequests);
mListener.onStatusChanged(newTop, STATUS_ACTIVE);
}
mListener.onStatusChanged(request, STATUS_CANCELED);
}
/**
* Cancels all requests that are currently marked sticky and notifies the listener of all
* changes to request status as a result of this operation.
*
* @see #setStickyRequestsAllowed(boolean)
*/
void cancelStickyRequests() {
mTmpRequestsToCancel.clear();
mTmpRequestsToCancel.addAll(mStickyRequests);
cancelRequestsLocked(mTmpRequestsToCancel);
}
/**
* Returns {@code true} if this controller is current managing a request with the specified
* {@code token}, {@code false} otherwise.
*/
boolean hasRequest(@NonNull IBinder token) {
return getRequestIndex(token) != -1;
}
/**
* Notifies the controller that the process with the specified {@code pid} has died. The
* controller will notify the listener of all changes to request status as a result of this
* operation.
*/
void handleProcessDied(int pid) {
if (mRequests.isEmpty()) {
return;
}
mTmpRequestsToCancel.clear();
OverrideRequest prevActiveRequest = getLast(mRequests);
for (OverrideRequest request : mRequests) {
if (request.getPid() == pid) {
mTmpRequestsToCancel.add(request);
}
}
if (mStickyRequestsAllowed) {
// Do not cancel the requests now because sticky requests are allowed. These
// requests will be cancelled on a call to cancelStickyRequests().
mStickyRequests.addAll(mTmpRequestsToCancel);
return;
}
cancelRequestsLocked(mTmpRequestsToCancel);
}
/**
* Notifies the controller that the base state has changed. The controller will notify the
* listener of all changes to request status as a result of this change.
*
* @return {@code true} if calling this method has lead to a new active request, {@code false}
* otherwise.
*/
boolean handleBaseStateChanged() {
if (mRequests.isEmpty()) {
return false;
}
mTmpRequestsToCancel.clear();
OverrideRequest prevActiveRequest = getLast(mRequests);
for (int i = 0; i < mRequests.size(); i++) {
OverrideRequest request = mRequests.get(i);
if ((request.getFlags() & DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES) != 0) {
mTmpRequestsToCancel.add(request);
}
}
final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel);
return newActiveRequest;
}
/**
* Notifies the controller that the set of supported states has changed. The controller will
* notify the listener of all changes to request status as a result of this change.
*
* @return {@code true} if calling this method has lead to a new active request, {@code false}
* otherwise.
*/
boolean handleNewSupportedStates(int[] newSupportedStates) {
if (mRequests.isEmpty()) {
return false;
}
mTmpRequestsToCancel.clear();
for (int i = 0; i < mRequests.size(); i++) {
OverrideRequest request = mRequests.get(i);
if (!contains(newSupportedStates, request.getRequestedState())) {
mTmpRequestsToCancel.add(request);
}
}
final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel);
return newActiveRequest;
}
void dumpInternal(PrintWriter pw) {
final int requestCount = mRequests.size();
pw.println();
pw.println("Override requests: size=" + requestCount);
for (int i = 0; i < requestCount; i++) {
OverrideRequest overrideRequest = mRequests.get(i);
int status = (i == requestCount - 1) ? STATUS_ACTIVE : STATUS_SUSPENDED;
pw.println(" " + i + ": mPid=" + overrideRequest.getPid()
+ ", mRequestedState=" + overrideRequest.getRequestedState()
+ ", mFlags=" + overrideRequest.getFlags()
+ ", mStatus=" + statusToString(status));
}
}
/**
* Handles cancelling a set of requests. If the set of requests to cancel will lead to a new
* request becoming active this request will also be notified of its change in state.
*
* @return {@code true} if calling this method has lead to a new active request, {@code false}
* otherwise.
*/
private boolean cancelRequestsLocked(List<OverrideRequest> requestsToCancel) {
if (requestsToCancel.isEmpty()) {
return false;
}
OverrideRequest prevActiveRequest = getLast(mRequests);
boolean causedNewRequestToBecomeActive = false;
mRequests.removeAll(requestsToCancel);
mStickyRequests.removeAll(requestsToCancel);
if (!mRequests.isEmpty()) {
OverrideRequest newActiveRequest = getLast(mRequests);
if (newActiveRequest != prevActiveRequest) {
mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE);
causedNewRequestToBecomeActive = true;
}
}
for (int i = 0; i < requestsToCancel.size(); i++) {
mListener.onStatusChanged(requestsToCancel.get(i), STATUS_CANCELED);
}
return causedNewRequestToBecomeActive;
}
private int getRequestIndex(@NonNull IBinder token) {
final int numberOfRequests = mRequests.size();
if (numberOfRequests == 0) {
return -1;
}
for (int i = 0; i < numberOfRequests; i++) {
OverrideRequest request = mRequests.get(i);
if (request.getToken() == token) {
return i;
}
}
return -1;
}
@Nullable
private static <T> T getLast(List<T> list) {
return list.size() > 0 ? list.get(list.size() - 1) : null;
}
private static boolean contains(int[] array, int value) {
for (int i = 0; i < array.length; i++) {
if (array[i] == value) {
return true;
}
}
return false;
}
public interface StatusChangeListener {
/**
* Notifies the listener of a change in request status. If a change within the controller
* causes one request to become active and one to become either suspended or cancelled, this
* method is guaranteed to be called with the active request first before the suspended or
* cancelled request.
*/
void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus);
}
}

View File

@@ -41,6 +41,7 @@ import com.android.server.devicestate.DeviceState;
import com.android.server.devicestate.DeviceStateProvider; import com.android.server.devicestate.DeviceStateProvider;
import com.android.server.policy.devicestate.config.Conditions; import com.android.server.policy.devicestate.config.Conditions;
import com.android.server.policy.devicestate.config.DeviceStateConfig; import com.android.server.policy.devicestate.config.DeviceStateConfig;
import com.android.server.policy.devicestate.config.Flags;
import com.android.server.policy.devicestate.config.LidSwitchCondition; import com.android.server.policy.devicestate.config.LidSwitchCondition;
import com.android.server.policy.devicestate.config.NumericRange; import com.android.server.policy.devicestate.config.NumericRange;
import com.android.server.policy.devicestate.config.SensorCondition; import com.android.server.policy.devicestate.config.SensorCondition;
@@ -87,7 +88,7 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
@VisibleForTesting @VisibleForTesting
static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(MINIMUM_DEVICE_STATE, static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(MINIMUM_DEVICE_STATE,
"DEFAULT"); "DEFAULT", 0 /* flags */);
private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/"; private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/";
private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/"; private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/";
@@ -131,7 +132,26 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
config.getDeviceState()) { config.getDeviceState()) {
final int state = stateConfig.getIdentifier().intValue(); final int state = stateConfig.getIdentifier().intValue();
final String name = stateConfig.getName() == null ? "" : stateConfig.getName(); final String name = stateConfig.getName() == null ? "" : stateConfig.getName();
deviceStateList.add(new DeviceState(state, name));
int flags = 0;
final Flags configFlags = stateConfig.getFlags();
if (configFlags != null) {
List<String> configFlagStrings = configFlags.getFlag();
for (int i = 0; i < configFlagStrings.size(); i++) {
final String configFlagString = configFlagStrings.get(i);
switch (configFlagString) {
case "FLAG_CANCEL_STICKY_REQUESTS":
flags |= DeviceState.FLAG_CANCEL_STICKY_REQUESTS;
break;
default:
Slog.w(TAG, "Parsed unknown flag with name: "
+ configFlagString);
break;
}
}
}
deviceStateList.add(new DeviceState(state, name, flags));
final Conditions condition = stateConfig.getConditions(); final Conditions condition = stateConfig.getConditions();
conditionsList.add(condition); conditionsList.add(condition);

View File

@@ -40,10 +40,19 @@
<xs:element name="name" type="xs:string" minOccurs="0"> <xs:element name="name" type="xs:string" minOccurs="0">
<xs:annotation name="nullable" /> <xs:annotation name="nullable" />
</xs:element> </xs:element>
<xs:element name="flags" type="flags" />
<xs:element name="conditions" type="conditions" /> <xs:element name="conditions" type="conditions" />
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
<xs:complexType name="flags">
<xs:sequence>
<xs:element name="flag" type="xs:string" minOccurs="0" maxOccurs="unbounded">
<xs:annotation name="nullable" />
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="conditions"> <xs:complexType name="conditions">
<xs:sequence> <xs:sequence>
<xs:element name="lid-switch" type="lidSwitchCondition" minOccurs="0"> <xs:element name="lid-switch" type="lidSwitchCondition" minOccurs="0">

View File

@@ -11,9 +11,11 @@ package com.android.server.policy.devicestate.config {
public class DeviceState { public class DeviceState {
ctor public DeviceState(); ctor public DeviceState();
method public com.android.server.policy.devicestate.config.Conditions getConditions(); method public com.android.server.policy.devicestate.config.Conditions getConditions();
method public com.android.server.policy.devicestate.config.Flags getFlags();
method public java.math.BigInteger getIdentifier(); method public java.math.BigInteger getIdentifier();
method @Nullable public String getName(); method @Nullable public String getName();
method public void setConditions(com.android.server.policy.devicestate.config.Conditions); method public void setConditions(com.android.server.policy.devicestate.config.Conditions);
method public void setFlags(com.android.server.policy.devicestate.config.Flags);
method public void setIdentifier(java.math.BigInteger); method public void setIdentifier(java.math.BigInteger);
method public void setName(@Nullable String); method public void setName(@Nullable String);
} }
@@ -23,6 +25,11 @@ package com.android.server.policy.devicestate.config {
method public java.util.List<com.android.server.policy.devicestate.config.DeviceState> getDeviceState(); method public java.util.List<com.android.server.policy.devicestate.config.DeviceState> getDeviceState();
} }
public class Flags {
ctor public Flags();
method @Nullable public java.util.List<java.lang.String> getFlag();
}
public class LidSwitchCondition { public class LidSwitchCondition {
ctor public LidSwitchCondition(); ctor public LidSwitchCondition();
method public boolean getOpen(); method public boolean getOpen();

View File

@@ -18,6 +18,7 @@ package com.android.server.devicestate;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNotNull;
@@ -35,6 +36,11 @@ import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry; import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4; import androidx.test.runner.AndroidJUnit4;
import static org.mockito.Mockito.mock;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowProcessController;
import junit.framework.Assert; import junit.framework.Assert;
import org.junit.Before; import org.junit.Before;
@@ -55,10 +61,15 @@ import javax.annotation.Nullable;
@Presubmit @Presubmit
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public final class DeviceStateManagerServiceTest { public final class DeviceStateManagerServiceTest {
private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT"); private static final DeviceState DEFAULT_DEVICE_STATE =
private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(1, "OTHER"); new DeviceState(0, "DEFAULT", 0 /* flags */);
private static final DeviceState OTHER_DEVICE_STATE =
new DeviceState(1, "OTHER", 0 /* flags */);
// A device state that is not reported as being supported for the default test provider. // A device state that is not reported as being supported for the default test provider.
private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(255, "UNSUPPORTED"); private static final DeviceState UNSUPPORTED_DEVICE_STATE =
new DeviceState(255, "UNSUPPORTED", 0 /* flags */);
private static final int FAKE_PROCESS_ID = 100;
private TestDeviceStatePolicy mPolicy; private TestDeviceStatePolicy mPolicy;
private TestDeviceStateProvider mProvider; private TestDeviceStateProvider mProvider;
@@ -69,6 +80,25 @@ public final class DeviceStateManagerServiceTest {
mProvider = new TestDeviceStateProvider(); mProvider = new TestDeviceStateProvider();
mPolicy = new TestDeviceStatePolicy(mProvider); mPolicy = new TestDeviceStatePolicy(mProvider);
mService = new DeviceStateManagerService(InstrumentationRegistry.getContext(), mPolicy); mService = new DeviceStateManagerService(InstrumentationRegistry.getContext(), mPolicy);
// Necessary to allow us to check for top app process id in tests
mService.mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
WindowProcessController windowProcessController = mock(WindowProcessController.class);
when(mService.mActivityTaskManagerInternal.getTopApp())
.thenReturn(windowProcessController);
when(windowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID);
flushHandler(); // Flush the handler to ensure the initial values are committed.
}
private void flushHandler() {
flushHandler(1);
}
private void flushHandler(int count) {
for (int i = 0; i < count; i++) {
mService.getHandler().runWithScissors(() -> {}, 0);
}
} }
@Test @Test
@@ -80,6 +110,7 @@ public final class DeviceStateManagerServiceTest {
DEFAULT_DEVICE_STATE.getIdentifier()); DEFAULT_DEVICE_STATE.getIdentifier());
mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
flushHandler();
assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE)); assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty()); assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE)); assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
@@ -92,6 +123,7 @@ public final class DeviceStateManagerServiceTest {
mPolicy.blockConfigure(); mPolicy.blockConfigure();
mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
flushHandler();
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE));
assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE)); assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
@@ -99,6 +131,7 @@ public final class DeviceStateManagerServiceTest {
OTHER_DEVICE_STATE.getIdentifier()); OTHER_DEVICE_STATE.getIdentifier());
mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier()); mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier());
flushHandler();
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE));
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
@@ -106,6 +139,7 @@ public final class DeviceStateManagerServiceTest {
OTHER_DEVICE_STATE.getIdentifier()); OTHER_DEVICE_STATE.getIdentifier());
mPolicy.resumeConfigure(); mPolicy.resumeConfigure();
flushHandler();
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty()); assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
@@ -149,6 +183,7 @@ public final class DeviceStateManagerServiceTest {
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE }); mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE });
flushHandler();
// The current committed and requests states do not change because the current state remains // The current committed and requests states do not change because the current state remains
// supported. // supported.
@@ -166,6 +201,7 @@ public final class DeviceStateManagerServiceTest {
mService.getBinderService().registerCallback(callback); mService.getBinderService().registerCallback(callback);
// An initial callback will be triggered on registration, so we clear it here. // An initial callback will be triggered on registration, so we clear it here.
flushHandler();
callback.clearLastNotifiedInfo(); callback.clearLastNotifiedInfo();
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
@@ -174,6 +210,7 @@ public final class DeviceStateManagerServiceTest {
mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE, mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE }); OTHER_DEVICE_STATE });
flushHandler();
// The current committed and requests states do not change because the current state remains // The current committed and requests states do not change because the current state remains
// supported. // supported.
@@ -203,12 +240,14 @@ public final class DeviceStateManagerServiceTest {
mService.getBinderService().registerCallback(callback); mService.getBinderService().registerCallback(callback);
mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
flushHandler();
assertEquals(callback.getLastNotifiedInfo().baseState, assertEquals(callback.getLastNotifiedInfo().baseState,
OTHER_DEVICE_STATE.getIdentifier()); OTHER_DEVICE_STATE.getIdentifier());
assertEquals(callback.getLastNotifiedInfo().currentState, assertEquals(callback.getLastNotifiedInfo().currentState,
OTHER_DEVICE_STATE.getIdentifier()); OTHER_DEVICE_STATE.getIdentifier());
mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier()); mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier());
flushHandler();
assertEquals(callback.getLastNotifiedInfo().baseState, assertEquals(callback.getLastNotifiedInfo().baseState,
DEFAULT_DEVICE_STATE.getIdentifier()); DEFAULT_DEVICE_STATE.getIdentifier());
assertEquals(callback.getLastNotifiedInfo().currentState, assertEquals(callback.getLastNotifiedInfo().currentState,
@@ -216,6 +255,7 @@ public final class DeviceStateManagerServiceTest {
mPolicy.blockConfigure(); mPolicy.blockConfigure();
mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
flushHandler();
// The callback should not have been notified of the state change as the policy is still // The callback should not have been notified of the state change as the policy is still
// pending callback. // pending callback.
assertEquals(callback.getLastNotifiedInfo().baseState, assertEquals(callback.getLastNotifiedInfo().baseState,
@@ -224,6 +264,7 @@ public final class DeviceStateManagerServiceTest {
DEFAULT_DEVICE_STATE.getIdentifier()); DEFAULT_DEVICE_STATE.getIdentifier());
mPolicy.resumeConfigure(); mPolicy.resumeConfigure();
flushHandler();
// Now that the policy is finished processing the callback should be notified of the state // Now that the policy is finished processing the callback should be notified of the state
// change. // change.
assertEquals(callback.getLastNotifiedInfo().baseState, assertEquals(callback.getLastNotifiedInfo().baseState,
@@ -236,6 +277,7 @@ public final class DeviceStateManagerServiceTest {
public void registerCallback_emitsInitialValue() throws RemoteException { public void registerCallback_emitsInitialValue() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback); mService.getBinderService().registerCallback(callback);
flushHandler();
assertNotNull(callback.getLastNotifiedInfo()); assertNotNull(callback.getLastNotifiedInfo());
assertEquals(callback.getLastNotifiedInfo().baseState, assertEquals(callback.getLastNotifiedInfo().baseState,
DEFAULT_DEVICE_STATE.getIdentifier()); DEFAULT_DEVICE_STATE.getIdentifier());
@@ -247,6 +289,7 @@ public final class DeviceStateManagerServiceTest {
public void requestState() throws RemoteException { public void requestState() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback); mService.getBinderService().registerCallback(callback);
flushHandler();
final IBinder token = new Binder(); final IBinder token = new Binder();
assertEquals(callback.getLastNotifiedStatus(token), assertEquals(callback.getLastNotifiedStatus(token),
@@ -254,6 +297,10 @@ public final class DeviceStateManagerServiceTest {
mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(), mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
0 /* flags */); 0 /* flags */);
// Flush the handler twice. The first flush ensures the request is added and the policy is
// notified, while the second flush ensures the callback is notified once the change is
// committed.
flushHandler(2 /* count */);
assertEquals(callback.getLastNotifiedStatus(token), assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_ACTIVE); TestDeviceStateManagerCallback.STATUS_ACTIVE);
@@ -271,6 +318,7 @@ public final class DeviceStateManagerServiceTest {
OTHER_DEVICE_STATE.getIdentifier()); OTHER_DEVICE_STATE.getIdentifier());
mService.getBinderService().cancelRequest(token); mService.getBinderService().cancelRequest(token);
flushHandler();
assertEquals(callback.getLastNotifiedStatus(token), assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_CANCELED); TestDeviceStateManagerCallback.STATUS_CANCELED);
@@ -291,6 +339,7 @@ public final class DeviceStateManagerServiceTest {
public void requestState_pendingStateAtRequest() throws RemoteException { public void requestState_pendingStateAtRequest() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback); mService.getBinderService().registerCallback(callback);
flushHandler();
mPolicy.blockConfigure(); mPolicy.blockConfigure();
@@ -303,6 +352,10 @@ public final class DeviceStateManagerServiceTest {
mService.getBinderService().requestState(firstRequestToken, mService.getBinderService().requestState(firstRequestToken,
OTHER_DEVICE_STATE.getIdentifier(), 0 /* flags */); OTHER_DEVICE_STATE.getIdentifier(), 0 /* flags */);
// Flush the handler twice. The first flush ensures the request is added and the policy is
// notified, while the second flush ensures the callback is notified once the change is
// committed.
flushHandler(2 /* count */);
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE));
@@ -312,8 +365,8 @@ public final class DeviceStateManagerServiceTest {
mService.getBinderService().requestState(secondRequestToken, mService.getBinderService().requestState(secondRequestToken,
DEFAULT_DEVICE_STATE.getIdentifier(), 0 /* flags */); DEFAULT_DEVICE_STATE.getIdentifier(), 0 /* flags */);
mPolicy.resumeConfigureOnce(); mPolicy.resumeConfigureOnce();
flushHandler();
// First request status is now suspended as there is another pending request. // First request status is now suspended as there is another pending request.
assertEquals(callback.getLastNotifiedStatus(firstRequestToken), assertEquals(callback.getLastNotifiedStatus(firstRequestToken),
@@ -330,6 +383,7 @@ public final class DeviceStateManagerServiceTest {
DEFAULT_DEVICE_STATE.getIdentifier()); DEFAULT_DEVICE_STATE.getIdentifier());
mPolicy.resumeConfigure(); mPolicy.resumeConfigure();
flushHandler();
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty()); assertEquals(mService.getPendingState(), Optional.empty());
@@ -339,6 +393,7 @@ public final class DeviceStateManagerServiceTest {
// Now cancel the second request to make the first request active. // Now cancel the second request to make the first request active.
mService.getBinderService().cancelRequest(secondRequestToken); mService.getBinderService().cancelRequest(secondRequestToken);
flushHandler();
assertEquals(callback.getLastNotifiedStatus(firstRequestToken), assertEquals(callback.getLastNotifiedStatus(firstRequestToken),
TestDeviceStateManagerCallback.STATUS_ACTIVE); TestDeviceStateManagerCallback.STATUS_ACTIVE);
@@ -356,6 +411,7 @@ public final class DeviceStateManagerServiceTest {
public void requestState_sameAsBaseState() throws RemoteException { public void requestState_sameAsBaseState() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback); mService.getBinderService().registerCallback(callback);
flushHandler();
final IBinder token = new Binder(); final IBinder token = new Binder();
assertEquals(callback.getLastNotifiedStatus(token), assertEquals(callback.getLastNotifiedStatus(token),
@@ -363,6 +419,7 @@ public final class DeviceStateManagerServiceTest {
mService.getBinderService().requestState(token, DEFAULT_DEVICE_STATE.getIdentifier(), mService.getBinderService().requestState(token, DEFAULT_DEVICE_STATE.getIdentifier(),
0 /* flags */); 0 /* flags */);
flushHandler();
assertEquals(callback.getLastNotifiedStatus(token), assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_ACTIVE); TestDeviceStateManagerCallback.STATUS_ACTIVE);
@@ -372,6 +429,7 @@ public final class DeviceStateManagerServiceTest {
public void requestState_flagCancelWhenBaseChanges() throws RemoteException { public void requestState_flagCancelWhenBaseChanges() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback); mService.getBinderService().registerCallback(callback);
flushHandler();
final IBinder token = new Binder(); final IBinder token = new Binder();
assertEquals(callback.getLastNotifiedStatus(token), assertEquals(callback.getLastNotifiedStatus(token),
@@ -379,6 +437,10 @@ public final class DeviceStateManagerServiceTest {
mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(), mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES); DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES);
// Flush the handler twice. The first flush ensures the request is added and the policy is
// notified, while the second flush ensures the callback is notified once the change is
// committed.
flushHandler(2 /* count */);
assertEquals(callback.getLastNotifiedStatus(token), assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_ACTIVE); TestDeviceStateManagerCallback.STATUS_ACTIVE);
@@ -391,6 +453,7 @@ public final class DeviceStateManagerServiceTest {
OTHER_DEVICE_STATE.getIdentifier()); OTHER_DEVICE_STATE.getIdentifier());
mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
flushHandler();
// Request is canceled because the base state changed. // Request is canceled because the base state changed.
assertEquals(callback.getLastNotifiedStatus(token), assertEquals(callback.getLastNotifiedStatus(token),
@@ -407,6 +470,7 @@ public final class DeviceStateManagerServiceTest {
public void requestState_becomesUnsupported() throws RemoteException { public void requestState_becomesUnsupported() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback); mService.getBinderService().registerCallback(callback);
flushHandler();
final IBinder token = new Binder(); final IBinder token = new Binder();
assertEquals(callback.getLastNotifiedStatus(token), assertEquals(callback.getLastNotifiedStatus(token),
@@ -414,6 +478,7 @@ public final class DeviceStateManagerServiceTest {
mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(), mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
0 /* flags */); 0 /* flags */);
flushHandler();
assertEquals(callback.getLastNotifiedStatus(token), assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_ACTIVE); TestDeviceStateManagerCallback.STATUS_ACTIVE);
@@ -425,6 +490,7 @@ public final class DeviceStateManagerServiceTest {
OTHER_DEVICE_STATE.getIdentifier()); OTHER_DEVICE_STATE.getIdentifier());
mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE }); mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE });
flushHandler();
// Request is canceled because the state is no longer supported. // Request is canceled because the state is no longer supported.
assertEquals(callback.getLastNotifiedStatus(token), assertEquals(callback.getLastNotifiedStatus(token),

View File

@@ -41,24 +41,26 @@ public final class DeviceStateTest {
@Test @Test
public void testConstruct() { public void testConstruct() {
final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE /* identifier */, final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE /* identifier */,
"CLOSED" /* name */); "CLOSED" /* name */, DeviceState.FLAG_CANCEL_STICKY_REQUESTS /* flags */);
assertEquals(state.getIdentifier(), MINIMUM_DEVICE_STATE); assertEquals(state.getIdentifier(), MINIMUM_DEVICE_STATE);
assertEquals(state.getName(), "CLOSED"); assertEquals(state.getName(), "CLOSED");
assertEquals(state.getFlags(), DeviceState.FLAG_CANCEL_STICKY_REQUESTS);
} }
@Test @Test
public void testConstruct_nullName() { public void testConstruct_nullName() {
final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE /* identifier */, final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE /* identifier */,
null /* name */); null /* name */, 0/* flags */);
assertEquals(state.getIdentifier(), MAXIMUM_DEVICE_STATE); assertEquals(state.getIdentifier(), MAXIMUM_DEVICE_STATE);
assertNull(state.getName()); assertNull(state.getName());
assertEquals(state.getFlags(), 0);
} }
@Test @Test
public void testConstruct_tooLargeIdentifier() { public void testConstruct_tooLargeIdentifier() {
assertThrows(IllegalArgumentException.class, () -> { assertThrows(IllegalArgumentException.class, () -> {
final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE + 1 /* identifier */, final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE + 1 /* identifier */,
null /* name */); null /* name */, 0 /* flags */);
}); });
} }
@@ -66,7 +68,7 @@ public final class DeviceStateTest {
public void testConstruct_tooSmallIdentifier() { public void testConstruct_tooSmallIdentifier() {
assertThrows(IllegalArgumentException.class, () -> { assertThrows(IllegalArgumentException.class, () -> {
final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE - 1 /* identifier */, final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE - 1 /* identifier */,
null /* name */); null /* name */, 0 /* flags */);
}); });
} }
} }

View File

@@ -0,0 +1,230 @@
/*
* 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 com.android.server.devicestate;
import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
import static com.android.server.devicestate.OverrideRequestController.STATUS_SUSPENDED;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
import android.annotation.Nullable;
import android.hardware.devicestate.DeviceStateRequest;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.HashMap;
import java.util.Map;
/**
* Unit tests for {@link OverrideRequestController}.
* <p/>
* Run with <code>atest OverrideRequestControllerTest</code>.
*/
@Presubmit
@RunWith(AndroidJUnit4.class)
public final class OverrideRequestControllerTest {
private TestStatusChangeListener mStatusListener;
private OverrideRequestController mController;
@Before
public void setup() {
mStatusListener = new TestStatusChangeListener();
mController = new OverrideRequestController(mStatusListener);
}
@Test
public void addRequest() {
OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
assertNull(mStatusListener.getLastStatus(request));
mController.addRequest(request);
assertEquals(mStatusListener.getLastStatus(request).intValue(), STATUS_ACTIVE);
}
@Test
public void addRequest_suspendExistingRequest() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
assertNull(mStatusListener.getLastStatus(firstRequest));
mController.addRequest(firstRequest);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
assertNull(mStatusListener.getLastStatus(secondRequest));
mController.addRequest(secondRequest);
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
}
@Test
public void addRequest_cancelActiveRequest() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
mController.addRequest(firstRequest);
mController.addRequest(secondRequest);
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
mController.cancelRequest(secondRequest.getToken());
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
}
@Test
public void addRequest_cancelSuspendedRequest() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
mController.addRequest(firstRequest);
mController.addRequest(secondRequest);
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
mController.cancelRequest(firstRequest.getToken());
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
@Test
public void handleBaseStateChanged() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */,
DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */);
mController.addRequest(firstRequest);
mController.addRequest(secondRequest);
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
mController.handleBaseStateChanged();
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
}
@Test
public void handleProcessDied() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
OverrideRequest secondRequest = new OverrideRequest(new Binder(), 1 /* pid */,
0 /* requestedState */, 0 /* flags */);
mController.addRequest(firstRequest);
mController.addRequest(secondRequest);
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
mController.handleProcessDied(1);
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
mController.handleProcessDied(0);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
@Test
public void handleProcessDied_stickyRequests() {
mController.setStickyRequestsAllowed(true);
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
0 /* requestedState */, 0 /* flags */);
OverrideRequest secondRequest = new OverrideRequest(new Binder(), 1 /* pid */,
0 /* requestedState */, 0 /* flags */);
mController.addRequest(firstRequest);
mController.addRequest(secondRequest);
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
mController.handleProcessDied(1);
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
mController.cancelStickyRequests();
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
}
@Test
public void handleNewSupportedStates() {
OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
1 /* requestedState */, 0 /* flags */);
OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
2 /* requestedState */, 0 /* flags */);
mController.addRequest(firstRequest);
mController.addRequest(secondRequest);
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
mController.handleNewSupportedStates(new int[]{ 0, 1 });
assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
mController.handleNewSupportedStates(new int[]{ 0 });
assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
}
private static final class TestStatusChangeListener implements
OverrideRequestController.StatusChangeListener {
private Map<OverrideRequest, Integer> mLastStatusMap = new HashMap<>();
@Override
public void onStatusChanged(@NonNull OverrideRequest request, int newStatus) {
mLastStatusMap.put(request, newStatus);
}
@Nullable
public Integer getLastStatus(OverrideRequest request) {
return mLastStatusMap.get(request);
}
}
}

View File

@@ -150,8 +150,9 @@ public final class DeviceStateProviderImplTest {
provider.setListener(listener); provider.setListener(listener);
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
final DeviceState[] expectedStates = new DeviceState[]{ new DeviceState(1, ""), final DeviceState[] expectedStates = new DeviceState[]{
new DeviceState(2, "") }; new DeviceState(1, "", 0 /* flags */),
new DeviceState(2, "", 0 /* flags */) };
assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue()); assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
verify(listener).onStateChanged(mIntegerCaptor.capture()); verify(listener).onStateChanged(mIntegerCaptor.capture());
@@ -187,8 +188,9 @@ public final class DeviceStateProviderImplTest {
provider.setListener(listener); provider.setListener(listener);
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
final DeviceState[] expectedStates = new DeviceState[]{ new DeviceState(1, ""), final DeviceState[] expectedStates = new DeviceState[]{
new DeviceState(2, "CLOSED") }; new DeviceState(1, "", 0 /* flags */),
new DeviceState(2, "CLOSED", 0 /* flags */) };
assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue()); assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
// onStateChanged() should not be called because the provider has not yet been notified of // onStateChanged() should not be called because the provider has not yet been notified of
@@ -264,8 +266,11 @@ public final class DeviceStateProviderImplTest {
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
assertArrayEquals( assertArrayEquals(
new DeviceState[]{ new DeviceState(1, "CLOSED"), new DeviceState(2, "HALF_OPENED"), new DeviceState[]{
new DeviceState(3, "OPENED") }, mDeviceStateArrayCaptor.getValue()); new DeviceState(1, "CLOSED", 0 /* flags */),
new DeviceState(2, "HALF_OPENED", 0 /* flags */),
new DeviceState(3, "OPENED", 0 /* flags */) },
mDeviceStateArrayCaptor.getValue());
// onStateChanged() should not be called because the provider has not yet been notified of // onStateChanged() should not be called because the provider has not yet been notified of
// the initial sensor state. // the initial sensor state.
verify(listener, never()).onStateChanged(mIntegerCaptor.capture()); verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
@@ -350,8 +355,10 @@ public final class DeviceStateProviderImplTest {
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
assertArrayEquals( assertArrayEquals(
new DeviceState[]{ new DeviceState(1, "CLOSED"), new DeviceState(2, "HALF_OPENED"), new DeviceState[]{
}, mDeviceStateArrayCaptor.getValue()); new DeviceState(1, "CLOSED", 0 /* flags */),
new DeviceState(2, "HALF_OPENED", 0 /* flags */)
}, mDeviceStateArrayCaptor.getValue());
// onStateChanged() should be called because the provider could not find the sensor. // onStateChanged() should be called because the provider could not find the sensor.
verify(listener).onStateChanged(mIntegerCaptor.capture()); verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(1, mIntegerCaptor.getValue().intValue()); assertEquals(1, mIntegerCaptor.getValue().intValue());