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