1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.hardware.devicestate; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.RequiresPermission; 22 import android.content.Context; 23 import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; 24 import android.os.Binder; 25 import android.os.Build; 26 import android.os.IBinder; 27 import android.os.RemoteException; 28 import android.os.ServiceManager; 29 import android.os.Trace; 30 import android.util.ArrayMap; 31 32 import com.android.internal.annotations.GuardedBy; 33 import com.android.internal.annotations.VisibleForTesting; 34 import com.android.internal.annotations.VisibleForTesting.Visibility; 35 36 import java.util.ArrayList; 37 import java.util.List; 38 import java.util.concurrent.Executor; 39 40 /** 41 * Provides communication with the device state system service on behalf of applications. 42 * 43 * @see DeviceStateManager 44 * 45 * @hide 46 */ 47 @VisibleForTesting(visibility = Visibility.PACKAGE) 48 public final class DeviceStateManagerGlobal { 49 private static DeviceStateManagerGlobal sInstance; 50 private static final String TAG = "DeviceStateManagerGlobal"; 51 private static final boolean DEBUG = Build.IS_DEBUGGABLE; 52 53 /** 54 * Returns an instance of {@link DeviceStateManagerGlobal}. May return {@code null} if a 55 * connection with the device state service couldn't be established. 56 */ 57 @Nullable getInstance()58 public static DeviceStateManagerGlobal getInstance() { 59 synchronized (DeviceStateManagerGlobal.class) { 60 if (sInstance == null) { 61 IBinder b = ServiceManager.getService(Context.DEVICE_STATE_SERVICE); 62 if (b != null) { 63 sInstance = new DeviceStateManagerGlobal(IDeviceStateManager 64 .Stub.asInterface(b)); 65 } 66 } 67 return sInstance; 68 } 69 } 70 71 private final Object mLock = new Object(); 72 @NonNull 73 private final IDeviceStateManager mDeviceStateManager; 74 @Nullable 75 private DeviceStateManagerCallback mCallback; 76 77 @GuardedBy("mLock") 78 private final ArrayList<DeviceStateCallbackWrapper> mCallbacks = new ArrayList<>(); 79 @GuardedBy("mLock") 80 private final ArrayMap<IBinder, DeviceStateRequestWrapper> mRequests = new ArrayMap<>(); 81 82 @Nullable 83 @GuardedBy("mLock") 84 private DeviceStateInfo mLastReceivedInfo; 85 86 @VisibleForTesting DeviceStateManagerGlobal(@onNull IDeviceStateManager deviceStateManager)87 public DeviceStateManagerGlobal(@NonNull IDeviceStateManager deviceStateManager) { 88 mDeviceStateManager = deviceStateManager; 89 registerCallbackIfNeededLocked(); 90 } 91 92 /** 93 * Returns {@link List} of supported {@link DeviceState}s. 94 * 95 * @see DeviceStateManager#getSupportedDeviceStates() 96 */ getSupportedDeviceStates()97 public List<DeviceState> getSupportedDeviceStates() { 98 synchronized (mLock) { 99 final DeviceStateInfo currentInfo; 100 if (mLastReceivedInfo != null) { 101 // If we have mLastReceivedInfo a callback is registered for this instance and it 102 // is receiving the most recent info from the server. Use that info here. 103 currentInfo = mLastReceivedInfo; 104 } else { 105 // If mLastReceivedInfo is null there is no registered callback so we manually 106 // fetch the current info. 107 try { 108 currentInfo = mDeviceStateManager.getDeviceStateInfo(); 109 } catch (RemoteException ex) { 110 throw ex.rethrowFromSystemServer(); 111 } 112 } 113 114 return List.copyOf(currentInfo.supportedStates); 115 } 116 } 117 118 /** 119 * Submits a {@link DeviceStateRequest request} to modify the device state. 120 * 121 * @see DeviceStateManager#requestState(DeviceStateRequest, Executor, 122 * DeviceStateRequest.Callback) 123 * @see DeviceStateRequest 124 */ 125 @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE, 126 conditional = true) requestState(@onNull DeviceStateRequest request, @Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback)127 public void requestState(@NonNull DeviceStateRequest request, 128 @Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) { 129 DeviceStateRequestWrapper requestWrapper = new DeviceStateRequestWrapper(request, callback, 130 executor); 131 synchronized (mLock) { 132 if (findRequestTokenLocked(request) != null) { 133 // This request has already been submitted. 134 return; 135 } 136 // Add the request wrapper to the mRequests array before requesting the state as the 137 // callback could be triggered immediately if the mDeviceStateManager IBinder is in the 138 // same process as this instance. 139 IBinder token = new Binder(); 140 mRequests.put(token, requestWrapper); 141 142 try { 143 mDeviceStateManager.requestState(token, request.getState(), request.getFlags()); 144 } catch (RemoteException ex) { 145 mRequests.remove(token); 146 throw ex.rethrowFromSystemServer(); 147 } 148 } 149 } 150 151 /** 152 * Cancels a {@link DeviceStateRequest request} previously submitted with a call to 153 * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. 154 * 155 * @see DeviceStateManager#cancelStateRequest 156 */ 157 @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE, 158 conditional = true) cancelStateRequest()159 public void cancelStateRequest() { 160 synchronized (mLock) { 161 try { 162 mDeviceStateManager.cancelStateRequest(); 163 } catch (RemoteException ex) { 164 throw ex.rethrowFromSystemServer(); 165 } 166 } 167 } 168 169 /** 170 * Submits a {@link DeviceStateRequest request} to modify the base state of the device. 171 * 172 * @see DeviceStateManager#requestBaseStateOverride(DeviceStateRequest, Executor, 173 * DeviceStateRequest.Callback) 174 * @see DeviceStateRequest 175 */ 176 @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) requestBaseStateOverride(@onNull DeviceStateRequest request, @Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback)177 public void requestBaseStateOverride(@NonNull DeviceStateRequest request, 178 @Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) { 179 DeviceStateRequestWrapper requestWrapper = new DeviceStateRequestWrapper(request, callback, 180 executor); 181 synchronized (mLock) { 182 if (findRequestTokenLocked(request) != null) { 183 // This request has already been submitted. 184 return; 185 } 186 // Add the request wrapper to the mRequests array before requesting the state as the 187 // callback could be triggered immediately if the mDeviceStateManager IBinder is in the 188 // same process as this instance. 189 IBinder token = new Binder(); 190 mRequests.put(token, requestWrapper); 191 192 try { 193 mDeviceStateManager.requestBaseStateOverride(token, request.getState(), 194 request.getFlags()); 195 } catch (RemoteException ex) { 196 mRequests.remove(token); 197 throw ex.rethrowFromSystemServer(); 198 } 199 } 200 } 201 202 /** 203 * Cancels a {@link DeviceStateRequest request} previously submitted with a call to 204 * {@link #requestBaseStateOverride(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. 205 * 206 * @see DeviceStateManager#cancelBaseStateOverride 207 */ 208 @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) cancelBaseStateOverride()209 public void cancelBaseStateOverride() { 210 synchronized (mLock) { 211 try { 212 mDeviceStateManager.cancelBaseStateOverride(); 213 } catch (RemoteException ex) { 214 throw ex.rethrowFromSystemServer(); 215 } 216 } 217 } 218 219 /** 220 * Registers a callback to receive notifications about changes in device state. 221 * 222 * @see DeviceStateManager#registerCallback(Executor, DeviceStateCallback) 223 */ 224 @VisibleForTesting(visibility = Visibility.PACKAGE) registerDeviceStateCallback(@onNull DeviceStateCallback callback, @NonNull Executor executor)225 public void registerDeviceStateCallback(@NonNull DeviceStateCallback callback, 226 @NonNull Executor executor) { 227 synchronized (mLock) { 228 int index = findCallbackLocked(callback); 229 if (index != -1) { 230 // This callback is already registered. 231 return; 232 } 233 // Add the callback wrapper to the mCallbacks array after registering the callback as 234 // the callback could be triggered immediately if the mDeviceStateManager IBinder is in 235 // the same process as this instance. 236 DeviceStateCallbackWrapper wrapper = new DeviceStateCallbackWrapper(callback, executor); 237 mCallbacks.add(wrapper); 238 239 if (mLastReceivedInfo != null) { 240 wrapper.notifySupportedDeviceStatesChanged( 241 List.copyOf(mLastReceivedInfo.supportedStates)); 242 wrapper.notifyDeviceStateChanged(mLastReceivedInfo.currentState); 243 } 244 } 245 } 246 247 /** 248 * Unregisters a callback previously registered with 249 * {@link #registerDeviceStateCallback(DeviceStateCallback, Executor)}}. 250 * 251 * @see DeviceStateManager#unregisterCallback(DeviceStateCallback) 252 */ 253 @VisibleForTesting(visibility = Visibility.PACKAGE) unregisterDeviceStateCallback(@onNull DeviceStateCallback callback)254 public void unregisterDeviceStateCallback(@NonNull DeviceStateCallback callback) { 255 synchronized (mLock) { 256 int indexToRemove = findCallbackLocked(callback); 257 if (indexToRemove != -1) { 258 mCallbacks.remove(indexToRemove); 259 } 260 } 261 } 262 263 /** 264 * Provides notification to the system server that a device state feature overlay 265 * was dismissed. This should only be called from the {@link android.app.Activity} that 266 * was showing the overlay corresponding to the feature. 267 * 268 * Validation of there being an overlay visible and pending state request is handled on the 269 * system server. 270 */ onStateRequestOverlayDismissed(boolean shouldCancelRequest)271 public void onStateRequestOverlayDismissed(boolean shouldCancelRequest) { 272 try { 273 mDeviceStateManager.onStateRequestOverlayDismissed(shouldCancelRequest); 274 } catch (RemoteException ex) { 275 throw ex.rethrowFromSystemServer(); 276 } 277 } 278 registerCallbackIfNeededLocked()279 private void registerCallbackIfNeededLocked() { 280 if (mCallback == null) { 281 mCallback = new DeviceStateManagerCallback(); 282 try { 283 mDeviceStateManager.registerCallback(mCallback); 284 } catch (RemoteException ex) { 285 mCallback = null; 286 throw ex.rethrowFromSystemServer(); 287 } 288 } 289 } 290 findCallbackLocked(DeviceStateCallback callback)291 private int findCallbackLocked(DeviceStateCallback callback) { 292 for (int i = 0; i < mCallbacks.size(); i++) { 293 if (mCallbacks.get(i).mDeviceStateCallback.equals(callback)) { 294 return i; 295 } 296 } 297 return -1; 298 } 299 300 @Nullable findRequestTokenLocked(@onNull DeviceStateRequest request)301 private IBinder findRequestTokenLocked(@NonNull DeviceStateRequest request) { 302 for (int i = 0; i < mRequests.size(); i++) { 303 if (mRequests.valueAt(i).mRequest.equals(request)) { 304 return mRequests.keyAt(i); 305 } 306 } 307 return null; 308 } 309 310 /** Handles a call from the server that the device state info has changed. */ handleDeviceStateInfoChanged(@onNull DeviceStateInfo info)311 private void handleDeviceStateInfoChanged(@NonNull DeviceStateInfo info) { 312 ArrayList<DeviceStateCallbackWrapper> callbacks; 313 DeviceStateInfo oldInfo; 314 synchronized (mLock) { 315 oldInfo = mLastReceivedInfo; 316 mLastReceivedInfo = info; 317 callbacks = new ArrayList<>(mCallbacks); 318 } 319 320 final int diff = oldInfo == null ? ~0 : info.diff(oldInfo); 321 if ((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0) { 322 for (int i = 0; i < callbacks.size(); i++) { 323 callbacks.get(i).notifySupportedDeviceStatesChanged( 324 List.copyOf(info.supportedStates)); 325 } 326 } 327 if ((diff & DeviceStateInfo.CHANGED_CURRENT_STATE) > 0) { 328 for (int i = 0; i < callbacks.size(); i++) { 329 callbacks.get(i).notifyDeviceStateChanged(info.currentState); 330 } 331 } 332 } 333 334 /** 335 * Handles a call from the server that a request for the supplied {@code token} has become 336 * active. 337 */ handleRequestActive(IBinder token)338 private void handleRequestActive(IBinder token) { 339 DeviceStateRequestWrapper request; 340 synchronized (mLock) { 341 request = mRequests.get(token); 342 } 343 if (request != null) { 344 request.notifyRequestActive(); 345 } 346 } 347 348 /** 349 * Handles a call from the server that a request for the supplied {@code token} has become 350 * canceled. 351 */ handleRequestCanceled(IBinder token)352 private void handleRequestCanceled(IBinder token) { 353 DeviceStateRequestWrapper request; 354 synchronized (mLock) { 355 request = mRequests.remove(token); 356 } 357 if (request != null) { 358 request.notifyRequestCanceled(); 359 } 360 } 361 362 private final class DeviceStateManagerCallback extends IDeviceStateManagerCallback.Stub { 363 @Override onDeviceStateInfoChanged(DeviceStateInfo info)364 public void onDeviceStateInfoChanged(DeviceStateInfo info) { 365 handleDeviceStateInfoChanged(info); 366 } 367 368 @Override onRequestActive(IBinder token)369 public void onRequestActive(IBinder token) { 370 handleRequestActive(token); 371 } 372 373 @Override onRequestCanceled(IBinder token)374 public void onRequestCanceled(IBinder token) { 375 handleRequestCanceled(token); 376 } 377 } 378 379 private static final class DeviceStateCallbackWrapper { 380 @NonNull 381 private final DeviceStateCallback mDeviceStateCallback; 382 @NonNull 383 private final Executor mExecutor; 384 DeviceStateCallbackWrapper(@onNull DeviceStateCallback callback, @NonNull Executor executor)385 DeviceStateCallbackWrapper(@NonNull DeviceStateCallback callback, 386 @NonNull Executor executor) { 387 mDeviceStateCallback = callback; 388 mExecutor = executor; 389 } 390 notifySupportedDeviceStatesChanged(List<DeviceState> newSupportedDeviceStates)391 void notifySupportedDeviceStatesChanged(List<DeviceState> newSupportedDeviceStates) { 392 mExecutor.execute(() -> 393 mDeviceStateCallback.onSupportedStatesChanged(newSupportedDeviceStates)); 394 } 395 notifyDeviceStateChanged(DeviceState newDeviceState)396 void notifyDeviceStateChanged(DeviceState newDeviceState) { 397 execute("notifyDeviceStateChanged", 398 () -> mDeviceStateCallback.onDeviceStateChanged(newDeviceState)); 399 } 400 execute(String traceName, Runnable r)401 private void execute(String traceName, Runnable r) { 402 mExecutor.execute(() -> { 403 if (DEBUG) { 404 Trace.beginSection( 405 mDeviceStateCallback.getClass().getSimpleName() + "#" + traceName); 406 } 407 try { 408 r.run(); 409 } finally { 410 if (DEBUG) { 411 Trace.endSection(); 412 } 413 } 414 }); 415 } 416 } 417 418 private static final class DeviceStateRequestWrapper { 419 private final DeviceStateRequest mRequest; 420 @Nullable 421 private final DeviceStateRequest.Callback mCallback; 422 @Nullable 423 private final Executor mExecutor; 424 DeviceStateRequestWrapper(@onNull DeviceStateRequest request, @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor)425 DeviceStateRequestWrapper(@NonNull DeviceStateRequest request, 426 @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) { 427 validateRequestWrapperParameters(callback, executor); 428 429 mRequest = request; 430 mCallback = callback; 431 mExecutor = executor; 432 } 433 notifyRequestActive()434 void notifyRequestActive() { 435 if (mCallback == null) { 436 return; 437 } 438 439 mExecutor.execute(() -> mCallback.onRequestActivated(mRequest)); 440 } 441 notifyRequestCanceled()442 void notifyRequestCanceled() { 443 if (mCallback == null) { 444 return; 445 } 446 447 mExecutor.execute(() -> mCallback.onRequestCanceled(mRequest)); 448 } 449 validateRequestWrapperParameters( @ullable DeviceStateRequest.Callback callback, @Nullable Executor executor)450 private void validateRequestWrapperParameters( 451 @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) { 452 if (callback == null && executor != null) { 453 throw new IllegalArgumentException("Callback must be supplied with executor."); 454 } else if (executor == null && callback != null) { 455 throw new IllegalArgumentException("Executor must be supplied with callback."); 456 } 457 } 458 } 459 } 460