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