Refactor request lifecycle logic into OverrideRequestController.

This refactors the request lifecycle login into a controller,
OverrideRequestController, which simplies the login within
DeviceStateManagerService itself.

Bug: 192671286
Test: atest DeviceStateManagerServiceTest
Test: atest OverrideRequestControllerTest
Test: atest DeviceStateManagerTests
Change-Id: Ifaf04665bf8c84d62fa08b3b8883559c6726d6af
This commit is contained in:
Darryl L Johnson
2021-07-02 11:42:09 -07:00
committed by Kenneth Ford
parent 8e97f22068
commit a5ab448ff0
5 changed files with 812 additions and 295 deletions

View File

@@ -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,18 @@ 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.ServiceThread;
import com.android.server.SystemService;
import com.android.server.policy.DeviceStatePolicyImpl;
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 +90,17 @@ 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;
// All supported device states keyed by identifier.
@GuardedBy("mLock")
@@ -109,17 +125,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,6 +143,13 @@ 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();
@@ -138,6 +160,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 +218,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,8 +272,6 @@ public final class DeviceStateManagerService extends SystemService {
}
private void updateSupportedStates(DeviceState[] supportedDeviceStates) {
boolean updatedPendingState;
boolean hasBaseState;
synchronized (mLock) {
final int[] oldStateIdentifiers = getSupportedStateIdentifiersLocked();
@@ -263,27 +286,18 @@ public final class DeviceStateManagerService extends SystemService {
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();
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();
}
updatedPendingState = updatePendingStateLocked();
hasBaseState = mBaseState.isPresent();
mHandler.post(this::notifyPolicyIfNeeded);
}
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 +325,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 +338,18 @@ 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);
}
mOverrideRequestController.handleBaseStateChanged();
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 +367,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 +434,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.
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() {
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;
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.");
}
ArraySet<OverrideRequestRecord> dirtyRequests;
synchronized (mLock) {
if (mRequestsPendingStatusChange.isEmpty()) {
return;
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();
}
dirtyRequests = new ArraySet<>(mRequestsPendingStatusChange);
mRequestsPendingStatusChange.clear();
} else {
throw new IllegalArgumentException("Unknown request status: " + status);
}
// After releasing the lock, send the notifications out.
for (int i = 0; i < dirtyRequests.size(); i++) {
dirtyRequests.valueAt(i).notifyStatusIfNeeded();
boolean updatedPendingState = updatePendingStateLocked();
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) {
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 +541,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 +567,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 +578,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 +591,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 +612,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 +636,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);
}
}
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.");
mHandler.post(() -> {
try {
mCallback.onDeviceStateInfoChanged(info);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify process " + mPid + " that device state changed.",
ex);
}
mStatus = status;
if (mStatus == STATUS_CANCELED) {
mRequestRecords.remove(this);
mProcessRecord.mRequestRecords.remove(mToken);
}
if (markDirty) {
mRequestsPendingStatusChange.add(this);
}
}
});
}
public void notifyStatusIfNeeded() {
int stateToReport;
synchronized (mLock) {
if (mLastNotifiedStatus == mStatus) {
return;
public void notifyRequestActiveAsync(IBinder token) {
@RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token);
if (lastStatus != null
&& (lastStatus == STATUS_ACTIVE || lastStatus == STATUS_CANCELED)) {
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;
mLastNotifiedStatus = mStatus;
public void notifyRequestSuspendedAsync(IBinder token) {
@RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token);
if (lastStatus != null
&& (lastStatus == STATUS_SUSPENDED || 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_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 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);
}
});
}
}

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,290 @@
/*
* 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<>();
OverrideRequestController(@NonNull StatusChangeListener listener) {
mListener = listener;
}
/**
* 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);
}
/**
* 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;
}
OverrideRequest prevActiveRequest = getLast(mRequests);
for (OverrideRequest request : mRequests) {
if (request.getPid() == pid) {
mTmpRequestsToCancel.add(request);
}
}
mRequests.removeAll(mTmpRequestsToCancel);
if (!mRequests.isEmpty()) {
OverrideRequest newActiveRequest = getLast(mRequests);
if (newActiveRequest != prevActiveRequest) {
mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE);
}
}
for (int i = 0; i < mTmpRequestsToCancel.size(); i++) {
mListener.onStatusChanged(mTmpRequestsToCancel.get(i), STATUS_CANCELED);
}
mTmpRequestsToCancel.clear();
}
/**
* 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;
}
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);
}
}
mRequests.removeAll(mTmpRequestsToCancel);
OverrideRequest newActiveRequest = null;
if (!mRequests.isEmpty()) {
newActiveRequest = getLast(mRequests);
if (newActiveRequest != prevActiveRequest) {
mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE);
}
}
for (int i = 0; i < mTmpRequestsToCancel.size(); i++) {
mListener.onStatusChanged(mTmpRequestsToCancel.get(i), STATUS_CANCELED);
}
mTmpRequestsToCancel.clear();
return newActiveRequest != prevActiveRequest;
}
/**
* 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;
}
OverrideRequest prevActiveRequest = getLast(mRequests);
for (int i = 0; i < mRequests.size(); i++) {
OverrideRequest request = mRequests.get(i);
if (!contains(newSupportedStates, request.getRequestedState())) {
mTmpRequestsToCancel.add(request);
}
}
mRequests.removeAll(mTmpRequestsToCancel);
OverrideRequest newActiveRequest = null;
if (!mRequests.isEmpty()) {
newActiveRequest = getLast(mRequests);
if (newActiveRequest != prevActiveRequest) {
mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE);
}
}
for (int i = 0; i < mTmpRequestsToCancel.size(); i++) {
mListener.onStatusChanged(mTmpRequestsToCancel.get(i), STATUS_CANCELED);
}
mTmpRequestsToCancel.clear();
return newActiveRequest != prevActiveRequest;
}
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));
}
}
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

@@ -69,6 +69,17 @@ public final class DeviceStateManagerServiceTest {
mProvider = new TestDeviceStateProvider();
mPolicy = new TestDeviceStatePolicy(mProvider);
mService = new DeviceStateManagerService(InstrumentationRegistry.getContext(), mPolicy);
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 +91,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 +104,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 +112,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 +120,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 +164,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 +182,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 +191,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 +221,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 +236,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 +245,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 +258,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 +270,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 +278,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 +299,7 @@ public final class DeviceStateManagerServiceTest {
OTHER_DEVICE_STATE.getIdentifier());
mService.getBinderService().cancelRequest(token);
flushHandler();
assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_CANCELED);
@@ -291,6 +320,7 @@ public final class DeviceStateManagerServiceTest {
public void requestState_pendingStateAtRequest() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
flushHandler();
mPolicy.blockConfigure();
@@ -303,6 +333,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 +346,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 +364,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 +374,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 +392,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 +400,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 +410,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 +418,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 +434,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 +451,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 +459,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 +471,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),

View File

@@ -0,0 +1,204 @@
/*
* 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 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);
}
}
}