• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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