1 /* 2 * Copyright (C) 2022 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 com.android.server.companion.devicepresence; 18 19 import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; 20 import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED; 21 import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED; 22 import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED; 23 import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED; 24 import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED; 25 import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED; 26 import static android.companion.DevicePresenceEvent.NO_ASSOCIATION; 27 import static android.content.Context.BLUETOOTH_SERVICE; 28 import static android.os.Process.ROOT_UID; 29 import static android.os.Process.SHELL_UID; 30 31 import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage; 32 import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanObserveDevicePresenceByUuid; 33 34 import android.annotation.NonNull; 35 import android.annotation.SuppressLint; 36 import android.annotation.TestApi; 37 import android.annotation.UserIdInt; 38 import android.bluetooth.BluetoothAdapter; 39 import android.bluetooth.BluetoothManager; 40 import android.companion.AssociationInfo; 41 import android.companion.DeviceNotAssociatedException; 42 import android.companion.DevicePresenceEvent; 43 import android.companion.ObservingDevicePresenceRequest; 44 import android.content.Context; 45 import android.hardware.power.Mode; 46 import android.os.Binder; 47 import android.os.Handler; 48 import android.os.Looper; 49 import android.os.Message; 50 import android.os.ParcelUuid; 51 import android.os.PowerManagerInternal; 52 import android.os.RemoteException; 53 import android.os.UserManager; 54 import android.util.Slog; 55 import android.util.SparseArray; 56 import android.util.SparseBooleanArray; 57 58 import com.android.internal.annotations.GuardedBy; 59 import com.android.internal.util.CollectionUtils; 60 import com.android.server.companion.association.AssociationStore; 61 62 import java.io.PrintWriter; 63 import java.util.ArrayList; 64 import java.util.HashSet; 65 import java.util.List; 66 import java.util.Objects; 67 import java.util.Set; 68 69 /** 70 * Class responsible for monitoring companion devices' "presence" status (i.e. 71 * connected/disconnected for Bluetooth devices; nearby or not for BLE devices). 72 * 73 * <p> 74 * Should only be used by 75 * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService} 76 * to which it provides the following API: 77 * <ul> 78 * <li> {@link #onSelfManagedDeviceConnected(int)} 79 * <li> {@link #onSelfManagedDeviceDisconnected(int)} 80 * <li> {@link #isDevicePresent(int)} 81 * </ul> 82 */ 83 @SuppressLint("LongLogTag") 84 public class DevicePresenceProcessor implements AssociationStore.OnChangeListener, 85 BluetoothDeviceProcessor.Callback, BleDeviceProcessor.Callback { 86 private static final String TAG = "CDM_DevicePresenceProcessor"; 87 88 @NonNull 89 private final Context mContext; 90 @NonNull 91 private final CompanionAppBinder mCompanionAppBinder; 92 @NonNull 93 private final AssociationStore mAssociationStore; 94 @NonNull 95 private final ObservableUuidStore mObservableUuidStore; 96 @NonNull 97 private final BluetoothDeviceProcessor mBluetoothDeviceProcessor; 98 @NonNull 99 private final BleDeviceProcessor mBleDeviceProcessor; 100 @NonNull 101 private final PowerManagerInternal mPowerManagerInternal; 102 @NonNull 103 private final UserManager mUserManager; 104 105 // NOTE: Same association may appear in more than one of the following sets at the same time. 106 // (E.g. self-managed devices that have MAC addresses, could be reported as present by their 107 // companion applications, while at the same be connected via BT, or detected nearby by BLE 108 // scanner) 109 @NonNull 110 private final Set<Integer> mConnectedBtDevices = new HashSet<>(); 111 @NonNull 112 private final Set<Integer> mNearbyBleDevices = new HashSet<>(); 113 @NonNull 114 private final Set<Integer> mReportedSelfManagedDevices = new HashSet<>(); 115 @NonNull 116 private final Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>(); 117 @NonNull 118 @GuardedBy("mBtDisconnectedDevices") 119 private final Set<Integer> mBtDisconnectedDevices = new HashSet<>(); 120 121 // A map to track device presence within 10 seconds of Bluetooth disconnection. 122 // The key is the association ID, and the boolean value indicates if the device 123 // was detected again within that time frame. 124 @GuardedBy("mBtDisconnectedDevices") 125 private final @NonNull SparseBooleanArray mBtDisconnectedDevicesBlePresence = 126 new SparseBooleanArray(); 127 128 // Tracking "simulated" presence. Used for debugging and testing only. 129 private final @NonNull Set<Integer> mSimulated = new HashSet<>(); 130 private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper = 131 new SimulatedDevicePresenceSchedulerHelper(); 132 133 private final BleDeviceDisappearedScheduler mBleDeviceDisappearedScheduler = 134 new BleDeviceDisappearedScheduler(); 135 136 /** 137 * A structure hold the DevicePresenceEvents that are pending to be reported to the companion 138 * app when the user unlocks the local device per userId. 139 */ 140 @GuardedBy("mPendingDevicePresenceEvents") 141 public final SparseArray<List<DevicePresenceEvent>> mPendingDevicePresenceEvents = 142 new SparseArray<>(); 143 DevicePresenceProcessor(@onNull Context context, @NonNull CompanionAppBinder companionAppBinder, @NonNull UserManager userManager, @NonNull AssociationStore associationStore, @NonNull ObservableUuidStore observableUuidStore, @NonNull PowerManagerInternal powerManagerInternal)144 public DevicePresenceProcessor(@NonNull Context context, 145 @NonNull CompanionAppBinder companionAppBinder, 146 @NonNull UserManager userManager, 147 @NonNull AssociationStore associationStore, 148 @NonNull ObservableUuidStore observableUuidStore, 149 @NonNull PowerManagerInternal powerManagerInternal) { 150 mContext = context; 151 mCompanionAppBinder = companionAppBinder; 152 mAssociationStore = associationStore; 153 mObservableUuidStore = observableUuidStore; 154 mUserManager = userManager; 155 mBluetoothDeviceProcessor = new BluetoothDeviceProcessor(associationStore, 156 mObservableUuidStore, this); 157 mBleDeviceProcessor = new BleDeviceProcessor(associationStore, this); 158 mPowerManagerInternal = powerManagerInternal; 159 } 160 161 /** Initialize {@link DevicePresenceProcessor} */ init(Context context)162 public void init(Context context) { 163 BluetoothManager bm = (BluetoothManager) context.getSystemService(BLUETOOTH_SERVICE); 164 if (bm == null) { 165 Slog.w(TAG, "BluetoothManager is not available."); 166 return; 167 } 168 final BluetoothAdapter btAdapter = bm.getAdapter(); 169 if (btAdapter == null) { 170 Slog.w(TAG, "BluetoothAdapter is NOT available."); 171 return; 172 } 173 174 mBluetoothDeviceProcessor.init(btAdapter); 175 mBleDeviceProcessor.init(context, btAdapter); 176 177 mAssociationStore.registerLocalListener(this); 178 } 179 180 /** 181 * Process device presence start request. 182 */ startObservingDevicePresence(ObservingDevicePresenceRequest request, String packageName, int userId, boolean enforcePermissions)183 public void startObservingDevicePresence(ObservingDevicePresenceRequest request, 184 String packageName, int userId, boolean enforcePermissions) { 185 Slog.i(TAG, 186 "Start observing request=[" + request + "] for userId=[" + userId + "], package=[" 187 + packageName + "]..."); 188 final ParcelUuid requestUuid = request.getUuid(); 189 190 if (requestUuid != null) { 191 if (enforcePermissions) { 192 enforceCallerCanObserveDevicePresenceByUuid(mContext, packageName, userId); 193 } 194 195 // If it's already being observed, then no-op. 196 if (mObservableUuidStore.isUuidBeingObserved(requestUuid, userId, packageName)) { 197 Slog.i(TAG, "UUID=[" + requestUuid + "], package=[" + packageName + "], userId=[" 198 + userId + "] is already being observed."); 199 return; 200 } 201 202 final ObservableUuid observableUuid = new ObservableUuid(userId, requestUuid, 203 packageName, System.currentTimeMillis()); 204 mObservableUuidStore.writeObservableUuid(userId, observableUuid); 205 } else { 206 final int associationId = request.getAssociationId(); 207 AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( 208 associationId); 209 210 // If it's already being observed, then no-op. 211 if (association.isNotifyOnDeviceNearby()) { 212 Slog.i(TAG, "Associated device id=[" + association.getId() 213 + "] is already being observed. No-op."); 214 return; 215 } 216 217 association = (new AssociationInfo.Builder(association)).setNotifyOnDeviceNearby(true) 218 .build(); 219 mAssociationStore.updateAssociation(association); 220 221 // Send callback immediately if the device is present. 222 if (isDevicePresent(associationId)) { 223 Slog.i(TAG, "Device is already present. Triggering callback."); 224 if (isBlePresent(associationId)) { 225 onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED); 226 } else if (isBtConnected(associationId)) { 227 onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED); 228 } else if (isSimulatePresent(associationId)) { 229 onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_APPEARED); 230 } 231 } 232 } 233 234 Slog.i(TAG, "Registered device presence listener."); 235 } 236 237 /** 238 * Process device presence stop request. 239 */ stopObservingDevicePresence(ObservingDevicePresenceRequest request, String packageName, int userId, boolean enforcePermissions)240 public void stopObservingDevicePresence(ObservingDevicePresenceRequest request, 241 String packageName, int userId, boolean enforcePermissions) { 242 Slog.i(TAG, 243 "Stop observing request=[" + request + "] for userId=[" + userId + "], package=[" 244 + packageName + "]..."); 245 246 final ParcelUuid requestUuid = request.getUuid(); 247 248 if (requestUuid != null) { 249 if (enforcePermissions) { 250 enforceCallerCanObserveDevicePresenceByUuid(mContext, packageName, userId); 251 } 252 253 if (!mObservableUuidStore.isUuidBeingObserved(requestUuid, userId, packageName)) { 254 Slog.i(TAG, "UUID=[" + requestUuid + "], package=[" + packageName + "], userId=[" 255 + userId + "] is already not being observed."); 256 return; 257 } 258 259 mObservableUuidStore.removeObservableUuid(userId, requestUuid, packageName); 260 removeCurrentConnectedUuidDevice(requestUuid); 261 } else { 262 final int associationId = request.getAssociationId(); 263 AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( 264 associationId); 265 266 // If it's already being observed, then no-op. 267 if (!association.isNotifyOnDeviceNearby()) { 268 Slog.i(TAG, "Associated device id=[" + association.getId() 269 + "] is already not being observed. No-op."); 270 return; 271 } 272 273 association = (new AssociationInfo.Builder(association)).setNotifyOnDeviceNearby(false) 274 .build(); 275 mAssociationStore.updateAssociation(association); 276 } 277 278 Slog.i(TAG, "Unregistered device presence listener."); 279 280 // If last listener is unregistered, then unbind application. 281 if (!shouldBindPackage(userId, packageName)) { 282 mCompanionAppBinder.unbindCompanionApp(userId, packageName); 283 } 284 } 285 286 /** 287 * For legacy device presence below Android V. 288 * 289 * @deprecated Use {@link #startObservingDevicePresence(ObservingDevicePresenceRequest, String, 290 * int, boolean)} 291 */ 292 @Deprecated startObservingDevicePresence(int userId, String packageName, String deviceAddress)293 public void startObservingDevicePresence(int userId, String packageName, String deviceAddress) 294 throws RemoteException { 295 Slog.i(TAG, 296 "Start observing device=[" + deviceAddress + "] for userId=[" + userId 297 + "], package=[" 298 + packageName + "]..."); 299 300 enforceCallerCanManageAssociationsForPackage(mContext, userId, packageName, null); 301 302 AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(userId, 303 packageName, deviceAddress); 304 305 if (association == null) { 306 throw new RemoteException(new DeviceNotAssociatedException("App " + packageName 307 + " is not associated with device " + deviceAddress 308 + " for user " + userId)); 309 } 310 311 startObservingDevicePresence( 312 new ObservingDevicePresenceRequest.Builder().setAssociationId(association.getId()) 313 .build(), packageName, userId, /* enforcePermissions */ true); 314 } 315 316 /** 317 * For legacy device presence below Android V. 318 * 319 * @deprecated Use {@link #stopObservingDevicePresence(ObservingDevicePresenceRequest, String, 320 * int, boolean)} 321 */ 322 @Deprecated stopObservingDevicePresence(int userId, String packageName, String deviceAddress)323 public void stopObservingDevicePresence(int userId, String packageName, String deviceAddress) 324 throws RemoteException { 325 Slog.i(TAG, 326 "Stop observing device=[" + deviceAddress + "] for userId=[" + userId 327 + "], package=[" 328 + packageName + "]..."); 329 330 enforceCallerCanManageAssociationsForPackage(mContext, userId, packageName, null); 331 332 AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(userId, 333 packageName, deviceAddress); 334 335 if (association == null) { 336 throw new RemoteException(new DeviceNotAssociatedException("App " + packageName 337 + " is not associated with device " + deviceAddress 338 + " for user " + userId)); 339 } 340 341 stopObservingDevicePresence( 342 new ObservingDevicePresenceRequest.Builder().setAssociationId(association.getId()) 343 .build(), packageName, userId, /* enforcePermissions */ true); 344 } 345 346 /** 347 * @return whether the package should be bound (i.e. at least one of the devices associated with 348 * the package is currently present OR the UUID to be observed by this package is 349 * currently present). 350 */ shouldBindPackage(@serIdInt int userId, @NonNull String packageName)351 private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) { 352 final List<AssociationInfo> packageAssociations = 353 mAssociationStore.getActiveAssociationsByPackage(userId, packageName); 354 final List<ObservableUuid> observableUuids = 355 mObservableUuidStore.getObservableUuidsForPackage(userId, packageName); 356 357 for (AssociationInfo association : packageAssociations) { 358 if (!association.shouldBindWhenPresent()) continue; 359 if (isDevicePresent(association.getId())) return true; 360 } 361 362 for (ObservableUuid uuid : observableUuids) { 363 if (isDeviceUuidPresent(uuid.getUuid())) { 364 return true; 365 } 366 } 367 368 return false; 369 } 370 371 /** 372 * Bind the system to the app if it's not bound. 373 * 374 * Set bindImportant to true when the association is self-managed to avoid the target service 375 * being killed. 376 */ bindApplicationIfNeeded(int userId, String packageName, boolean bindImportant)377 private void bindApplicationIfNeeded(int userId, String packageName, boolean bindImportant) { 378 if (!mCompanionAppBinder.isCompanionApplicationBound(userId, packageName)) { 379 mCompanionAppBinder.bindCompanionApp( 380 userId, packageName, bindImportant, this::onBinderDied); 381 } else { 382 Slog.i(TAG, 383 "UserId=[" + userId + "], packageName=[" + packageName + "] is already bound."); 384 } 385 } 386 387 /** 388 * @return current connected UUID devices. 389 */ getCurrentConnectedUuidDevices()390 public Set<ParcelUuid> getCurrentConnectedUuidDevices() { 391 return mConnectedUuidDevices; 392 } 393 394 /** 395 * Remove current connected UUID device. 396 */ removeCurrentConnectedUuidDevice(ParcelUuid uuid)397 public void removeCurrentConnectedUuidDevice(ParcelUuid uuid) { 398 mConnectedUuidDevices.remove(uuid); 399 } 400 401 /** 402 * @return whether the associated companion devices is present. I.e. device is nearby (for BLE); 403 * or devices is connected (for Bluetooth); or reported (by the application) to be 404 * nearby (for "self-managed" associations). 405 */ isDevicePresent(int associationId)406 public boolean isDevicePresent(int associationId) { 407 return mReportedSelfManagedDevices.contains(associationId) 408 || mConnectedBtDevices.contains(associationId) 409 || mNearbyBleDevices.contains(associationId) 410 || mSimulated.contains(associationId); 411 } 412 413 /** 414 * @return whether the current uuid to be observed is present. 415 */ isDeviceUuidPresent(ParcelUuid uuid)416 public boolean isDeviceUuidPresent(ParcelUuid uuid) { 417 return mConnectedUuidDevices.contains(uuid); 418 } 419 420 /** 421 * @return whether the current device is BT connected and had already reported to the app. 422 */ 423 isBtConnected(int associationId)424 public boolean isBtConnected(int associationId) { 425 return mConnectedBtDevices.contains(associationId); 426 } 427 428 /** 429 * @return whether the current device in BLE range and had already reported to the app. 430 */ isBlePresent(int associationId)431 public boolean isBlePresent(int associationId) { 432 return mNearbyBleDevices.contains(associationId); 433 } 434 435 /** 436 * @return whether the current device had been already reported by the simulator. 437 */ isSimulatePresent(int associationId)438 public boolean isSimulatePresent(int associationId) { 439 return mSimulated.contains(associationId); 440 } 441 442 /** 443 * Marks a "self-managed" device as connected. 444 * 445 * <p> 446 * Must ONLY be invoked by the 447 * {@link com.android.server.companion.CompanionDeviceManagerService 448 * CompanionDeviceManagerService} 449 * when an application invokes 450 * {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int) 451 * notifyDeviceAppeared()} 452 */ onSelfManagedDeviceConnected(int associationId)453 public void onSelfManagedDeviceConnected(int associationId) { 454 onDevicePresenceEvent(mReportedSelfManagedDevices, 455 associationId, EVENT_SELF_MANAGED_APPEARED); 456 } 457 458 /** 459 * Marks a "self-managed" device as disconnected. 460 * 461 * <p> 462 * Must ONLY be invoked by the 463 * {@link com.android.server.companion.CompanionDeviceManagerService 464 * CompanionDeviceManagerService} 465 * when an application invokes 466 * {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int) 467 * notifyDeviceDisappeared()} 468 */ onSelfManagedDeviceDisconnected(int associationId)469 public void onSelfManagedDeviceDisconnected(int associationId) { 470 onDevicePresenceEvent(mReportedSelfManagedDevices, 471 associationId, EVENT_SELF_MANAGED_DISAPPEARED); 472 } 473 474 /** 475 * Marks a "self-managed" device as disconnected when binderDied. 476 */ onSelfManagedDeviceReporterBinderDied(int associationId)477 public void onSelfManagedDeviceReporterBinderDied(int associationId) { 478 onDevicePresenceEvent(mReportedSelfManagedDevices, 479 associationId, EVENT_SELF_MANAGED_DISAPPEARED); 480 } 481 482 @Override onBluetoothCompanionDeviceConnected(int associationId, int userId)483 public void onBluetoothCompanionDeviceConnected(int associationId, int userId) { 484 Slog.i(TAG, "onBluetoothCompanionDeviceConnected: " 485 + "associationId( " + associationId + " )"); 486 if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { 487 onDeviceLocked(associationId, userId, EVENT_BT_CONNECTED, /* ParcelUuid */ null); 488 return; 489 } 490 491 synchronized (mBtDisconnectedDevices) { 492 // A device is considered reconnected within 10 seconds if a pending BLE lost report is 493 // followed by a detected Bluetooth connection. 494 boolean isReconnected = mBtDisconnectedDevices.contains(associationId); 495 if (isReconnected) { 496 Slog.i(TAG, "Device ( " + associationId + " ) is reconnected within 10s."); 497 mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId); 498 } 499 500 Slog.i(TAG, "onBluetoothCompanionDeviceConnected: " 501 + "associationId( " + associationId + " )"); 502 onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED); 503 504 // Stop the BLE scan if all devices report BT connected status and BLE was present. 505 if (canStopBleScan()) { 506 mBleDeviceProcessor.stopScanIfNeeded(); 507 } 508 509 } 510 } 511 512 @Override onBluetoothCompanionDeviceDisconnected(int associationId, int userId)513 public void onBluetoothCompanionDeviceDisconnected(int associationId, int userId) { 514 Slog.i(TAG, "onBluetoothCompanionDeviceDisconnected " 515 + "associationId( " + associationId + " )"); 516 517 if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { 518 onDeviceLocked(associationId, userId, EVENT_BT_DISCONNECTED, /* ParcelUuid */ null); 519 return; 520 } 521 522 // Start BLE scanning when the device is disconnected. 523 mBleDeviceProcessor.startScan(); 524 525 onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED); 526 // If current device is BLE present but BT is disconnected , means it will be 527 // potentially out of range later. Schedule BLE disappeared callback. 528 if (isBlePresent(associationId)) { 529 synchronized (mBtDisconnectedDevices) { 530 mBtDisconnectedDevices.add(associationId); 531 } 532 mBleDeviceDisappearedScheduler.scheduleBleDeviceDisappeared(associationId); 533 } 534 } 535 536 537 @Override onBleCompanionDeviceFound(int associationId, int userId)538 public void onBleCompanionDeviceFound(int associationId, int userId) { 539 Slog.i(TAG, "onBleCompanionDeviceFound " + "associationId( " + associationId + " )"); 540 if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { 541 onDeviceLocked(associationId, userId, EVENT_BLE_APPEARED, /* ParcelUuid */ null); 542 return; 543 } 544 545 onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED); 546 synchronized (mBtDisconnectedDevices) { 547 final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(associationId); 548 if (mBtDisconnectedDevices.contains(associationId) && isCurrentPresent) { 549 mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId); 550 } 551 } 552 } 553 554 @Override onBleCompanionDeviceLost(int associationId, int userId)555 public void onBleCompanionDeviceLost(int associationId, int userId) { 556 Slog.i(TAG, "onBleCompanionDeviceLost " + "associationId( " + associationId + " )"); 557 if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { 558 onDeviceLocked(associationId, userId, EVENT_BLE_APPEARED, /* ParcelUuid */ null); 559 return; 560 } 561 562 onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED); 563 } 564 565 /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */ 566 @TestApi simulateDeviceEvent(int associationId, int event)567 public void simulateDeviceEvent(int associationId, int event) { 568 // IMPORTANT: this API should only be invoked via the 569 // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to 570 // make this call are SHELL and ROOT. 571 // No other caller (including SYSTEM!) should be allowed. 572 enforceCallerShellOrRoot(); 573 // Make sure the association exists. 574 enforceAssociationExists(associationId); 575 576 final AssociationInfo associationInfo = mAssociationStore.getAssociationById(associationId); 577 578 switch (event) { 579 case EVENT_BLE_APPEARED: 580 simulateDeviceAppeared(associationId, event); 581 break; 582 case EVENT_BT_CONNECTED: 583 onBluetoothCompanionDeviceConnected(associationId, associationInfo.getUserId()); 584 break; 585 case EVENT_BLE_DISAPPEARED: 586 simulateDeviceDisappeared(associationId, event); 587 break; 588 case EVENT_BT_DISCONNECTED: 589 onBluetoothCompanionDeviceDisconnected(associationId, associationInfo.getUserId()); 590 break; 591 default: 592 throw new IllegalArgumentException("Event: " + event + "is not supported"); 593 } 594 } 595 596 /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */ 597 @TestApi simulateDeviceEventByUuid(ObservableUuid uuid, int event)598 public void simulateDeviceEventByUuid(ObservableUuid uuid, int event) { 599 // IMPORTANT: this API should only be invoked via the 600 // 'companiondevice simulate-device-uuid-events' Shell command, so the only uid-s allowed to 601 // make this call are SHELL and ROOT. 602 // No other caller (including SYSTEM!) should be allowed. 603 enforceCallerShellOrRoot(); 604 onDevicePresenceEventByUuid(uuid, event); 605 } 606 607 /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */ 608 @TestApi simulateDeviceEventOnDeviceLocked( int associationId, int userId, int event, ParcelUuid uuid)609 public void simulateDeviceEventOnDeviceLocked( 610 int associationId, int userId, int event, ParcelUuid uuid) { 611 // IMPORTANT: this API should only be invoked via the 612 // 'companiondevice simulate-device-event-device-locked' Shell command, 613 // so the only uid-s allowed to make this call are SHELL and ROOT. 614 // No other caller (including SYSTEM!) should be allowed. 615 enforceCallerShellOrRoot(); 616 onDeviceLocked(associationId, userId, event, uuid); 617 } 618 619 /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */ 620 @TestApi simulateDeviceEventOnUserUnlocked(int userId)621 public void simulateDeviceEventOnUserUnlocked(int userId) { 622 // IMPORTANT: this API should only be invoked via the 623 // 'companiondevice simulate-device-event-device-unlocked' Shell command, 624 // so the only uid-s allowed to make this call are SHELL and ROOT. 625 // No other caller (including SYSTEM!) should be allowed. 626 enforceCallerShellOrRoot(); 627 sendDevicePresenceEventOnUnlocked(userId); 628 } 629 simulateDeviceAppeared(int associationId, int state)630 private void simulateDeviceAppeared(int associationId, int state) { 631 onDevicePresenceEvent(mSimulated, associationId, state); 632 mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId); 633 } 634 simulateDeviceDisappeared(int associationId, int state)635 private void simulateDeviceDisappeared(int associationId, int state) { 636 mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId); 637 onDevicePresenceEvent(mSimulated, associationId, state); 638 } 639 enforceAssociationExists(int associationId)640 private void enforceAssociationExists(int associationId) { 641 if (mAssociationStore.getAssociationById(associationId) == null) { 642 throw new IllegalArgumentException( 643 "Association with id " + associationId + " does not exist."); 644 } 645 } 646 onDevicePresenceEvent(@onNull Set<Integer> presentDevicesForSource, int associationId, int eventType)647 private void onDevicePresenceEvent(@NonNull Set<Integer> presentDevicesForSource, 648 int associationId, int eventType) { 649 Slog.i(TAG, 650 "onDevicePresenceEvent() id=[" + associationId + "], event=[" + eventType + "]..."); 651 652 AssociationInfo association = mAssociationStore.getAssociationById(associationId); 653 if (association == null) { 654 Slog.e(TAG, "Association doesn't exist."); 655 return; 656 } 657 658 final int userId = association.getUserId(); 659 final String packageName = association.getPackageName(); 660 final DevicePresenceEvent event = new DevicePresenceEvent(associationId, eventType, null); 661 662 if (eventType == EVENT_BLE_APPEARED) { 663 synchronized (mBtDisconnectedDevices) { 664 // If a BLE device is detected within 10 seconds after BT is disconnected, 665 // flag it as BLE is present. 666 if (mBtDisconnectedDevices.contains(associationId)) { 667 Slog.i(TAG, "Device ( " + associationId + " ) is present," 668 + " do not need to send the callback with event ( " 669 + EVENT_BLE_APPEARED + " )."); 670 mBtDisconnectedDevicesBlePresence.append(associationId, true); 671 } 672 } 673 } 674 675 switch (eventType) { 676 case EVENT_BLE_APPEARED: 677 case EVENT_BT_CONNECTED: 678 case EVENT_SELF_MANAGED_APPEARED: 679 final boolean added = presentDevicesForSource.add(associationId); 680 if (!added) { 681 Slog.w(TAG, "The association is already present."); 682 } 683 684 if (association.shouldBindWhenPresent()) { 685 bindApplicationIfNeeded(userId, packageName, association.isSelfManaged()); 686 } else { 687 return; 688 } 689 690 if (association.isSelfManaged() || added) { 691 notifyDevicePresenceEvent(userId, packageName, event); 692 // Also send the legacy callback. 693 legacyNotifyDevicePresenceEvent(association, true); 694 } 695 break; 696 case EVENT_BLE_DISAPPEARED: 697 case EVENT_BT_DISCONNECTED: 698 case EVENT_SELF_MANAGED_DISAPPEARED: 699 final boolean removed = presentDevicesForSource.remove(associationId); 700 if (!removed) { 701 Slog.w(TAG, "The association is already NOT present."); 702 } 703 704 if (!mCompanionAppBinder.isCompanionApplicationBound(userId, packageName)) { 705 Slog.e(TAG, "Package is not bound"); 706 return; 707 } 708 709 if (association.isSelfManaged() || removed) { 710 notifyDevicePresenceEvent(userId, packageName, event); 711 // Also send the legacy callback. 712 legacyNotifyDevicePresenceEvent(association, false); 713 } 714 715 // Check if there are other devices associated to the app that are present. 716 if (!shouldBindPackage(userId, packageName)) { 717 mCompanionAppBinder.unbindCompanionApp(userId, packageName); 718 } 719 break; 720 default: 721 Slog.e(TAG, "Event: " + eventType + " is not supported."); 722 break; 723 } 724 } 725 726 @Override onDevicePresenceEventByUuid(ObservableUuid uuid, int eventType)727 public void onDevicePresenceEventByUuid(ObservableUuid uuid, int eventType) { 728 Slog.i(TAG, "onDevicePresenceEventByUuid ObservableUuid=[" + uuid + "], event=[" + eventType 729 + "]..."); 730 731 final ParcelUuid parcelUuid = uuid.getUuid(); 732 final int userId = uuid.getUserId(); 733 if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { 734 onDeviceLocked(NO_ASSOCIATION, userId, eventType, parcelUuid); 735 return; 736 } 737 738 final String packageName = uuid.getPackageName(); 739 final DevicePresenceEvent event = new DevicePresenceEvent(NO_ASSOCIATION, eventType, 740 parcelUuid); 741 742 switch (eventType) { 743 case EVENT_BT_CONNECTED: 744 boolean added = mConnectedUuidDevices.add(parcelUuid); 745 if (!added) { 746 Slog.w(TAG, "This device is already connected."); 747 } 748 749 bindApplicationIfNeeded(userId, packageName, false); 750 751 notifyDevicePresenceEvent(userId, packageName, event); 752 break; 753 case EVENT_BT_DISCONNECTED: 754 final boolean removed = mConnectedUuidDevices.remove(parcelUuid); 755 if (!removed) { 756 Slog.w(TAG, "This device is already disconnected."); 757 return; 758 } 759 760 if (!mCompanionAppBinder.isCompanionApplicationBound(userId, packageName)) { 761 Slog.e(TAG, "Package is not bound."); 762 return; 763 } 764 765 notifyDevicePresenceEvent(userId, packageName, event); 766 767 if (!shouldBindPackage(userId, packageName)) { 768 mCompanionAppBinder.unbindCompanionApp(userId, packageName); 769 } 770 break; 771 default: 772 Slog.e(TAG, "Event: " + eventType + " is not supported"); 773 break; 774 } 775 } 776 777 /** 778 * Notify device presence event to the app. 779 * 780 * @deprecated Use {@link #notifyDevicePresenceEvent(int, String, DevicePresenceEvent)} instead. 781 */ 782 @Deprecated legacyNotifyDevicePresenceEvent(AssociationInfo association, boolean isAppeared)783 private void legacyNotifyDevicePresenceEvent(AssociationInfo association, 784 boolean isAppeared) { 785 Slog.i(TAG, "legacyNotifyDevicePresenceEvent() association=[" + association.toShortString() 786 + "], isAppeared=[" + isAppeared + "]"); 787 788 final int userId = association.getUserId(); 789 final String packageName = association.getPackageName(); 790 791 final CompanionServiceConnector primaryServiceConnector = 792 mCompanionAppBinder.getPrimaryServiceConnector(userId, packageName); 793 if (primaryServiceConnector == null) { 794 Slog.e(TAG, "Package is not bound."); 795 return; 796 } 797 798 if (isAppeared) { 799 primaryServiceConnector.postOnDeviceAppeared(association); 800 } else { 801 primaryServiceConnector.postOnDeviceDisappeared(association); 802 } 803 } 804 805 /** 806 * Notify the device presence event to the app. 807 */ notifyDevicePresenceEvent(int userId, String packageName, DevicePresenceEvent event)808 private void notifyDevicePresenceEvent(int userId, String packageName, 809 DevicePresenceEvent event) { 810 Slog.i(TAG, 811 "notifyCompanionDevicePresenceEvent userId=[" + userId + "], packageName=[" 812 + packageName + "], event=[" + event + "]..."); 813 814 final CompanionServiceConnector primaryServiceConnector = 815 mCompanionAppBinder.getPrimaryServiceConnector(userId, packageName); 816 817 if (primaryServiceConnector == null) { 818 Slog.e(TAG, "Package is NOT bound."); 819 return; 820 } 821 822 primaryServiceConnector.postOnDevicePresenceEvent(event); 823 } 824 825 /** 826 * Notify the self-managed device presence event to the app. 827 */ notifySelfManagedDevicePresenceEvent(int associationId, boolean isAppeared)828 public void notifySelfManagedDevicePresenceEvent(int associationId, boolean isAppeared) { 829 Slog.i(TAG, "notifySelfManagedDeviceAppeared() id=" + associationId); 830 831 AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks( 832 associationId); 833 if (!association.isSelfManaged()) { 834 throw new IllegalArgumentException("Association id=[" + associationId 835 + "] is not self-managed."); 836 } 837 // AssociationInfo class is immutable: create a new AssociationInfo object with updated 838 // timestamp. 839 association = (new AssociationInfo.Builder(association)) 840 .setLastTimeConnected(System.currentTimeMillis()) 841 .build(); 842 mAssociationStore.updateAssociation(association); 843 844 if (isAppeared) { 845 onSelfManagedDeviceConnected(associationId); 846 } else { 847 onSelfManagedDeviceDisconnected(associationId); 848 } 849 850 final String deviceProfile = association.getDeviceProfile(); 851 if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) { 852 Slog.i(TAG, "Enable hint mode for device device profile: " + deviceProfile); 853 mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, isAppeared); 854 } 855 } 856 onBinderDied(@serIdInt int userId, @NonNull String packageName, @NonNull CompanionServiceConnector serviceConnector)857 private void onBinderDied(@UserIdInt int userId, @NonNull String packageName, 858 @NonNull CompanionServiceConnector serviceConnector) { 859 860 boolean isPrimary = serviceConnector.isPrimary(); 861 Slog.i(TAG, "onBinderDied() u" + userId + "/" + packageName + " isPrimary: " + isPrimary); 862 863 // First, disable hint mode for Auto profile and mark not BOUND for primary service ONLY. 864 if (isPrimary) { 865 final List<AssociationInfo> associations = 866 mAssociationStore.getActiveAssociationsByPackage(userId, packageName); 867 868 for (AssociationInfo association : associations) { 869 final String deviceProfile = association.getDeviceProfile(); 870 if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) { 871 Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile); 872 mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false); 873 break; 874 } 875 } 876 877 mCompanionAppBinder.removePackage(userId, packageName); 878 } 879 880 // Second: schedule rebinding if needed. 881 final boolean shouldScheduleRebind = shouldScheduleRebind(userId, packageName, isPrimary); 882 883 if (shouldScheduleRebind) { 884 mCompanionAppBinder.scheduleRebinding(userId, packageName, serviceConnector); 885 } 886 } 887 888 /** 889 * Check if the system should rebind the self-managed secondary services 890 * OR non-self-managed services. 891 */ shouldScheduleRebind(int userId, String packageName, boolean isPrimary)892 private boolean shouldScheduleRebind(int userId, String packageName, boolean isPrimary) { 893 // Make sure do not schedule rebind for the case ServiceConnector still gets callback after 894 // app is uninstalled. 895 boolean stillAssociated = false; 896 // Make sure to clean up the state for all the associations 897 // that associate with this package. 898 boolean shouldScheduleRebind = false; 899 boolean shouldScheduleRebindForUuid = false; 900 final List<ObservableUuid> uuids = 901 mObservableUuidStore.getObservableUuidsForPackage(userId, packageName); 902 903 for (AssociationInfo ai : 904 mAssociationStore.getActiveAssociationsByPackage(userId, packageName)) { 905 final int associationId = ai.getId(); 906 stillAssociated = true; 907 if (ai.isSelfManaged()) { 908 // Do not rebind if primary one is died for selfManaged application. 909 if (isPrimary && isDevicePresent(associationId)) { 910 onSelfManagedDeviceReporterBinderDied(associationId); 911 shouldScheduleRebind = false; 912 } 913 // Do not rebind if both primary and secondary services are died for 914 // selfManaged application. 915 shouldScheduleRebind = mCompanionAppBinder.isCompanionApplicationBound(userId, 916 packageName); 917 } else if (ai.isNotifyOnDeviceNearby()) { 918 // Always rebind for non-selfManaged devices. 919 shouldScheduleRebind = true; 920 } 921 } 922 923 for (ObservableUuid uuid : uuids) { 924 if (isDeviceUuidPresent(uuid.getUuid())) { 925 shouldScheduleRebindForUuid = true; 926 break; 927 } 928 } 929 930 return (stillAssociated && shouldScheduleRebind) || shouldScheduleRebindForUuid; 931 } 932 933 /** 934 * Implements 935 * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)} 936 */ 937 @Override onAssociationRemoved(@onNull AssociationInfo association)938 public void onAssociationRemoved(@NonNull AssociationInfo association) { 939 final int id = association.getId(); 940 941 mConnectedBtDevices.remove(id); 942 mNearbyBleDevices.remove(id); 943 mReportedSelfManagedDevices.remove(id); 944 mSimulated.remove(id); 945 synchronized (mBtDisconnectedDevices) { 946 mBtDisconnectedDevices.remove(id); 947 mBtDisconnectedDevicesBlePresence.delete(id); 948 } 949 950 // Do NOT call mCallback.onDeviceDisappeared()! 951 // CompanionDeviceManagerService will know that the association is removed, and will do 952 // what's needed. 953 } 954 955 enforceCallerShellOrRoot()956 private static void enforceCallerShellOrRoot() { 957 final int callingUid = Binder.getCallingUid(); 958 if (callingUid == SHELL_UID || callingUid == ROOT_UID) return; 959 960 throw new SecurityException("Caller is neither Shell nor Root"); 961 } 962 963 /** 964 * The BLE scan can be only stopped if all the devices have been reported 965 * BT connected and BLE presence and are not pending to report BLE lost. 966 */ canStopBleScan()967 private boolean canStopBleScan() { 968 for (AssociationInfo ai : mAssociationStore.getActiveAssociations()) { 969 int id = ai.getId(); 970 synchronized (mBtDisconnectedDevices) { 971 if (ai.isNotifyOnDeviceNearby() && !(isBtConnected(id) 972 && isBlePresent(id) && mBtDisconnectedDevices.isEmpty())) { 973 Slog.i(TAG, "The BLE scan cannot be stopped, " 974 + "device( " + id + " ) is not yet connected " 975 + "OR the BLE is not current present Or is pending to report BLE lost"); 976 return false; 977 } 978 } 979 } 980 return true; 981 } 982 983 /** 984 * Store the positive DevicePresenceEvent in the cache if the current device is still 985 * locked. 986 * Remove the current DevicePresenceEvent if there's a negative event occurs. 987 */ onDeviceLocked(int associationId, int userId, int event, ParcelUuid uuid)988 private void onDeviceLocked(int associationId, int userId, int event, ParcelUuid uuid) { 989 switch (event) { 990 case EVENT_BLE_APPEARED, EVENT_BT_CONNECTED -> { 991 // Try to bind and notify the app after the phone is unlocked. 992 Slog.i(TAG, "Current user is not in unlocking or unlocked stage yet. " 993 + "Notify the application when the phone is unlocked"); 994 synchronized (mPendingDevicePresenceEvents) { 995 final DevicePresenceEvent devicePresenceEvent = new DevicePresenceEvent( 996 associationId, event, uuid); 997 List<DevicePresenceEvent> deviceEvents = mPendingDevicePresenceEvents.get( 998 userId, new ArrayList<>()); 999 deviceEvents.add(devicePresenceEvent); 1000 mPendingDevicePresenceEvents.put(userId, deviceEvents); 1001 } 1002 } 1003 case EVENT_BLE_DISAPPEARED -> { 1004 synchronized (mPendingDevicePresenceEvents) { 1005 List<DevicePresenceEvent> deviceEvents = mPendingDevicePresenceEvents 1006 .get(userId); 1007 if (deviceEvents != null) { 1008 deviceEvents.removeIf(deviceEvent -> 1009 deviceEvent.getEvent() == EVENT_BLE_APPEARED 1010 && Objects.equals(deviceEvent.getUuid(), uuid) 1011 && deviceEvent.getAssociationId() == associationId); 1012 } 1013 } 1014 } 1015 case EVENT_BT_DISCONNECTED -> { 1016 // Do not need to report the event since the user is not unlock the 1017 // phone so that cdm is not bind with the app yet. 1018 synchronized (mPendingDevicePresenceEvents) { 1019 List<DevicePresenceEvent> deviceEvents = mPendingDevicePresenceEvents 1020 .get(userId); 1021 if (deviceEvents != null) { 1022 deviceEvents.removeIf(deviceEvent -> 1023 deviceEvent.getEvent() == EVENT_BT_CONNECTED 1024 && Objects.equals(deviceEvent.getUuid(), uuid) 1025 && deviceEvent.getAssociationId() == associationId); 1026 } 1027 } 1028 } 1029 default -> Slog.e(TAG, "Event: " + event + "is not supported"); 1030 } 1031 } 1032 1033 /** 1034 * Send the device presence event by userID when the device is unlocked. 1035 */ sendDevicePresenceEventOnUnlocked(int userId)1036 public void sendDevicePresenceEventOnUnlocked(int userId) { 1037 final List<DevicePresenceEvent> deviceEvents = getPendingDevicePresenceEventsByUserId( 1038 userId); 1039 if (CollectionUtils.isEmpty(deviceEvents)) { 1040 return; 1041 } 1042 final List<ObservableUuid> observableUuids = 1043 mObservableUuidStore.getObservableUuidsForUser(userId); 1044 // Notify and bind the app after the phone is unlocked. 1045 for (DevicePresenceEvent deviceEvent : deviceEvents) { 1046 boolean isUuid = deviceEvent.getUuid() != null; 1047 if (isUuid) { 1048 for (ObservableUuid uuid : observableUuids) { 1049 if (uuid.getUuid().equals(deviceEvent.getUuid())) { 1050 onDevicePresenceEventByUuid(uuid, EVENT_BT_CONNECTED); 1051 } 1052 } 1053 } else { 1054 int event = deviceEvent.getEvent(); 1055 int associationId = deviceEvent.getAssociationId(); 1056 final AssociationInfo associationInfo = mAssociationStore.getAssociationById( 1057 associationId); 1058 1059 if (associationInfo == null) { 1060 return; 1061 } 1062 1063 switch (event) { 1064 case EVENT_BLE_APPEARED: 1065 onBleCompanionDeviceFound( 1066 associationInfo.getId(), associationInfo.getUserId()); 1067 break; 1068 case EVENT_BT_CONNECTED: 1069 onBluetoothCompanionDeviceConnected( 1070 associationInfo.getId(), associationInfo.getUserId()); 1071 break; 1072 default: 1073 Slog.e(TAG, "Event: " + event + "is not supported"); 1074 break; 1075 } 1076 } 1077 } 1078 1079 removePendingDevicePresenceEventsByUserId(userId); 1080 } 1081 getPendingDevicePresenceEventsByUserId(int userId)1082 private List<DevicePresenceEvent> getPendingDevicePresenceEventsByUserId(int userId) { 1083 synchronized (mPendingDevicePresenceEvents) { 1084 return mPendingDevicePresenceEvents.get(userId, new ArrayList<>()); 1085 } 1086 } 1087 removePendingDevicePresenceEventsByUserId(int userId)1088 private void removePendingDevicePresenceEventsByUserId(int userId) { 1089 synchronized (mPendingDevicePresenceEvents) { 1090 if (mPendingDevicePresenceEvents.contains(userId)) { 1091 mPendingDevicePresenceEvents.remove(userId); 1092 } 1093 } 1094 } 1095 1096 /** 1097 * Dumps system information about devices that are marked as "present". 1098 */ dump(@onNull PrintWriter out)1099 public void dump(@NonNull PrintWriter out) { 1100 out.append("Companion Device Present: "); 1101 if (mConnectedBtDevices.isEmpty() 1102 && mNearbyBleDevices.isEmpty() 1103 && mReportedSelfManagedDevices.isEmpty()) { 1104 out.append("<empty>\n"); 1105 return; 1106 } else { 1107 out.append("\n"); 1108 } 1109 1110 out.append(" Connected Bluetooth Devices: "); 1111 if (mConnectedBtDevices.isEmpty()) { 1112 out.append("<empty>\n"); 1113 } else { 1114 out.append("\n"); 1115 for (int associationId : mConnectedBtDevices) { 1116 AssociationInfo a = mAssociationStore.getAssociationById(associationId); 1117 out.append(" ").append(a.toShortString()).append('\n'); 1118 } 1119 } 1120 1121 out.append(" Nearby BLE Devices: "); 1122 if (mNearbyBleDevices.isEmpty()) { 1123 out.append("<empty>\n"); 1124 } else { 1125 out.append("\n"); 1126 for (int associationId : mNearbyBleDevices) { 1127 AssociationInfo a = mAssociationStore.getAssociationById(associationId); 1128 out.append(" ").append(a.toShortString()).append('\n'); 1129 } 1130 } 1131 1132 out.append(" Self-Reported Devices: "); 1133 if (mReportedSelfManagedDevices.isEmpty()) { 1134 out.append("<empty>\n"); 1135 } else { 1136 out.append("\n"); 1137 for (int associationId : mReportedSelfManagedDevices) { 1138 AssociationInfo a = mAssociationStore.getAssociationById(associationId); 1139 out.append(" ").append(a.toShortString()).append('\n'); 1140 } 1141 } 1142 } 1143 1144 private class SimulatedDevicePresenceSchedulerHelper extends Handler { SimulatedDevicePresenceSchedulerHelper()1145 SimulatedDevicePresenceSchedulerHelper() { 1146 super(Looper.getMainLooper()); 1147 } 1148 scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId)1149 void scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) { 1150 // First, unschedule if it was scheduled previously. 1151 if (hasMessages(/* what */ associationId)) { 1152 removeMessages(/* what */ associationId); 1153 } 1154 1155 sendEmptyMessageDelayed(/* what */ associationId, 60 * 1000 /* 60 seconds */); 1156 } 1157 unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId)1158 void unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) { 1159 removeMessages(/* what */ associationId); 1160 } 1161 1162 @Override handleMessage(@onNull Message msg)1163 public void handleMessage(@NonNull Message msg) { 1164 final int associationId = msg.what; 1165 if (mSimulated.contains(associationId)) { 1166 onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_DISAPPEARED); 1167 } 1168 } 1169 } 1170 1171 private class BleDeviceDisappearedScheduler extends Handler { BleDeviceDisappearedScheduler()1172 BleDeviceDisappearedScheduler() { 1173 super(Looper.getMainLooper()); 1174 } 1175 scheduleBleDeviceDisappeared(int associationId)1176 void scheduleBleDeviceDisappeared(int associationId) { 1177 if (hasMessages(associationId)) { 1178 removeMessages(associationId); 1179 } 1180 Slog.i(TAG, "scheduleBleDeviceDisappeared for Device: ( " + associationId + " )."); 1181 sendEmptyMessageDelayed(associationId, 10 * 1000 /* 10 seconds */); 1182 } 1183 unScheduleDeviceDisappeared(int associationId)1184 void unScheduleDeviceDisappeared(int associationId) { 1185 if (hasMessages(associationId)) { 1186 Slog.i(TAG, "unScheduleDeviceDisappeared for Device( " + associationId + " )"); 1187 synchronized (mBtDisconnectedDevices) { 1188 mBtDisconnectedDevices.remove(associationId); 1189 mBtDisconnectedDevicesBlePresence.delete(associationId); 1190 } 1191 1192 removeMessages(associationId); 1193 } 1194 } 1195 1196 @Override handleMessage(@onNull Message msg)1197 public void handleMessage(@NonNull Message msg) { 1198 final int associationId = msg.what; 1199 synchronized (mBtDisconnectedDevices) { 1200 final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get( 1201 associationId); 1202 // If a device hasn't reported after 10 seconds and is not currently present, 1203 // assume BLE is lost and trigger the onDeviceEvent callback with the 1204 // EVENT_BLE_DISAPPEARED event. 1205 if (mBtDisconnectedDevices.contains(associationId) 1206 && !isCurrentPresent) { 1207 Slog.i(TAG, "Device ( " + associationId + " ) is likely BLE out of range, " 1208 + "sending callback with event ( " + EVENT_BLE_DISAPPEARED + " )"); 1209 onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED); 1210 } 1211 1212 mBtDisconnectedDevices.remove(associationId); 1213 mBtDisconnectedDevicesBlePresence.delete(associationId); 1214 } 1215 } 1216 } 1217 } 1218