script-astra/Android/Sdk/sources/android-35/android/hardware/devicestate/DeviceStateManagerGlobal.java
localadmin 4380f00a78 init
2025-01-20 18:15:20 +03:00

460 lines
17 KiB
Java

/*
* 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 android.hardware.devicestate;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Trace;
import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
/**
* Provides communication with the device state system service on behalf of applications.
*
* @see DeviceStateManager
*
* @hide
*/
@VisibleForTesting(visibility = Visibility.PACKAGE)
public final class DeviceStateManagerGlobal {
private static DeviceStateManagerGlobal sInstance;
private static final String TAG = "DeviceStateManagerGlobal";
private static final boolean DEBUG = Build.IS_DEBUGGABLE;
/**
* Returns an instance of {@link DeviceStateManagerGlobal}. May return {@code null} if a
* connection with the device state service couldn't be established.
*/
@Nullable
public static DeviceStateManagerGlobal getInstance() {
synchronized (DeviceStateManagerGlobal.class) {
if (sInstance == null) {
IBinder b = ServiceManager.getService(Context.DEVICE_STATE_SERVICE);
if (b != null) {
sInstance = new DeviceStateManagerGlobal(IDeviceStateManager
.Stub.asInterface(b));
}
}
return sInstance;
}
}
private final Object mLock = new Object();
@NonNull
private final IDeviceStateManager mDeviceStateManager;
@Nullable
private DeviceStateManagerCallback mCallback;
@GuardedBy("mLock")
private final ArrayList<DeviceStateCallbackWrapper> mCallbacks = new ArrayList<>();
@GuardedBy("mLock")
private final ArrayMap<IBinder, DeviceStateRequestWrapper> mRequests = new ArrayMap<>();
@Nullable
@GuardedBy("mLock")
private DeviceStateInfo mLastReceivedInfo;
@VisibleForTesting
public DeviceStateManagerGlobal(@NonNull IDeviceStateManager deviceStateManager) {
mDeviceStateManager = deviceStateManager;
registerCallbackIfNeededLocked();
}
/**
* Returns {@link List} of supported {@link DeviceState}s.
*
* @see DeviceStateManager#getSupportedDeviceStates()
*/
public List<DeviceState> getSupportedDeviceStates() {
synchronized (mLock) {
final DeviceStateInfo currentInfo;
if (mLastReceivedInfo != null) {
// If we have mLastReceivedInfo a callback is registered for this instance and it
// is receiving the most recent info from the server. Use that info here.
currentInfo = mLastReceivedInfo;
} else {
// If mLastReceivedInfo is null there is no registered callback so we manually
// fetch the current info.
try {
currentInfo = mDeviceStateManager.getDeviceStateInfo();
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
return List.copyOf(currentInfo.supportedStates);
}
}
/**
* Submits a {@link DeviceStateRequest request} to modify the device state.
*
* @see DeviceStateManager#requestState(DeviceStateRequest, Executor,
* DeviceStateRequest.Callback)
* @see DeviceStateRequest
*/
@RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
conditional = true)
public void requestState(@NonNull DeviceStateRequest request,
@Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) {
DeviceStateRequestWrapper requestWrapper = new DeviceStateRequestWrapper(request, callback,
executor);
synchronized (mLock) {
if (findRequestTokenLocked(request) != null) {
// This request has already been submitted.
return;
}
// Add the request wrapper to the mRequests array before requesting the state as the
// callback could be triggered immediately if the mDeviceStateManager IBinder is in the
// same process as this instance.
IBinder token = new Binder();
mRequests.put(token, requestWrapper);
try {
mDeviceStateManager.requestState(token, request.getState(), request.getFlags());
} catch (RemoteException ex) {
mRequests.remove(token);
throw ex.rethrowFromSystemServer();
}
}
}
/**
* Cancels a {@link DeviceStateRequest request} previously submitted with a call to
* {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
*
* @see DeviceStateManager#cancelStateRequest
*/
@RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
conditional = true)
public void cancelStateRequest() {
synchronized (mLock) {
try {
mDeviceStateManager.cancelStateRequest();
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}
/**
* Submits a {@link DeviceStateRequest request} to modify the base state of the device.
*
* @see DeviceStateManager#requestBaseStateOverride(DeviceStateRequest, Executor,
* DeviceStateRequest.Callback)
* @see DeviceStateRequest
*/
@RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
public void requestBaseStateOverride(@NonNull DeviceStateRequest request,
@Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) {
DeviceStateRequestWrapper requestWrapper = new DeviceStateRequestWrapper(request, callback,
executor);
synchronized (mLock) {
if (findRequestTokenLocked(request) != null) {
// This request has already been submitted.
return;
}
// Add the request wrapper to the mRequests array before requesting the state as the
// callback could be triggered immediately if the mDeviceStateManager IBinder is in the
// same process as this instance.
IBinder token = new Binder();
mRequests.put(token, requestWrapper);
try {
mDeviceStateManager.requestBaseStateOverride(token, request.getState(),
request.getFlags());
} catch (RemoteException ex) {
mRequests.remove(token);
throw ex.rethrowFromSystemServer();
}
}
}
/**
* Cancels a {@link DeviceStateRequest request} previously submitted with a call to
* {@link #requestBaseStateOverride(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
*
* @see DeviceStateManager#cancelBaseStateOverride
*/
@RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
public void cancelBaseStateOverride() {
synchronized (mLock) {
try {
mDeviceStateManager.cancelBaseStateOverride();
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}
/**
* Registers a callback to receive notifications about changes in device state.
*
* @see DeviceStateManager#registerCallback(Executor, DeviceStateCallback)
*/
@VisibleForTesting(visibility = Visibility.PACKAGE)
public void registerDeviceStateCallback(@NonNull DeviceStateCallback callback,
@NonNull Executor executor) {
synchronized (mLock) {
int index = findCallbackLocked(callback);
if (index != -1) {
// This callback is already registered.
return;
}
// Add the callback wrapper to the mCallbacks array after registering the callback as
// the callback could be triggered immediately if the mDeviceStateManager IBinder is in
// the same process as this instance.
DeviceStateCallbackWrapper wrapper = new DeviceStateCallbackWrapper(callback, executor);
mCallbacks.add(wrapper);
if (mLastReceivedInfo != null) {
wrapper.notifySupportedDeviceStatesChanged(
List.copyOf(mLastReceivedInfo.supportedStates));
wrapper.notifyDeviceStateChanged(mLastReceivedInfo.currentState);
}
}
}
/**
* Unregisters a callback previously registered with
* {@link #registerDeviceStateCallback(DeviceStateCallback, Executor)}}.
*
* @see DeviceStateManager#unregisterCallback(DeviceStateCallback)
*/
@VisibleForTesting(visibility = Visibility.PACKAGE)
public void unregisterDeviceStateCallback(@NonNull DeviceStateCallback callback) {
synchronized (mLock) {
int indexToRemove = findCallbackLocked(callback);
if (indexToRemove != -1) {
mCallbacks.remove(indexToRemove);
}
}
}
/**
* Provides notification to the system server that a device state feature overlay
* was dismissed. This should only be called from the {@link android.app.Activity} that
* was showing the overlay corresponding to the feature.
*
* Validation of there being an overlay visible and pending state request is handled on the
* system server.
*/
public void onStateRequestOverlayDismissed(boolean shouldCancelRequest) {
try {
mDeviceStateManager.onStateRequestOverlayDismissed(shouldCancelRequest);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
private void registerCallbackIfNeededLocked() {
if (mCallback == null) {
mCallback = new DeviceStateManagerCallback();
try {
mDeviceStateManager.registerCallback(mCallback);
} catch (RemoteException ex) {
mCallback = null;
throw ex.rethrowFromSystemServer();
}
}
}
private int findCallbackLocked(DeviceStateCallback callback) {
for (int i = 0; i < mCallbacks.size(); i++) {
if (mCallbacks.get(i).mDeviceStateCallback.equals(callback)) {
return i;
}
}
return -1;
}
@Nullable
private IBinder findRequestTokenLocked(@NonNull DeviceStateRequest request) {
for (int i = 0; i < mRequests.size(); i++) {
if (mRequests.valueAt(i).mRequest.equals(request)) {
return mRequests.keyAt(i);
}
}
return null;
}
/** Handles a call from the server that the device state info has changed. */
private void handleDeviceStateInfoChanged(@NonNull DeviceStateInfo info) {
ArrayList<DeviceStateCallbackWrapper> callbacks;
DeviceStateInfo oldInfo;
synchronized (mLock) {
oldInfo = mLastReceivedInfo;
mLastReceivedInfo = info;
callbacks = new ArrayList<>(mCallbacks);
}
final int diff = oldInfo == null ? ~0 : info.diff(oldInfo);
if ((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0) {
for (int i = 0; i < callbacks.size(); i++) {
callbacks.get(i).notifySupportedDeviceStatesChanged(
List.copyOf(info.supportedStates));
}
}
if ((diff & DeviceStateInfo.CHANGED_CURRENT_STATE) > 0) {
for (int i = 0; i < callbacks.size(); i++) {
callbacks.get(i).notifyDeviceStateChanged(info.currentState);
}
}
}
/**
* Handles a call from the server that a request for the supplied {@code token} has become
* active.
*/
private void handleRequestActive(IBinder token) {
DeviceStateRequestWrapper request;
synchronized (mLock) {
request = mRequests.get(token);
}
if (request != null) {
request.notifyRequestActive();
}
}
/**
* Handles a call from the server that a request for the supplied {@code token} has become
* canceled.
*/
private void handleRequestCanceled(IBinder token) {
DeviceStateRequestWrapper request;
synchronized (mLock) {
request = mRequests.remove(token);
}
if (request != null) {
request.notifyRequestCanceled();
}
}
private final class DeviceStateManagerCallback extends IDeviceStateManagerCallback.Stub {
@Override
public void onDeviceStateInfoChanged(DeviceStateInfo info) {
handleDeviceStateInfoChanged(info);
}
@Override
public void onRequestActive(IBinder token) {
handleRequestActive(token);
}
@Override
public void onRequestCanceled(IBinder token) {
handleRequestCanceled(token);
}
}
private static final class DeviceStateCallbackWrapper {
@NonNull
private final DeviceStateCallback mDeviceStateCallback;
@NonNull
private final Executor mExecutor;
DeviceStateCallbackWrapper(@NonNull DeviceStateCallback callback,
@NonNull Executor executor) {
mDeviceStateCallback = callback;
mExecutor = executor;
}
void notifySupportedDeviceStatesChanged(List<DeviceState> newSupportedDeviceStates) {
mExecutor.execute(() ->
mDeviceStateCallback.onSupportedStatesChanged(newSupportedDeviceStates));
}
void notifyDeviceStateChanged(DeviceState newDeviceState) {
execute("notifyDeviceStateChanged",
() -> mDeviceStateCallback.onDeviceStateChanged(newDeviceState));
}
private void execute(String traceName, Runnable r) {
mExecutor.execute(() -> {
if (DEBUG) {
Trace.beginSection(
mDeviceStateCallback.getClass().getSimpleName() + "#" + traceName);
}
try {
r.run();
} finally {
if (DEBUG) {
Trace.endSection();
}
}
});
}
}
private static final class DeviceStateRequestWrapper {
private final DeviceStateRequest mRequest;
@Nullable
private final DeviceStateRequest.Callback mCallback;
@Nullable
private final Executor mExecutor;
DeviceStateRequestWrapper(@NonNull DeviceStateRequest request,
@Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) {
validateRequestWrapperParameters(callback, executor);
mRequest = request;
mCallback = callback;
mExecutor = executor;
}
void notifyRequestActive() {
if (mCallback == null) {
return;
}
mExecutor.execute(() -> mCallback.onRequestActivated(mRequest));
}
void notifyRequestCanceled() {
if (mCallback == null) {
return;
}
mExecutor.execute(() -> mCallback.onRequestCanceled(mRequest));
}
private void validateRequestWrapperParameters(
@Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) {
if (callback == null && executor != null) {
throw new IllegalArgumentException("Callback must be supplied with executor.");
} else if (executor == null && callback != null) {
throw new IllegalArgumentException("Executor must be supplied with callback.");
}
}
}
}