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