• Home
  • 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.presence;
18 
19 import static android.os.Process.ROOT_UID;
20 import static android.os.Process.SHELL_UID;
21 
22 import android.annotation.NonNull;
23 import android.annotation.SuppressLint;
24 import android.annotation.TestApi;
25 import android.bluetooth.BluetoothAdapter;
26 import android.companion.AssociationInfo;
27 import android.content.Context;
28 import android.os.Binder;
29 import android.os.Handler;
30 import android.os.Looper;
31 import android.os.Message;
32 import android.util.Log;
33 
34 import com.android.server.companion.AssociationStore;
35 
36 import java.io.PrintWriter;
37 import java.util.HashSet;
38 import java.util.Set;
39 
40 /**
41  * Class responsible for monitoring companion devices' "presence" status (i.e.
42  * connected/disconnected for Bluetooth devices; nearby or not for BLE devices).
43  *
44  * <p>
45  * Should only be used by
46  * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
47  * to which it provides the following API:
48  * <ul>
49  * <li> {@link #onSelfManagedDeviceConnected(int)}
50  * <li> {@link #onSelfManagedDeviceDisconnected(int)}
51  * <li> {@link #isDevicePresent(int)}
52  * <li> {@link Callback#onDeviceAppeared(int) Callback.onDeviceAppeared(int)}
53  * <li> {@link Callback#onDeviceDisappeared(int) Callback.onDeviceDisappeared(int)}
54  * </ul>
55  */
56 @SuppressLint("LongLogTag")
57 public class CompanionDevicePresenceMonitor implements AssociationStore.OnChangeListener,
58         BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback {
59     static final boolean DEBUG = false;
60     private static final String TAG = "CompanionDevice_PresenceMonitor";
61 
62     /** Callback for notifying about changes to status of companion devices. */
63     public interface Callback {
64         /** Invoked when companion device is found nearby or connects. */
onDeviceAppeared(int associationId)65         void onDeviceAppeared(int associationId);
66 
67         /** Invoked when a companion device no longer seen nearby or disconnects. */
onDeviceDisappeared(int associationId)68         void onDeviceDisappeared(int associationId);
69     }
70 
71     private final @NonNull AssociationStore mAssociationStore;
72     private final @NonNull Callback mCallback;
73     private final @NonNull BluetoothCompanionDeviceConnectionListener mBtConnectionListener;
74     private final @NonNull BleCompanionDeviceScanner mBleScanner;
75 
76     // NOTE: Same association may appear in more than one of the following sets at the same time.
77     // (E.g. self-managed devices that have MAC addresses, could be reported as present by their
78     // companion applications, while at the same be connected via BT, or detected nearby by BLE
79     // scanner)
80     private final @NonNull Set<Integer> mConnectedBtDevices = new HashSet<>();
81     private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>();
82     private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
83 
84     // Tracking "simulated" presence. Used for debugging and testing only.
85     private final @NonNull Set<Integer> mSimulated = new HashSet<>();
86     private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper =
87             new SimulatedDevicePresenceSchedulerHelper();
88 
CompanionDevicePresenceMonitor(@onNull AssociationStore associationStore, @NonNull Callback callback)89     public CompanionDevicePresenceMonitor(@NonNull AssociationStore associationStore,
90             @NonNull Callback callback) {
91         mAssociationStore = associationStore;
92         mCallback = callback;
93 
94         mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(associationStore,
95                 /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
96         mBleScanner = new BleCompanionDeviceScanner(associationStore,
97                 /* BleCompanionDeviceScanner.Callback */ this);
98     }
99 
100     /** Initialize {@link CompanionDevicePresenceMonitor} */
init(Context context)101     public void init(Context context) {
102         if (DEBUG) Log.i(TAG, "init()");
103 
104         final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
105         if (btAdapter != null) {
106             mBtConnectionListener.init(btAdapter);
107             mBleScanner.init(context, btAdapter);
108         } else {
109             Log.w(TAG, "BluetoothAdapter is NOT available.");
110         }
111 
112         mAssociationStore.registerListener(this);
113     }
114 
115     /**
116      * @return whether the associated companion devices is present. I.e. device is nearby (for BLE);
117      *         or devices is connected (for Bluetooth); or reported (by the application) to be
118      *         nearby (for "self-managed" associations).
119      */
isDevicePresent(int associationId)120     public boolean isDevicePresent(int associationId) {
121         return mReportedSelfManagedDevices.contains(associationId)
122                 || mConnectedBtDevices.contains(associationId)
123                 || mNearbyBleDevices.contains(associationId)
124                 || mSimulated.contains(associationId);
125     }
126 
127     /**
128      * Marks a "self-managed" device as connected.
129      *
130      * <p>
131      * Must ONLY be invoked by the
132      * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
133      * when an application invokes
134      * {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int) notifyDeviceAppeared()}
135      */
onSelfManagedDeviceConnected(int associationId)136     public void onSelfManagedDeviceConnected(int associationId) {
137         onDevicePresent(mReportedSelfManagedDevices, associationId, "application-reported");
138     }
139 
140     /**
141      * Marks a "self-managed" device as disconnected.
142      *
143      * <p>
144      * Must ONLY be invoked by the
145      * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
146      * when an application invokes
147      * {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int) notifyDeviceDisappeared()}
148      */
onSelfManagedDeviceDisconnected(int associationId)149     public void onSelfManagedDeviceDisconnected(int associationId) {
150         onDeviceGone(mReportedSelfManagedDevices, associationId, "application-reported");
151     }
152 
153     /**
154      * Marks a "self-managed" device as disconnected when binderDied.
155      */
onSelfManagedDeviceReporterBinderDied(int associationId)156     public void onSelfManagedDeviceReporterBinderDied(int associationId) {
157         onDeviceGone(mReportedSelfManagedDevices, associationId, "application-reported");
158     }
159 
160     @Override
onBluetoothCompanionDeviceConnected(int associationId)161     public void onBluetoothCompanionDeviceConnected(int associationId) {
162         onDevicePresent(mConnectedBtDevices, associationId, /* sourceLoggingTag */ "bt");
163     }
164 
165     @Override
onBluetoothCompanionDeviceDisconnected(int associationId)166     public void onBluetoothCompanionDeviceDisconnected(int associationId) {
167         onDeviceGone(mConnectedBtDevices, associationId, /* sourceLoggingTag */ "bt");
168     }
169 
170     @Override
onBleCompanionDeviceFound(int associationId)171     public void onBleCompanionDeviceFound(int associationId) {
172         onDevicePresent(mNearbyBleDevices, associationId, /* sourceLoggingTag */ "ble");
173     }
174 
175     @Override
onBleCompanionDeviceLost(int associationId)176     public void onBleCompanionDeviceLost(int associationId) {
177         onDeviceGone(mNearbyBleDevices, associationId, /* sourceLoggingTag */ "ble");
178     }
179 
180     /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
181     @TestApi
simulateDeviceAppeared(int associationId)182     public void simulateDeviceAppeared(int associationId) {
183         // IMPORTANT: this API should only be invoked via the
184         // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
185         // make this call are SHELL and ROOT.
186         // No other caller (including SYSTEM!) should be allowed.
187         enforceCallerShellOrRoot();
188         // Make sure the association exists.
189         enforceAssociationExists(associationId);
190 
191         onDevicePresent(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
192 
193         mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
194     }
195 
196     /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
197     @TestApi
simulateDeviceDisappeared(int associationId)198     public void simulateDeviceDisappeared(int associationId) {
199         // IMPORTANT: this API should only be invoked via the
200         // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
201         // make this call are SHELL and ROOT.
202         // No other caller (including SYSTEM!) should be allowed.
203         enforceCallerShellOrRoot();
204         // Make sure the association exists.
205         enforceAssociationExists(associationId);
206 
207         mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
208 
209         onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
210     }
211 
enforceAssociationExists(int associationId)212     private void enforceAssociationExists(int associationId) {
213         if (mAssociationStore.getAssociationById(associationId) == null) {
214             throw new IllegalArgumentException(
215                     "Association with id " + associationId + " does not exist.");
216         }
217     }
218 
onDevicePresent(@onNull Set<Integer> presentDevicesForSource, int newDeviceAssociationId, @NonNull String sourceLoggingTag)219     private void onDevicePresent(@NonNull Set<Integer> presentDevicesForSource,
220             int newDeviceAssociationId, @NonNull String sourceLoggingTag) {
221         if (DEBUG) {
222             Log.i(TAG, "onDevice_Present() id=" + newDeviceAssociationId
223                     + ", source=" + sourceLoggingTag);
224             Log.d(TAG, "  > association="
225                     + mAssociationStore.getAssociationById(newDeviceAssociationId));
226         }
227 
228         final boolean alreadyPresent = isDevicePresent(newDeviceAssociationId);
229         if (DEBUG && alreadyPresent) Log.i(TAG, "Device is already present.");
230 
231         final boolean added = presentDevicesForSource.add(newDeviceAssociationId);
232         if (DEBUG && !added) {
233             Log.w(TAG, "Association with id " + newDeviceAssociationId + " is ALREADY reported as "
234                     + "present by this source (" + sourceLoggingTag + ")");
235         }
236 
237         if (alreadyPresent) return;
238 
239         mCallback.onDeviceAppeared(newDeviceAssociationId);
240     }
241 
onDeviceGone(@onNull Set<Integer> presentDevicesForSource, int goneDeviceAssociationId, @NonNull String sourceLoggingTag)242     private void onDeviceGone(@NonNull Set<Integer> presentDevicesForSource,
243             int goneDeviceAssociationId, @NonNull String sourceLoggingTag) {
244         if (DEBUG) {
245             Log.i(TAG, "onDevice_Gone() id=" + goneDeviceAssociationId
246                     + ", source=" + sourceLoggingTag);
247             Log.d(TAG, "  > association="
248                     + mAssociationStore.getAssociationById(goneDeviceAssociationId));
249         }
250 
251         final boolean removed = presentDevicesForSource.remove(goneDeviceAssociationId);
252         if (!removed) {
253             if (DEBUG) {
254                 Log.w(TAG, "Association with id " + goneDeviceAssociationId + " was NOT reported "
255                         + "as present by this source (" + sourceLoggingTag + ")");
256             }
257             return;
258         }
259 
260         final boolean stillPresent = isDevicePresent(goneDeviceAssociationId);
261         if (stillPresent) {
262             if (DEBUG) Log.i(TAG, "  Device is still present.");
263             return;
264         }
265 
266         mCallback.onDeviceDisappeared(goneDeviceAssociationId);
267     }
268 
269     /**
270      * Implements
271      * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)}
272      */
273     @Override
onAssociationRemoved(@onNull AssociationInfo association)274     public void onAssociationRemoved(@NonNull AssociationInfo association) {
275         final int id = association.getId();
276         if (DEBUG) {
277             Log.i(TAG, "onAssociationRemoved() id=" + id);
278             Log.d(TAG, "  > association=" + association);
279         }
280 
281         mConnectedBtDevices.remove(id);
282         mNearbyBleDevices.remove(id);
283         mReportedSelfManagedDevices.remove(id);
284         mSimulated.remove(id);
285 
286         // Do NOT call mCallback.onDeviceDisappeared()!
287         // CompanionDeviceManagerService will know that the association is removed, and will do
288         // what's needed.
289     }
290 
enforceCallerShellOrRoot()291     private static void enforceCallerShellOrRoot() {
292         final int callingUid = Binder.getCallingUid();
293         if (callingUid == SHELL_UID || callingUid == ROOT_UID) return;
294 
295         throw new SecurityException("Caller is neither Shell nor Root");
296     }
297 
298     /**
299      * Dumps system information about devices that are marked as "present".
300      */
dump(@onNull PrintWriter out)301     public void dump(@NonNull PrintWriter out) {
302         out.append("Companion Device Present: ");
303         if (mConnectedBtDevices.isEmpty()
304                 && mNearbyBleDevices.isEmpty()
305                 && mReportedSelfManagedDevices.isEmpty()) {
306             out.append("<empty>\n");
307             return;
308         } else {
309             out.append("\n");
310         }
311 
312         out.append("  Connected Bluetooth Devices: ");
313         if (mConnectedBtDevices.isEmpty()) {
314             out.append("<empty>\n");
315         } else {
316             out.append("\n");
317             for (int associationId : mConnectedBtDevices) {
318                 AssociationInfo a = mAssociationStore.getAssociationById(associationId);
319                 out.append("    ").append(a.toShortString()).append('\n');
320             }
321         }
322 
323         out.append("  Nearby BLE Devices: ");
324         if (mNearbyBleDevices.isEmpty()) {
325             out.append("<empty>\n");
326         } else {
327             out.append("\n");
328             for (int associationId : mNearbyBleDevices) {
329                 AssociationInfo a = mAssociationStore.getAssociationById(associationId);
330                 out.append("    ").append(a.toShortString()).append('\n');
331             }
332         }
333 
334         out.append("  Self-Reported Devices: ");
335         if (mReportedSelfManagedDevices.isEmpty()) {
336             out.append("<empty>\n");
337         } else {
338             out.append("\n");
339             for (int associationId : mReportedSelfManagedDevices) {
340                 AssociationInfo a = mAssociationStore.getAssociationById(associationId);
341                 out.append("    ").append(a.toShortString()).append('\n');
342             }
343         }
344     }
345 
346     private class SimulatedDevicePresenceSchedulerHelper extends Handler {
SimulatedDevicePresenceSchedulerHelper()347         SimulatedDevicePresenceSchedulerHelper() {
348             super(Looper.getMainLooper());
349         }
350 
scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId)351         void scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
352             // First, unschedule if it was scheduled previously.
353             if (hasMessages(/* what */ associationId)) {
354                 removeMessages(/* what */ associationId);
355             }
356 
357             sendEmptyMessageDelayed(/* what */ associationId, 60 * 1000 /* 60 seconds */);
358         }
359 
unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId)360         void unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
361             removeMessages(/* what */ associationId);
362         }
363 
364         @Override
handleMessage(@onNull Message msg)365         public void handleMessage(@NonNull Message msg) {
366             final int associationId = msg.what;
367             if (mSimulated.contains(associationId)) {
368                 onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
369             }
370         }
371     }
372 }
373