blob: 17dccfa7ee4c092bd3f7345392a985aefa92f499 [file] [log] [blame]
/*
* Copyright (C) 2020 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 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 com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.devicestate.DeviceStateInfo;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManagerInternal;
import android.hardware.devicestate.IDeviceStateManager;
import android.hardware.devicestate.IDeviceStateManagerCallback;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.SystemProperties;
import android.os.Trace;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.R;
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.DisplayThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
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.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.WeakHashMap;
/**
* A system service that manages the state of a device with user-configurable hardware like a
* foldable phone.
* <p>
* Device state is an abstract concept that allows mapping the current state of the device to the
* state of the system. For example, system services (like
* {@link com.android.server.display.DisplayManagerService display manager} and
* {@link com.android.server.wm.WindowManagerService window manager}) and system UI may have
* different behaviors depending on the physical state of the device. This is useful for
* variable-state devices, like foldable or rollable devices, that can be configured by users into
* differing hardware states, which each may have a different expected use case.
* </p>
* <p>
* The {@link DeviceStateManagerService} is responsible for receiving state change requests from
* the {@link DeviceStateProvider} to modify the current device state and communicating with the
* {@link DeviceStatePolicy policy} to ensure the system is configured to match the requested state.
* </p>
* The service also provides the {@link DeviceStateManager} API allowing clients to listen for
* changes in device state and submit requests to override the device state provided by the
* {@link DeviceStateProvider}.
*
* @see DeviceStatePolicy
* @see DeviceStateManager
*/
public final class DeviceStateManagerService extends SystemService {
private static final String TAG = "DeviceStateManagerService";
private static final boolean DEBUG = false;
private final Object mLock = new Object();
// Handler on the {@link DisplayThread} 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 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")
private SparseArray<DeviceState> mDeviceStates = new SparseArray<>();
// The current committed device state. Will be empty until the first device state provided by
// the DeviceStateProvider is committed.
@GuardedBy("mLock")
@NonNull
private Optional<DeviceState> mCommittedState = Optional.empty();
// The device state that is currently awaiting callback from the policy to be committed.
@GuardedBy("mLock")
@NonNull
private Optional<DeviceState> mPendingState = Optional.empty();
// Whether or not the policy is currently waiting to be notified of the current pending state.
@GuardedBy("mLock")
private boolean mIsPolicyWaitingForState = false;
// The device state that is set by the DeviceStateProvider. Will be empty until the first
// callback from the provider and then will always contain the most recent value.
@GuardedBy("mLock")
@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<>();
private Set<Integer> mDeviceStatesAvailableForAppRequests;
@VisibleForTesting
interface SystemPropertySetter {
void setDebugTracingDeviceStateProperty(String value);
}
@NonNull
private final SystemPropertySetter mSystemPropertySetter;
public DeviceStateManagerService(@NonNull Context context) {
this(context, DeviceStatePolicy.Provider
.fromResources(context.getResources())
.instantiate(context));
}
private DeviceStateManagerService(@NonNull Context context, @NonNull DeviceStatePolicy policy) {
this(context, policy, (value) -> {
SystemProperties.set("debug.tracing.device_state", value);
});
}
@VisibleForTesting
DeviceStateManagerService(@NonNull Context context, @NonNull DeviceStatePolicy policy,
@NonNull SystemPropertySetter systemPropertySetter) {
super(context);
mSystemPropertySetter = systemPropertySetter;
// We use the DisplayThread because this service indirectly drives
// display (on/off) and window (position) events through its callbacks.
DisplayThread displayThread = DisplayThread.get();
mHandler = new Handler(displayThread.getLooper());
mOverrideRequestController = new OverrideRequestController(
this::onOverrideRequestStatusChangedLocked);
mDeviceStatePolicy = policy;
mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener());
mBinderService = new BinderService();
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
}
@Override
public void onStart() {
publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService);
publishLocalService(DeviceStateManagerInternal.class, new LocalService());
synchronized (mLock) {
readStatesAvailableForRequestFromApps();
}
}
@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.
* <p>
* Note: This method will return {@link Optional#empty()} if called before the first state has
* been committed, otherwise it will return the last committed state.
*
* @see #getPendingState()
*/
@NonNull
Optional<DeviceState> getCommittedState() {
synchronized (mLock) {
return mCommittedState;
}
}
/**
* Returns the state the system is currently configuring, or {@link Optional#empty()} if the
* system is not in the process of configuring a state.
*/
@VisibleForTesting
@NonNull
Optional<DeviceState> getPendingState() {
synchronized (mLock) {
return mPendingState;
}
}
/**
* Returns the base state. The service will configure the device to match the base state when
* there is no active request to override the base state.
* <p>
* Note: This method will return {@link Optional#empty()} if called before a base state is
* provided to the service by the {@link DeviceStateProvider}, otherwise it will return the
* most recent provided value.
*
* @see #getOverrideState()
*/
@NonNull
Optional<DeviceState> getBaseState() {
synchronized (mLock) {
return mBaseState;
}
}
/**
* Returns the current override state, or {@link Optional#empty()} if no override state is
* requested. If an override states is present, the returned state will take precedence over
* the base state returned from {@link #getBaseState()}.
*/
@NonNull
Optional<DeviceState> getOverrideState() {
synchronized (mLock) {
if (mActiveOverride.isPresent()) {
return getStateLocked(mActiveOverride.get().getRequestedState());
}
return Optional.empty();
}
}
/** Returns the list of currently supported device states. */
DeviceState[] getSupportedStates() {
synchronized (mLock) {
DeviceState[] supportedStates = new DeviceState[mDeviceStates.size()];
for (int i = 0; i < supportedStates.length; i++) {
supportedStates[i] = mDeviceStates.valueAt(i);
}
return supportedStates;
}
}
/** Returns the list of currently supported device state identifiers. */
private int[] getSupportedStateIdentifiersLocked() {
int[] supportedStates = new int[mDeviceStates.size()];
for (int i = 0; i < supportedStates.length; i++) {
supportedStates[i] = mDeviceStates.valueAt(i).getIdentifier();
}
return supportedStates;
}
@NonNull
private DeviceStateInfo getDeviceStateInfoLocked() {
if (!mBaseState.isPresent() || !mCommittedState.isPresent()) {
throw new IllegalStateException("Trying to get the current DeviceStateInfo before the"
+ " initial state has been committed.");
}
final int[] supportedStates = getSupportedStateIdentifiersLocked();
final int baseState = mBaseState.get().getIdentifier();
final int currentState = mCommittedState.get().getIdentifier();
return new DeviceStateInfo(supportedStates, baseState, currentState);
}
@VisibleForTesting
IDeviceStateManager getBinderService() {
return mBinderService;
}
private void updateSupportedStates(DeviceState[] supportedDeviceStates) {
synchronized (mLock) {
final int[] oldStateIdentifiers = getSupportedStateIdentifiersLocked();
// Whether or not at least one device state has the flag FLAG_CANCEL_OVERRIDE_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.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
hasTerminalDeviceState = true;
}
mDeviceStates.put(state.getIdentifier(), state);
}
mOverrideRequestController.setStickyRequestsAllowed(hasTerminalDeviceState);
final int[] newStateIdentifiers = getSupportedStateIdentifiersLocked();
if (Arrays.equals(oldStateIdentifiers, newStateIdentifiers)) {
return;
}
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();
}
mHandler.post(this::notifyPolicyIfNeeded);
}
}
/**
* Returns {@code true} if the provided state is supported. Requires that
* {@link #mDeviceStates} is sorted prior to calling.
*/
private boolean isSupportedStateLocked(int identifier) {
return mDeviceStates.contains(identifier);
}
/**
* Returns the {@link DeviceState} with the supplied {@code identifier}, or {@code null} if
* there is no device state with the identifier.
*/
@Nullable
private Optional<DeviceState> getStateLocked(int identifier) {
return Optional.ofNullable(mDeviceStates.get(identifier));
}
/**
* Sets the base state.
*
* @throws IllegalArgumentException if the {@code identifier} is not a supported state.
*
* @see #isSupportedStateLocked(int)
*/
private void setBaseState(int identifier) {
synchronized (mLock) {
final Optional<DeviceState> baseStateOptional = getStateLocked(identifier);
if (!baseStateOptional.isPresent()) {
throw new IllegalArgumentException("Base state is not supported");
}
final DeviceState baseState = baseStateOptional.get();
if (mBaseState.isPresent() && mBaseState.get().equals(baseState)) {
// Base state hasn't changed. Nothing to do.
return;
}
mBaseState = Optional.of(baseState);
if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
mOverrideRequestController.cancelOverrideRequest();
}
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();
}
mHandler.post(this::notifyPolicyIfNeeded);
}
}
/**
* Tries to update the current pending state with the current requested state. Must call
* {@link #notifyPolicyIfNeeded()} to actually notify the policy that the state is being
* changed.
*
* @return {@code true} if the pending state has changed as a result of this call, {@code false}
* otherwise.
*/
private boolean updatePendingStateLocked() {
if (mPendingState.isPresent()) {
// Have pending state, can not configure a new state until the state is committed.
return false;
}
final DeviceState stateToConfigure;
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.
stateToConfigure = mBaseState.get();
} else {
stateToConfigure = null;
}
if (stateToConfigure == null) {
// No currently requested state.
return false;
}
if (mCommittedState.isPresent() && stateToConfigure.equals(mCommittedState.get())) {
// The state requesting to be committed already matches the current committed state.
return false;
}
mPendingState = Optional.of(stateToConfigure);
mIsPolicyWaitingForState = true;
return true;
}
/**
* Notifies the policy to configure the supplied state. Should not be called with {@link #mLock}
* held.
*/
private void notifyPolicyIfNeeded() {
if (Thread.holdsLock(mLock)) {
Throwable error = new Throwable("Attempting to notify DeviceStatePolicy with service"
+ " lock held");
error.fillInStackTrace();
Slog.w(TAG, error);
}
int state;
synchronized (mLock) {
if (!mIsPolicyWaitingForState) {
return;
}
mIsPolicyWaitingForState = false;
state = mPendingState.get().getIdentifier();
}
if (DEBUG) {
Slog.d(TAG, "Notifying policy to configure state: " + state);
}
mDeviceStatePolicy.configureDeviceForState(state, this::commitPendingState);
}
/**
* Commits the current pending state after a callback from the {@link DeviceStatePolicy}.
*
* <pre>
* ------------- ----------- -------------
* Provider -> | Requested | -> | Pending | -> Policy -> | Committed |
* ------------- ----------- -------------
* </pre>
* <p>
* When a new state is requested it immediately enters the requested state. Once the policy is
* available to accept a new state, which could also be immediately if there is no current
* pending state at the point of request, the policy is notified and a callback is provided to
* trigger the state to be committed.
* </p>
*/
private void commitPendingState() {
synchronized (mLock) {
final DeviceState newState = mPendingState.get();
if (DEBUG) {
Slog.d(TAG, "Committing state: " + newState);
}
FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_STATE_CHANGED,
newState.getIdentifier(), !mCommittedState.isPresent());
String traceString = newState.getIdentifier() + ":" + newState.getName();
Trace.instantForTrack(
Trace.TRACE_TAG_SYSTEM_SERVER, "DeviceStateChanged", traceString);
mSystemPropertySetter.setDebugTracingDeviceStateProperty(traceString);
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);
}
}
private void notifyDeviceStateInfoChangedAsync() {
synchronized (mLock) {
if (mProcessRecords.size() == 0) {
return;
}
ArrayList<ProcessRecord> registeredProcesses = new ArrayList<>();
for (int i = 0; i < mProcessRecords.size(); i++) {
registeredProcesses.add(mProcessRecords.valueAt(i));
}
DeviceStateInfo info = getDeviceStateInfoLocked();
for (int i = 0; i < registeredProcesses.size(); i++) {
registeredProcesses.get(i).notifyDeviceStateInfoAsync(info);
}
}
}
private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request,
@OverrideRequestController.RequestStatus int status) {
if (status == STATUS_ACTIVE) {
mActiveOverride = Optional.of(request);
} else if (status == STATUS_CANCELED) {
if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
mActiveOverride = Optional.empty();
}
} else {
throw new IllegalArgumentException("Unknown request status: " + status);
}
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 {
processRecord.notifyRequestCanceledAsync(request.getToken());
}
mHandler.post(this::notifyPolicyIfNeeded);
}
private void registerProcess(int pid, IDeviceStateManagerCallback callback) {
synchronized (mLock) {
if (mProcessRecords.contains(pid)) {
throw new SecurityException("The calling process has already registered an"
+ " IDeviceStateManagerCallback.");
}
ProcessRecord record = new ProcessRecord(callback, pid, this::handleProcessDied,
mHandler);
try {
callback.asBinder().linkToDeath(record, 0);
} catch (RemoteException ex) {
throw new RuntimeException(ex);
}
mProcessRecords.put(pid, record);
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) {
mProcessRecords.remove(processRecord.mPid);
mOverrideRequestController.handleProcessDied(processRecord.mPid);
}
}
private void requestStateInternal(int state, int flags, int callingPid,
@NonNull IBinder token) {
synchronized (mLock) {
final ProcessRecord processRecord = mProcessRecords.get(callingPid);
if (processRecord == null) {
throw new IllegalStateException("Process " + callingPid
+ " has no registered callback.");
}
if (mOverrideRequestController.hasRequest(token)) {
throw new IllegalStateException("Request has already been made for the supplied"
+ " token: " + token);
}
final Optional<DeviceState> deviceState = getStateLocked(state);
if (!deviceState.isPresent()) {
throw new IllegalArgumentException("Requested state: " + state
+ " is not supported.");
}
OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
mOverrideRequestController.addRequest(request);
}
}
private void cancelStateRequestInternal(int callingPid) {
synchronized (mLock) {
final ProcessRecord processRecord = mProcessRecords.get(callingPid);
if (processRecord == null) {
throw new IllegalStateException("Process " + callingPid
+ " has no registered callback.");
}
mActiveOverride.ifPresent(mOverrideRequestController::cancelRequest);
}
}
private void dumpInternal(PrintWriter pw) {
pw.println("DEVICE STATE MANAGER (dumpsys device_state)");
synchronized (mLock) {
pw.println(" mCommittedState=" + mCommittedState);
pw.println(" mPendingState=" + mPendingState);
pw.println(" mBaseState=" + mBaseState);
pw.println(" mOverrideState=" + getOverrideState());
final int processCount = mProcessRecords.size();
pw.println();
pw.println("Registered processes: size=" + processCount);
for (int i = 0; i < processCount; i++) {
ProcessRecord processRecord = mProcessRecords.valueAt(i);
pw.println(" " + i + ": mPid=" + processRecord.mPid);
}
mOverrideRequestController.dumpInternal(pw);
}
}
/**
* Allow top processes to request or cancel a device state change. If the calling process ID is
* not the top app, then check if this process holds the
* {@link android.Manifest.permission.CONTROL_DEVICE_STATE} permission. If the calling process
* is the top app, check to verify they are requesting a state we've deemed to be able to be
* available for an app process to request. States that can be requested are based around
* features that we've created that require specific device state overrides.
* @param callingPid Process ID that is requesting this state change
* @param state state that is being requested.
*/
private void assertCanRequestDeviceState(int callingPid, int state) {
final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
if (topApp == null || topApp.getPid() != callingPid
|| !isStateAvailableForAppRequests(state)) {
getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
"Permission required to request device state, "
+ "or the call must come from the top app "
+ "and be a device state that is available for apps to request.");
}
}
/**
* Checks if the process can control the device state. If the calling process ID is
* not the top app, then check if this process holds the CONTROL_DEVICE_STATE permission.
*
* @param callingPid Process ID that is requesting this state change
*/
private void assertCanControlDeviceState(int callingPid) {
final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
if (topApp == null || topApp.getPid() != callingPid) {
getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
"Permission required to request device state, "
+ "or the call must come from the top app.");
}
}
private boolean isStateAvailableForAppRequests(int state) {
synchronized (mLock) {
return mDeviceStatesAvailableForAppRequests.contains(state);
}
}
/**
* Adds device state values that are available to be requested by the top level app.
*/
@GuardedBy("mLock")
private void readStatesAvailableForRequestFromApps() {
mDeviceStatesAvailableForAppRequests = new HashSet<>();
String[] availableAppStatesConfigIdentifiers = getContext().getResources()
.getStringArray(R.array.config_deviceStatesAvailableForAppRequests);
for (int i = 0; i < availableAppStatesConfigIdentifiers.length; i++) {
String identifierToFetch = availableAppStatesConfigIdentifiers[i];
int configValueIdentifier = getContext().getResources()
.getIdentifier(identifierToFetch, "integer", "android");
int state = getContext().getResources().getInteger(configValueIdentifier);
if (isValidState(state)) {
mDeviceStatesAvailableForAppRequests.add(state);
} else {
Slog.e(TAG, "Invalid device state was found in the configuration file. State id: "
+ state);
}
}
}
@GuardedBy("mLock")
private boolean isValidState(int state) {
for (int i = 0; i < mDeviceStates.size(); i++) {
if (state == mDeviceStates.valueAt(i).getIdentifier()) {
return true;
}
}
return false;
}
private final class DeviceStateProviderListener implements DeviceStateProvider.Listener {
@Override
public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) {
if (newDeviceStates.length == 0) {
throw new IllegalArgumentException("Supported device states must not be empty");
}
updateSupportedStates(newDeviceStates);
}
@Override
public void onStateChanged(
@IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier) {
if (identifier < MINIMUM_DEVICE_STATE || identifier > MAXIMUM_DEVICE_STATE) {
throw new IllegalArgumentException("Invalid identifier: " + identifier);
}
setBaseState(identifier);
}
}
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 WeakHashMap<IBinder, Integer> mLastNotifiedStatus = new WeakHashMap<>();
ProcessRecord(IDeviceStateManagerCallback callback, int pid, DeathListener deathListener,
Handler handler) {
mCallback = callback;
mPid = pid;
mDeathListener = deathListener;
mHandler = handler;
}
@Override
public void binderDied() {
mDeathListener.onProcessDied(this);
}
public void notifyDeviceStateInfoAsync(@NonNull DeviceStateInfo info) {
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(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);
}
});
}
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);
}
});
}
}
/** Implementation of {@link IDeviceStateManager} published as a binder service. */
private final class BinderService extends IDeviceStateManager.Stub {
@Override // Binder call
public DeviceStateInfo getDeviceStateInfo() {
synchronized (mLock) {
return getDeviceStateInfoLocked();
}
}
@Override // Binder call
public void registerCallback(IDeviceStateManagerCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("Device state callback must not be null.");
}
final int callingPid = Binder.getCallingPid();
final long token = Binder.clearCallingIdentity();
try {
registerProcess(callingPid, callback);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override // Binder call
public void requestState(IBinder token, int state, int flags) {
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
assertCanRequestDeviceState(callingPid, state);
if (token == null) {
throw new IllegalArgumentException("Request token must not be null.");
}
final long callingIdentity = Binder.clearCallingIdentity();
try {
requestStateInternal(state, flags, callingPid, token);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
}
@Override // Binder call
public void cancelStateRequest() {
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
assertCanControlDeviceState(callingPid);
final long callingIdentity = Binder.clearCallingIdentity();
try {
cancelStateRequestInternal(callingPid);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
}
@Override // Binder call
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver result) {
new DeviceStateManagerShellCommand(DeviceStateManagerService.this)
.exec(this, in, out, err, args, callback, result);
}
@Override // Binder call
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
final long token = Binder.clearCallingIdentity();
try {
dumpInternal(pw);
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
/** Implementation of {@link DeviceStateManagerInternal} published as a local service. */
private final class LocalService extends DeviceStateManagerInternal {
@Override
public int[] getSupportedStateIdentifiers() {
synchronized (mLock) {
return getSupportedStateIdentifiersLocked();
}
}
}
}