diff options
13 files changed, 1025 insertions, 324 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index f6b437405049..0732c6136401 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1133,10 +1133,10 @@ package android.hardware.camera2 { package android.hardware.devicestate { 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 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); field public static final int MAXIMUM_DEVICE_STATE = 255; // 0xff field public static final int MINIMUM_DEVICE_STATE = 0; // 0x0 diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java index 52dad3efefb8..95892aa7af81 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManager.java +++ b/core/java/android/hardware/devicestate/DeviceStateManager.java @@ -75,23 +75,24 @@ public final class DeviceStateManager { /** * Submits a {@link DeviceStateRequest request} to modify the device state. * <p> - * By default, the request is kept active until a call to - * {@link #cancelRequest(DeviceStateRequest)} or until one of the following occurs: + * By default, the request is kept active until one of the following occurs: * <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 * will be suspended until the interrupting request is canceled. - * <li>The requested state has become unsupported. - * <li>The process submitting the request dies. * </ul> * However, this behavior can be changed by setting flags on the {@link DeviceStateRequest}. * * @throws IllegalArgumentException if the requested state is unsupported. - * @throws SecurityException if the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} - * permission is not held. + * @throws SecurityException if the caller is neither the current top-focused activity nor if + * the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held. * * @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, @Nullable @CallbackExecutor Executor executor, @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 * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. * - * @throws SecurityException if the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} - * permission is not held. + * @throws SecurityException if the caller is neither the current top-focused activity nor if + * 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) { mGlobal.cancelRequest(request); } diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java index e693bcc93f8f..7fe24ff1f069 100644 --- a/services/core/java/com/android/server/devicestate/DeviceState.java +++ b/services/core/java/com/android/server/devicestate/DeviceState.java @@ -19,11 +19,14 @@ package com.android.server.devicestate; import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -39,6 +42,19 @@ import java.util.Objects; * @see DeviceStateManagerService */ 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. */ @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) private final int mIdentifier; @@ -47,14 +63,19 @@ public final class DeviceState { @NonNull private final String mName; + @DeviceStateFlags + private final int mFlags; + public DeviceState( @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, "identifier"); mIdentifier = identifier; mName = name; + mFlags = flags; } /** Returns the unique identifier for the device state. */ @@ -69,6 +90,11 @@ public final class DeviceState { return mName; } + @DeviceStateFlags + public int getFlags() { + return mFlags; + } + @Override public String toString() { return "DeviceState{" + "identifier=" + mIdentifier + ", name='" + mName + '\'' + '}'; @@ -80,11 +106,12 @@ public final class DeviceState { if (o == null || getClass() != o.getClass()) return false; DeviceState that = (DeviceState) o; return mIdentifier == that.mIdentifier - && Objects.equals(mName, that.mName); + && Objects.equals(mName, that.mName) + && mFlags == that.mFlags; } @Override public int hashCode() { - return Objects.hash(mIdentifier, mName); + return Objects.hash(mIdentifier, mName, mFlags); } } diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index a8b0994402e8..383b3928e5e6 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -19,8 +19,13 @@ package com.android.server.devicestate; import static android.Manifest.permission.CONTROL_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.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.NonNull; import android.annotation.Nullable; @@ -30,12 +35,12 @@ import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.IDeviceStateManager; import android.hardware.devicestate.IDeviceStateManagerCallback; import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; -import android.util.ArrayMap; -import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; @@ -43,14 +48,21 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; 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.policy.DeviceStatePolicyImpl; +import com.android.server.wm.ActivityTaskManagerInternal; +import com.android.server.wm.WindowProcessController; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Optional; +import java.util.WeakHashMap; /** * 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 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 private final DeviceStatePolicy mDeviceStatePolicy; @NonNull private final BinderService mBinderService; + @NonNull + private final OverrideRequestController mOverrideRequestController; + @VisibleForTesting + @NonNull + public ActivityTaskManagerInternal mActivityTaskManagerInternal; // All supported device states keyed by identifier. @GuardedBy("mLock") @@ -109,17 +131,16 @@ public final class DeviceStateManagerService extends SystemService { @NonNull 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 // request status indexed by process id. @GuardedBy("mLock") private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>(); - // List of override requests with the highest precedence request at the end. - @GuardedBy("mLock") - private final ArrayList<OverrideRequestRecord> mRequestRecords = new ArrayList<>(); - // Set of override requests that are pending a call to notifyStatusIfNeeded() to be notified - // of a change in status. - @GuardedBy("mLock") - private final ArraySet<OverrideRequestRecord> mRequestsPendingStatusChange = new ArraySet<>(); public DeviceStateManagerService(@NonNull Context context) { this(context, new DeviceStatePolicyImpl(context)); @@ -128,9 +149,17 @@ public final class DeviceStateManagerService extends SystemService { @VisibleForTesting DeviceStateManagerService(@NonNull Context context, @NonNull DeviceStatePolicy policy) { 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.getDeviceStateProvider().setListener(new DeviceStateProviderListener()); mBinderService = new BinderService(); + mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); } @Override @@ -138,6 +167,11 @@ public final class DeviceStateManagerService extends SystemService { 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 * configuring a different state. @@ -191,12 +225,10 @@ public final class DeviceStateManagerService extends SystemService { @NonNull Optional<DeviceState> getOverrideState() { synchronized (mLock) { - if (mRequestRecords.isEmpty()) { - return Optional.empty(); + if (mActiveOverride.isPresent()) { + return getStateLocked(mActiveOverride.get().getRequestedState()); } - - OverrideRequestRecord topRequest = mRequestRecords.get(mRequestRecords.size() - 1); - return Optional.of(topRequest.mRequestedState); + return Optional.empty(); } } @@ -247,43 +279,41 @@ public final class DeviceStateManagerService extends SystemService { } private void updateSupportedStates(DeviceState[] supportedDeviceStates) { - boolean updatedPendingState; - boolean hasBaseState; synchronized (mLock) { 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(); for (int i = 0; i < supportedDeviceStates.length; i++) { DeviceState state = supportedDeviceStates[i]; + if ((state.getFlags() & DeviceState.FLAG_CANCEL_STICKY_REQUESTS) != 0) { + hasTerminalDeviceState = true; + } mDeviceStates.put(state.getIdentifier(), state); } + mOverrideRequestController.setStickyRequestsAllowed(hasTerminalDeviceState); + final int[] newStateIdentifiers = getSupportedStateIdentifiersLocked(); if (Arrays.equals(oldStateIdentifiers, newStateIdentifiers)) { return; } - final int requestSize = mRequestRecords.size(); - for (int i = 0; i < requestSize; i++) { - OverrideRequestRecord request = mRequestRecords.get(i); - if (!isSupportedStateLocked(request.mRequestedState.getIdentifier())) { - request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED); - } - } + mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers); + updatePendingStateLocked(); - updatedPendingState = updatePendingStateLocked(); - hasBaseState = mBaseState.isPresent(); - } + if (!mPendingState.isPresent()) { + // 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(); + } - 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(); + mHandler.post(this::notifyPolicyIfNeeded); } - - notifyRequestsOfStatusChangeIfNeeded(); - notifyPolicyIfNeeded(); } /** @@ -311,7 +341,6 @@ public final class DeviceStateManagerService extends SystemService { * @see #isSupportedStateLocked(int) */ private void setBaseState(int identifier) { - boolean updatedPendingState; synchronized (mLock) { final Optional<DeviceState> baseStateOptional = getStateLocked(identifier); if (!baseStateOptional.isPresent()) { @@ -325,26 +354,21 @@ public final class DeviceStateManagerService extends SystemService { } mBaseState = Optional.of(baseState); - final int requestSize = mRequestRecords.size(); - for (int i = 0; i < requestSize; i++) { - OverrideRequestRecord request = mRequestRecords.get(i); - if ((request.mFlags & FLAG_CANCEL_WHEN_BASE_CHANGES) > 0) { - request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED); - } + if ((baseState.getFlags() & DeviceState.FLAG_CANCEL_STICKY_REQUESTS) != 0) { + mOverrideRequestController.cancelStickyRequests(); } + mOverrideRequestController.handleBaseStateChanged(); + updatePendingStateLocked(); - updatedPendingState = 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(); + } - 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(); + mHandler.post(this::notifyPolicyIfNeeded); } - - notifyRequestsOfStatusChangeIfNeeded(); - notifyPolicyIfNeeded(); } /** @@ -362,8 +386,8 @@ public final class DeviceStateManagerService extends SystemService { } final DeviceState stateToConfigure; - if (!mRequestRecords.isEmpty()) { - stateToConfigure = mRequestRecords.get(mRequestRecords.size() - 1).mRequestedState; + if (mActiveOverride.isPresent()) { + stateToConfigure = getStateLocked(mActiveOverride.get().getRequestedState()).get(); } else if (mBaseState.isPresent() && isSupportedStateLocked(mBaseState.get().getIdentifier())) { // Base state could have recently become unsupported after a change in supported states. @@ -429,108 +453,106 @@ public final class DeviceStateManagerService extends SystemService { * </p> */ private void commitPendingState() { - // Update the current state. synchronized (mLock) { final DeviceState newState = mPendingState.get(); if (DEBUG) { 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, newState.getIdentifier(), !mCommittedState.isPresent()); mCommittedState = Optional.of(newState); mPendingState = Optional.empty(); updatePendingStateLocked(); - } - - // Notify callbacks of a change. - notifyDeviceStateInfoChanged(); - // Notify the top request that it's active. - notifyRequestsOfStatusChangeIfNeeded(); - - // Try to configure the next state if needed. - notifyPolicyIfNeeded(); - } + // 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()); + } + } - private void notifyDeviceStateInfoChanged() { - if (Thread.holdsLock(mLock)) { - throw new IllegalStateException( - "Attempting to notify callbacks with service lock held."); + // Try to configure the next state if needed. + mHandler.post(this::notifyPolicyIfNeeded); } + } - // Grab the lock and copy the process records and the current info. - ArrayList<ProcessRecord> registeredProcesses; - DeviceStateInfo info; + private void notifyDeviceStateInfoChangedAsync() { synchronized (mLock) { if (mProcessRecords.size() == 0) { return; } - registeredProcesses = new ArrayList<>(); + ArrayList<ProcessRecord> registeredProcesses = new ArrayList<>(); for (int i = 0; i < mProcessRecords.size(); 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++) { - registeredProcesses.get(i).notifyDeviceStateInfoAsync(info); + for (int i = 0; i < registeredProcesses.size(); i++) { + registeredProcesses.get(i).notifyDeviceStateInfoAsync(info); + } } } - /** - * Notifies all dirty requests (requests that have a change in status, but have not yet been - * notified) that their status has changed. - */ - private void notifyRequestsOfStatusChangeIfNeeded() { - if (Thread.holdsLock(mLock)) { - throw new IllegalStateException( - "Attempting to notify requests with service lock held."); + private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request, + @OverrideRequestController.RequestStatus int status) { + if (status == STATUS_ACTIVE) { + mActiveOverride = Optional.of(request); + } else if (status == STATUS_SUSPENDED || status == STATUS_CANCELED) { + if (mActiveOverride.isPresent() && mActiveOverride.get() == request) { + mActiveOverride = Optional.empty(); + } + } else { + throw new IllegalArgumentException("Unknown request status: " + status); } - ArraySet<OverrideRequestRecord> dirtyRequests; - synchronized (mLock) { - if (mRequestsPendingStatusChange.isEmpty()) { - return; - } + boolean updatedPendingState = updatePendingStateLocked(); - dirtyRequests = new ArraySet<>(mRequestsPendingStatusChange); - mRequestsPendingStatusChange.clear(); + 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; } - // After releasing the lock, send the notifications out. - for (int i = 0; i < dirtyRequests.size(); i++) { - dirtyRequests.valueAt(i).notifyStatusIfNeeded(); + 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) { - DeviceStateInfo currentInfo; - ProcessRecord record; - // Grab the lock to register the callback and get the current state. synchronized (mLock) { if (mProcessRecords.contains(pid)) { throw new SecurityException("The calling process has already registered an" + " IDeviceStateManagerCallback."); } - record = new ProcessRecord(callback, pid); + ProcessRecord record = new ProcessRecord(callback, pid, this::handleProcessDied, + mHandlerThread.getThreadHandler()); try { callback.asBinder().linkToDeath(record, 0); } catch (RemoteException ex) { @@ -538,34 +560,21 @@ public final class DeviceStateManagerService extends SystemService { } mProcessRecords.put(pid, record); - currentInfo = mCommittedState.isPresent() ? getDeviceStateInfoLocked() : null; - } - - if (currentInfo != null) { - // If there is not a committed state we'll wait to notify the process of the initial - // value. - record.notifyDeviceStateInfoAsync(currentInfo); + DeviceStateInfo currentInfo = mCommittedState.isPresent() + ? getDeviceStateInfoLocked() : null; + if (currentInfo != null) { + // If there is not a committed state we'll wait to notify the process of the initial + // value. + record.notifyDeviceStateInfoAsync(currentInfo); + } } } private void handleProcessDied(ProcessRecord processRecord) { 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); - - updatePendingStateLocked(); + mOverrideRequestController.handleProcessDied(processRecord.mPid); } - - notifyPolicyIfNeeded(); } private void requestStateInternal(int state, int flags, int callingPid, @@ -577,7 +586,7 @@ public final class DeviceStateManagerService extends SystemService { + " 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" + " token: " + token); } @@ -588,27 +597,9 @@ public final class DeviceStateManagerService extends SystemService { + " is not supported."); } - OverrideRequestRecord topRecord = mRequestRecords.isEmpty() - ? null : mRequestRecords.get(mRequestRecords.size() - 1); - if (topRecord != null) { - topRecord.setStatusLocked(OverrideRequestRecord.STATUS_SUSPENDED); - } - - final OverrideRequestRecord request = - new OverrideRequestRecord(processRecord, token, deviceState.get(), flags); - mRequestRecords.add(request); - processRecord.mRequestRecords.put(request.mToken, request); - - 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 */); - } + OverrideRequest request = new OverrideRequest(token, callingPid, state, flags); + mOverrideRequestController.addRequest(request); } - - notifyRequestsOfStatusChangeIfNeeded(); - notifyPolicyIfNeeded(); } private void cancelRequestInternal(int callingPid, @NonNull IBinder token) { @@ -619,18 +610,8 @@ public final class DeviceStateManagerService extends SystemService { + " has no registered callback."); } - OverrideRequestRecord request = processRecord.mRequestRecords.get(token); - if (request == null) { - throw new IllegalStateException("No known request for the given token"); - } - - request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED); - - updatePendingStateLocked(); + mOverrideRequestController.cancelRequest(token); } - - notifyRequestsOfStatusChangeIfNeeded(); - notifyPolicyIfNeeded(); } private void dumpInternal(PrintWriter pw) { @@ -650,16 +631,7 @@ public final class DeviceStateManagerService extends SystemService { pw.println(" " + i + ": mPid=" + processRecord.mPid); } - final int requestCount = mRequestRecords.size(); - pw.println(); - pw.println("Override requests: size=" + requestCount); - for (int i = 0; i < requestCount; i++) { - OverrideRequestRecord requestRecord = mRequestRecords.get(i); - pw.println(" " + i + ": mPid=" + requestRecord.mProcessRecord.mPid - + ", mRequestedState=" + requestRecord.mRequestedState - + ", mFlags=" + requestRecord.mFlags - + ", mStatus=" + requestRecord.statusToString(requestRecord.mStatus)); - } + mOverrideRequestController.dumpInternal(pw); } } @@ -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 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; mPid = pid; + mDeathListener = deathListener; + mHandler = handler; } @Override public void binderDied() { - handleProcessDied(this); + mDeathListener.onProcessDied(this); } public void notifyDeviceStateInfoAsync(@NonNull DeviceStateInfo info) { - try { - mCallback.onDeviceStateInfoChanged(info); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to notify process " + mPid + " that device state changed.", - ex); - } + mHandler.post(() -> { + try { + mCallback.onDeviceStateInfoChanged(info); + } catch (RemoteException 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 notifyRequestActiveAsync(IBinder token) { + @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token); + if (lastStatus != null + && (lastStatus == STATUS_ACTIVE || lastStatus == STATUS_CANCELED)) { + return; } - } - 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); - } + 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); + } + }); } - 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); + public void notifyRequestSuspendedAsync(IBinder token) { + @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token); + if (lastStatus != null + && (lastStatus == STATUS_SUSPENDED || lastStatus == STATUS_CANCELED)) { + return; } - } - } - - /** 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); + mLastNotifiedStatus.put(token, STATUS_SUSPENDED); + mHandler.post(() -> { + try { + mCallback.onRequestSuspended(token); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.", + ex); } - } + }); } - public void notifyStatusIfNeeded() { - int stateToReport; - synchronized (mLock) { - if (mLastNotifiedStatus == mStatus) { - return; - } - - stateToReport = mStatus; - mLastNotifiedStatus = mStatus; + public void notifyRequestCanceledAsync(IBinder token) { + @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token); + if (lastStatus != null && lastStatus == STATUS_CANCELED) { + return; } - if (stateToReport == STATUS_ACTIVE) { - mProcessRecord.notifyRequestActiveAsync(this); - } else if (stateToReport == STATUS_SUSPENDED) { - mProcessRecord.notifyRequestSuspendedAsync(this); - } else if (stateToReport == STATUS_CANCELED) { - mProcessRecord.notifyRequestCanceledAsync(this); - } + 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 public void requestState(IBinder token, int state, int flags) { - getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, - "Permission required to request device state."); + final int callingPid = Binder.getCallingPid(); + // 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) { throw new IllegalArgumentException("Request token must not be null."); } - final int callingPid = Binder.getCallingPid(); final long callingIdentity = Binder.clearCallingIdentity(); try { requestStateInternal(state, flags, callingPid, token); @@ -866,14 +810,21 @@ public final class DeviceStateManagerService extends SystemService { @Override // Binder call public void cancelRequest(IBinder token) { - getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, - "Permission required to clear requested device state."); + final int callingPid = Binder.getCallingPid(); + // 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) { throw new IllegalArgumentException("Request token must not be null."); } - final int callingPid = Binder.getCallingPid(); final long callingIdentity = Binder.clearCallingIdentity(); try { cancelRequestInternal(callingPid, token); diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java new file mode 100644 index 000000000000..35a4c844c710 --- /dev/null +++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java @@ -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; + } +} diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java new file mode 100644 index 000000000000..05c9eb2c5bbe --- /dev/null +++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java @@ -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); + } +} diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java index edd5f5f415c6..ff6511f06577 100644 --- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java +++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java @@ -41,6 +41,7 @@ import com.android.server.devicestate.DeviceState; import com.android.server.devicestate.DeviceStateProvider; import com.android.server.policy.devicestate.config.Conditions; 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.NumericRange; import com.android.server.policy.devicestate.config.SensorCondition; @@ -87,7 +88,7 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, @VisibleForTesting 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 DATA_CONFIG_FILE_PATH = "system/devicestate/"; @@ -131,7 +132,26 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, config.getDeviceState()) { final int state = stateConfig.getIdentifier().intValue(); 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(); conditionsList.add(condition); diff --git a/services/core/xsd/device-state-config/device-state-config.xsd b/services/core/xsd/device-state-config/device-state-config.xsd index 94a398f2cdb7..86f41769008d 100644 --- a/services/core/xsd/device-state-config/device-state-config.xsd +++ b/services/core/xsd/device-state-config/device-state-config.xsd @@ -40,10 +40,19 @@ <xs:element name="name" type="xs:string" minOccurs="0"> <xs:annotation name="nullable" /> </xs:element> + <xs:element name="flags" type="flags" /> <xs:element name="conditions" type="conditions" /> </xs:sequence> </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:sequence> <xs:element name="lid-switch" type="lidSwitchCondition" minOccurs="0"> diff --git a/services/core/xsd/device-state-config/schema/current.txt b/services/core/xsd/device-state-config/schema/current.txt index 08fccf8ad949..a98d4e569cd6 100644 --- a/services/core/xsd/device-state-config/schema/current.txt +++ b/services/core/xsd/device-state-config/schema/current.txt @@ -11,9 +11,11 @@ package com.android.server.policy.devicestate.config { public class DeviceState { ctor public DeviceState(); 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 @Nullable public String getName(); 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 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(); } + public class Flags { + ctor public Flags(); + method @Nullable public java.util.List<java.lang.String> getFlag(); + } + public class LidSwitchCondition { ctor public LidSwitchCondition(); method public boolean getOpen(); diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java index ff8fbda6c83e..b1b6e5341f38 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java @@ -18,6 +18,7 @@ package com.android.server.devicestate; 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.assertFalse; import static org.testng.Assert.assertNotNull; @@ -35,6 +36,11 @@ import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; 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 org.junit.Before; @@ -55,10 +61,15 @@ import javax.annotation.Nullable; @Presubmit @RunWith(AndroidJUnit4.class) public final class DeviceStateManagerServiceTest { - private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT"); - private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(1, "OTHER"); + private static final DeviceState DEFAULT_DEVICE_STATE = + 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. - 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 TestDeviceStateProvider mProvider; @@ -69,6 +80,25 @@ public final class DeviceStateManagerServiceTest { mProvider = new TestDeviceStateProvider(); mPolicy = new TestDeviceStatePolicy(mProvider); 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 @@ -80,6 +110,7 @@ public final class DeviceStateManagerServiceTest { DEFAULT_DEVICE_STATE.getIdentifier()); mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); + flushHandler(); assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.empty()); assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE)); @@ -92,6 +123,7 @@ public final class DeviceStateManagerServiceTest { mPolicy.blockConfigure(); mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); + flushHandler(); assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getPendingState(), 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()); mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier()); + flushHandler(); assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE)); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); @@ -106,6 +139,7 @@ public final class DeviceStateManagerServiceTest { OTHER_DEVICE_STATE.getIdentifier()); mPolicy.resumeConfigure(); + flushHandler(); assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.empty()); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); @@ -149,6 +183,7 @@ public final class DeviceStateManagerServiceTest { assertEquals(mService.getBaseState(), Optional.of(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 // supported. @@ -166,6 +201,7 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().registerCallback(callback); // An initial callback will be triggered on registration, so we clear it here. + flushHandler(); callback.clearLastNotifiedInfo(); assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); @@ -174,6 +210,7 @@ public final class DeviceStateManagerServiceTest { mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE }); + flushHandler(); // The current committed and requests states do not change because the current state remains // supported. @@ -203,12 +240,14 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().registerCallback(callback); mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); + flushHandler(); assertEquals(callback.getLastNotifiedInfo().baseState, OTHER_DEVICE_STATE.getIdentifier()); assertEquals(callback.getLastNotifiedInfo().currentState, OTHER_DEVICE_STATE.getIdentifier()); mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier()); + flushHandler(); assertEquals(callback.getLastNotifiedInfo().baseState, DEFAULT_DEVICE_STATE.getIdentifier()); assertEquals(callback.getLastNotifiedInfo().currentState, @@ -216,6 +255,7 @@ public final class DeviceStateManagerServiceTest { mPolicy.blockConfigure(); mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); + flushHandler(); // The callback should not have been notified of the state change as the policy is still // pending callback. assertEquals(callback.getLastNotifiedInfo().baseState, @@ -224,6 +264,7 @@ public final class DeviceStateManagerServiceTest { DEFAULT_DEVICE_STATE.getIdentifier()); mPolicy.resumeConfigure(); + flushHandler(); // Now that the policy is finished processing the callback should be notified of the state // change. assertEquals(callback.getLastNotifiedInfo().baseState, @@ -236,6 +277,7 @@ public final class DeviceStateManagerServiceTest { public void registerCallback_emitsInitialValue() throws RemoteException { TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); mService.getBinderService().registerCallback(callback); + flushHandler(); assertNotNull(callback.getLastNotifiedInfo()); assertEquals(callback.getLastNotifiedInfo().baseState, DEFAULT_DEVICE_STATE.getIdentifier()); @@ -247,6 +289,7 @@ public final class DeviceStateManagerServiceTest { public void requestState() throws RemoteException { TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); mService.getBinderService().registerCallback(callback); + flushHandler(); final IBinder token = new Binder(); assertEquals(callback.getLastNotifiedStatus(token), @@ -254,6 +297,10 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().requestState(token, 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(callback.getLastNotifiedStatus(token), TestDeviceStateManagerCallback.STATUS_ACTIVE); @@ -271,6 +318,7 @@ public final class DeviceStateManagerServiceTest { OTHER_DEVICE_STATE.getIdentifier()); mService.getBinderService().cancelRequest(token); + flushHandler(); assertEquals(callback.getLastNotifiedStatus(token), TestDeviceStateManagerCallback.STATUS_CANCELED); @@ -291,6 +339,7 @@ public final class DeviceStateManagerServiceTest { public void requestState_pendingStateAtRequest() throws RemoteException { TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); mService.getBinderService().registerCallback(callback); + flushHandler(); mPolicy.blockConfigure(); @@ -303,6 +352,10 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().requestState(firstRequestToken, 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.getPendingState(), Optional.of(OTHER_DEVICE_STATE)); @@ -312,8 +365,8 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().requestState(secondRequestToken, DEFAULT_DEVICE_STATE.getIdentifier(), 0 /* flags */); - mPolicy.resumeConfigureOnce(); + flushHandler(); // First request status is now suspended as there is another pending request. assertEquals(callback.getLastNotifiedStatus(firstRequestToken), @@ -330,6 +383,7 @@ public final class DeviceStateManagerServiceTest { DEFAULT_DEVICE_STATE.getIdentifier()); mPolicy.resumeConfigure(); + flushHandler(); assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.empty()); @@ -339,6 +393,7 @@ public final class DeviceStateManagerServiceTest { // Now cancel the second request to make the first request active. mService.getBinderService().cancelRequest(secondRequestToken); + flushHandler(); assertEquals(callback.getLastNotifiedStatus(firstRequestToken), TestDeviceStateManagerCallback.STATUS_ACTIVE); @@ -356,6 +411,7 @@ public final class DeviceStateManagerServiceTest { public void requestState_sameAsBaseState() throws RemoteException { TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); mService.getBinderService().registerCallback(callback); + flushHandler(); final IBinder token = new Binder(); assertEquals(callback.getLastNotifiedStatus(token), @@ -363,6 +419,7 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().requestState(token, DEFAULT_DEVICE_STATE.getIdentifier(), 0 /* flags */); + flushHandler(); assertEquals(callback.getLastNotifiedStatus(token), TestDeviceStateManagerCallback.STATUS_ACTIVE); @@ -372,6 +429,7 @@ public final class DeviceStateManagerServiceTest { public void requestState_flagCancelWhenBaseChanges() throws RemoteException { TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); mService.getBinderService().registerCallback(callback); + flushHandler(); final IBinder token = new Binder(); assertEquals(callback.getLastNotifiedStatus(token), @@ -379,6 +437,10 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(), 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), TestDeviceStateManagerCallback.STATUS_ACTIVE); @@ -391,6 +453,7 @@ public final class DeviceStateManagerServiceTest { OTHER_DEVICE_STATE.getIdentifier()); mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); + flushHandler(); // Request is canceled because the base state changed. assertEquals(callback.getLastNotifiedStatus(token), @@ -407,6 +470,7 @@ public final class DeviceStateManagerServiceTest { public void requestState_becomesUnsupported() throws RemoteException { TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); mService.getBinderService().registerCallback(callback); + flushHandler(); final IBinder token = new Binder(); assertEquals(callback.getLastNotifiedStatus(token), @@ -414,6 +478,7 @@ public final class DeviceStateManagerServiceTest { mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(), 0 /* flags */); + flushHandler(); assertEquals(callback.getLastNotifiedStatus(token), TestDeviceStateManagerCallback.STATUS_ACTIVE); @@ -425,6 +490,7 @@ public final class DeviceStateManagerServiceTest { OTHER_DEVICE_STATE.getIdentifier()); mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE }); + flushHandler(); // Request is canceled because the state is no longer supported. assertEquals(callback.getLastNotifiedStatus(token), diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java index b5c8053ad77e..e286cb27cc41 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java @@ -41,24 +41,26 @@ public final class DeviceStateTest { @Test public void testConstruct() { 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.getName(), "CLOSED"); + assertEquals(state.getFlags(), DeviceState.FLAG_CANCEL_STICKY_REQUESTS); } @Test public void testConstruct_nullName() { final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE /* identifier */, - null /* name */); + null /* name */, 0/* flags */); assertEquals(state.getIdentifier(), MAXIMUM_DEVICE_STATE); assertNull(state.getName()); + assertEquals(state.getFlags(), 0); } @Test public void testConstruct_tooLargeIdentifier() { assertThrows(IllegalArgumentException.class, () -> { 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() { assertThrows(IllegalArgumentException.class, () -> { final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE - 1 /* identifier */, - null /* name */); + null /* name */, 0 /* flags */); }); } } diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java new file mode 100644 index 000000000000..c9cf2f06640d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java @@ -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); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java index 4d2d2f1a4b7d..8e2c1f051279 100644 --- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java @@ -150,8 +150,9 @@ public final class DeviceStateProviderImplTest { provider.setListener(listener); verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); - final DeviceState[] expectedStates = new DeviceState[]{ new DeviceState(1, ""), - new DeviceState(2, "") }; + final DeviceState[] expectedStates = new DeviceState[]{ + new DeviceState(1, "", 0 /* flags */), + new DeviceState(2, "", 0 /* flags */) }; assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue()); verify(listener).onStateChanged(mIntegerCaptor.capture()); @@ -187,8 +188,9 @@ public final class DeviceStateProviderImplTest { provider.setListener(listener); verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); - final DeviceState[] expectedStates = new DeviceState[]{ new DeviceState(1, ""), - new DeviceState(2, "CLOSED") }; + final DeviceState[] expectedStates = new DeviceState[]{ + new DeviceState(1, "", 0 /* flags */), + new DeviceState(2, "CLOSED", 0 /* flags */) }; assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue()); // 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()); assertArrayEquals( - new DeviceState[]{ new DeviceState(1, "CLOSED"), new DeviceState(2, "HALF_OPENED"), - new DeviceState(3, "OPENED") }, mDeviceStateArrayCaptor.getValue()); + new DeviceState[]{ + 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 // the initial sensor state. verify(listener, never()).onStateChanged(mIntegerCaptor.capture()); @@ -350,8 +355,10 @@ public final class DeviceStateProviderImplTest { verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); assertArrayEquals( - new DeviceState[]{ new DeviceState(1, "CLOSED"), new DeviceState(2, "HALF_OPENED"), - }, mDeviceStateArrayCaptor.getValue()); + new DeviceState[]{ + 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. verify(listener).onStateChanged(mIntegerCaptor.capture()); assertEquals(1, mIntegerCaptor.getValue().intValue()); |
