• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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.companiondevicemanager;
18 
19 import static com.android.companiondevicemanager.Utils.runOnMainThread;
20 import static com.android.internal.util.ArrayUtils.isEmpty;
21 import static com.android.internal.util.CollectionUtils.filter;
22 import static com.android.internal.util.CollectionUtils.find;
23 import static com.android.internal.util.CollectionUtils.map;
24 
25 import static java.lang.Math.max;
26 import static java.lang.Math.min;
27 import static java.util.Objects.requireNonNull;
28 
29 import android.annotation.MainThread;
30 import android.annotation.NonNull;
31 import android.annotation.Nullable;
32 import android.annotation.SuppressLint;
33 import android.app.Service;
34 import android.bluetooth.BluetoothAdapter;
35 import android.bluetooth.BluetoothDevice;
36 import android.bluetooth.BluetoothManager;
37 import android.bluetooth.BluetoothProfile;
38 import android.bluetooth.le.BluetoothLeScanner;
39 import android.bluetooth.le.ScanCallback;
40 import android.bluetooth.le.ScanFilter;
41 import android.bluetooth.le.ScanResult;
42 import android.bluetooth.le.ScanSettings;
43 import android.companion.AssociationRequest;
44 import android.companion.BluetoothDeviceFilter;
45 import android.companion.BluetoothLeDeviceFilter;
46 import android.companion.DeviceFilter;
47 import android.companion.WifiDeviceFilter;
48 import android.content.BroadcastReceiver;
49 import android.content.Context;
50 import android.content.Intent;
51 import android.content.IntentFilter;
52 import android.net.wifi.WifiManager;
53 import android.os.Handler;
54 import android.os.IBinder;
55 import android.os.Parcelable;
56 import android.os.SystemProperties;
57 import android.text.TextUtils;
58 import android.util.Slog;
59 
60 import androidx.lifecycle.LiveData;
61 import androidx.lifecycle.MutableLiveData;
62 
63 import com.android.internal.annotations.GuardedBy;
64 
65 import java.util.ArrayList;
66 import java.util.Collection;
67 import java.util.Collections;
68 import java.util.List;
69 import java.util.Objects;
70 
71 /**
72  *  A CompanionDevice service response for scanning nearby devices
73  */
74 @SuppressLint("LongLogTag")
75 public class CompanionDeviceDiscoveryService extends Service {
76     private static final String TAG = "CDM_CompanionDeviceDiscoveryService";
77 
78     private static final String SYS_PROP_DEBUG_TIMEOUT = "debug.cdm.discovery_timeout";
79     private static final long TIMEOUT_DEFAULT = 20_000L; // 20 seconds
80     private static final long TIMEOUT_MIN = 1_000L; // 1 sec
81     private static final long TIMEOUT_MAX = 60_000L; // 1 min
82 
83     private static final String ACTION_START_DISCOVERY =
84             "com.android.companiondevicemanager.action.START_DISCOVERY";
85     private static final String ACTION_STOP_DISCOVERY =
86             "com.android.companiondevicemanager.action.ACTION_STOP_DISCOVERY";
87     private static final String EXTRA_ASSOCIATION_REQUEST = "association_request";
88 
89     private static MutableLiveData<List<DeviceFilterPair<?>>> sScanResultsLiveData =
90             new MutableLiveData<>(Collections.emptyList());
91     private static MutableLiveData<DiscoveryState> sStateLiveData =
92             new MutableLiveData<>(DiscoveryState.NOT_STARTED);
93     private static final Object LOCK = new Object();
94     @GuardedBy("LOCK")
95     private static boolean sDiscoveryStarted = false;
96 
97     private BluetoothManager mBtManager;
98     private BluetoothAdapter mBtAdapter;
99     private WifiManager mWifiManager;
100     private BluetoothLeScanner mBleScanner;
101 
102     private ScanCallback mBleScanCallback;
103     private BluetoothBroadcastReceiver mBtReceiver;
104     private WifiBroadcastReceiver mWifiReceiver;
105 
106     private final List<DeviceFilterPair<?>> mDevicesFound = new ArrayList<>();
107 
108     private final Runnable mTimeoutRunnable = this::timeout;
109 
110     private boolean mStopAfterFirstMatch;
111 
112     /**
113      * A state enum for devices' discovery.
114      */
115     enum DiscoveryState {
116         NOT_STARTED,
117         IN_PROGRESS,
118         FINISHED_STOPPED,
119         FINISHED_TIMEOUT
120     }
121 
startForRequest( @onNull Context context, @NonNull AssociationRequest associationRequest)122     static boolean startForRequest(
123             @NonNull Context context, @NonNull AssociationRequest associationRequest) {
124         synchronized (LOCK) {
125             if (sDiscoveryStarted) {
126                 Slog.e(TAG, "Discovery is already started. Ignoring this request...");
127                 return false;
128             }
129         }
130         requireNonNull(associationRequest);
131         final Intent intent = new Intent(context, CompanionDeviceDiscoveryService.class);
132         intent.setAction(ACTION_START_DISCOVERY);
133         intent.putExtra(EXTRA_ASSOCIATION_REQUEST, associationRequest);
134 
135         context.startService(intent);
136 
137         return true;
138     }
139 
stop(@onNull Context context)140     static void stop(@NonNull Context context) {
141         final Intent intent = new Intent(context, CompanionDeviceDiscoveryService.class);
142         intent.setAction(ACTION_STOP_DISCOVERY);
143         context.startService(intent);
144     }
145 
getScanResult()146     static LiveData<List<DeviceFilterPair<?>>> getScanResult() {
147         return sScanResultsLiveData;
148     }
149 
getDiscoveryState()150     static LiveData<DiscoveryState> getDiscoveryState() {
151         return sStateLiveData;
152     }
153 
154     @Override
onCreate()155     public void onCreate() {
156         super.onCreate();
157 
158         mBtManager = getSystemService(BluetoothManager.class);
159         mBtAdapter = mBtManager.getAdapter();
160         mBleScanner = mBtAdapter.getBluetoothLeScanner();
161         mWifiManager = getSystemService(WifiManager.class);
162     }
163 
164     @Override
onStartCommand(Intent intent, int flags, int startId)165     public int onStartCommand(Intent intent, int flags, int startId) {
166         final String action = intent.getAction();
167 
168         switch (action) {
169             case ACTION_START_DISCOVERY:
170                 final AssociationRequest request =
171                         intent.getParcelableExtra(EXTRA_ASSOCIATION_REQUEST);
172                 startDiscovery(request);
173                 break;
174 
175             case ACTION_STOP_DISCOVERY:
176                 stopDiscoveryAndFinish(/* timeout */ false);
177                 break;
178         }
179         return START_NOT_STICKY;
180     }
181 
182     @MainThread
startDiscovery(@onNull AssociationRequest request)183     private void startDiscovery(@NonNull AssociationRequest request) {
184         Slog.d(TAG, "startDiscovery() request=" + request);
185         requireNonNull(request);
186 
187         synchronized (LOCK) {
188             if (sDiscoveryStarted) {
189                 Slog.e(TAG, "Discovery is already started. Returning...");
190                 return;
191             }
192             sDiscoveryStarted = true;
193         }
194         mStopAfterFirstMatch = request.isSingleDevice();
195         sScanResultsLiveData.setValue(Collections.emptyList());
196         sStateLiveData.setValue(DiscoveryState.IN_PROGRESS);
197 
198         final List<DeviceFilter<?>> allFilters = request.getDeviceFilters();
199         final List<BluetoothDeviceFilter> btFilters =
200                 filter(allFilters, BluetoothDeviceFilter.class);
201         final List<BluetoothLeDeviceFilter> bleFilters =
202                 filter(allFilters, BluetoothLeDeviceFilter.class);
203         final List<WifiDeviceFilter> wifiFilters = filter(allFilters, WifiDeviceFilter.class);
204 
205         // No need to startDiscovery if the device is already bound or connected for
206         // singleDevice dialog.
207         if (checkBoundDevicesIfNeeded(request, btFilters)) {
208             stopSelf();
209             return;
210         }
211 
212         // If no filters are specified: look for everything.
213         final boolean forceStartScanningAll = isEmpty(allFilters);
214         // Start BT scanning (if needed)
215         mBtReceiver = startBtScanningIfNeeded(btFilters, forceStartScanningAll);
216         // Start Wi-Fi scanning (if needed)
217         mWifiReceiver = startWifiScanningIfNeeded(wifiFilters, forceStartScanningAll);
218         // Start BLE scanning (if needed)
219         mBleScanCallback = startBleScanningIfNeeded(bleFilters, forceStartScanningAll);
220 
221         scheduleTimeout();
222     }
223 
224     @MainThread
stopDiscoveryAndFinish(boolean timeout)225     private void stopDiscoveryAndFinish(boolean timeout) {
226         Slog.d(TAG, "stopDiscoveryAndFinish(" + timeout + ")");
227 
228         synchronized (LOCK) {
229             if (!sDiscoveryStarted) {
230                 stopSelf();
231                 return;
232             }
233         }
234 
235         // Stop BT discovery.
236         if (mBtReceiver != null) {
237             // Cancel discovery.
238             mBtAdapter.cancelDiscovery();
239             // Unregister receiver.
240             unregisterReceiver(mBtReceiver);
241             mBtReceiver = null;
242         }
243 
244         // Stop Wi-Fi scanning.
245         if (mWifiReceiver != null) {
246             // TODO: need to stop scan?
247             // Unregister receiver.
248             unregisterReceiver(mWifiReceiver);
249             mWifiReceiver = null;
250         }
251 
252         // Stop BLE scanning.
253         if (mBleScanCallback != null) {
254             mBleScanner.stopScan(mBleScanCallback);
255         }
256 
257         Handler.getMain().removeCallbacks(mTimeoutRunnable);
258 
259         if (timeout) {
260             sStateLiveData.setValue(DiscoveryState.FINISHED_TIMEOUT);
261         } else {
262             sStateLiveData.setValue(DiscoveryState.FINISHED_STOPPED);
263         }
264 
265         synchronized (LOCK) {
266             sDiscoveryStarted = false;
267         }
268 
269         // "Finish".
270         stopSelf();
271     }
272 
checkBoundDevicesIfNeeded(@onNull AssociationRequest request, @NonNull List<BluetoothDeviceFilter> btFilters)273     private boolean checkBoundDevicesIfNeeded(@NonNull AssociationRequest request,
274             @NonNull List<BluetoothDeviceFilter> btFilters) {
275         // If filtering to get single device by mac address, also search in the set of already
276         // bonded devices to allow linking those directly
277         if (btFilters.isEmpty() || !request.isSingleDevice()) return false;
278 
279         final BluetoothDeviceFilter singleMacAddressFilter =
280                 find(btFilters, filter -> !TextUtils.isEmpty(filter.getAddress()));
281 
282         if (singleMacAddressFilter == null) return false;
283 
284         return findAndReportMatches(mBtAdapter.getBondedDevices(), btFilters)
285                 || findAndReportMatches(mBtManager.getConnectedDevices(
286                         BluetoothProfile.GATT), btFilters)
287                 || findAndReportMatches(mBtManager.getConnectedDevices(
288                         BluetoothProfile.GATT_SERVER), btFilters);
289     }
290 
findAndReportMatches(@ullable Collection<BluetoothDevice> devices, @NonNull List<BluetoothDeviceFilter> filters)291     private boolean findAndReportMatches(@Nullable Collection<BluetoothDevice> devices,
292             @NonNull List<BluetoothDeviceFilter> filters) {
293         if (devices == null) return false;
294 
295         for (BluetoothDevice device : devices) {
296             final DeviceFilterPair<BluetoothDevice> match = findMatch(device, filters);
297             if (match != null) {
298                 onDeviceFound(match);
299                 return true;
300             }
301         }
302 
303         return false;
304     }
305 
startBtScanningIfNeeded( List<BluetoothDeviceFilter> filters, boolean force)306     private BluetoothBroadcastReceiver startBtScanningIfNeeded(
307             List<BluetoothDeviceFilter> filters, boolean force) {
308         if (isEmpty(filters) && !force) return null;
309         Slog.d(TAG, "registerReceiver(BluetoothDevice.ACTION_FOUND)");
310 
311         final BluetoothBroadcastReceiver receiver = new BluetoothBroadcastReceiver(filters);
312         final IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
313         registerReceiver(receiver, intentFilter);
314 
315         mBtAdapter.startDiscovery();
316 
317         return receiver;
318     }
319 
startWifiScanningIfNeeded( List<WifiDeviceFilter> filters, boolean force)320     private WifiBroadcastReceiver startWifiScanningIfNeeded(
321             List<WifiDeviceFilter> filters, boolean force) {
322         if (isEmpty(filters) && !force) return null;
323         Slog.d(TAG, "registerReceiver(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)");
324 
325         final WifiBroadcastReceiver receiver = new WifiBroadcastReceiver(filters);
326 
327         final IntentFilter intentFilter = new IntentFilter(
328                 WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
329         registerReceiver(receiver, intentFilter);
330 
331         mWifiManager.startScan();
332 
333         return receiver;
334     }
335 
startBleScanningIfNeeded( List<BluetoothLeDeviceFilter> filters, boolean force)336     private ScanCallback startBleScanningIfNeeded(
337             List<BluetoothLeDeviceFilter> filters, boolean force) {
338         if (isEmpty(filters) && !force) return null;
339         Slog.d(TAG, "BLEScanner.startScan");
340 
341         if (mBleScanner == null) {
342             Slog.w(TAG, "BLE Scanner is not available.");
343             return null;
344         }
345 
346         final BLEScanCallback callback = new BLEScanCallback(filters);
347 
348         final List<ScanFilter> scanFilters = map(
349                 filters, BluetoothLeDeviceFilter::getScanFilter);
350         final ScanSettings scanSettings = new ScanSettings.Builder()
351                 .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
352                 .build();
353         mBleScanner.startScan(scanFilters, scanSettings, callback);
354 
355         return callback;
356     }
357 
onDeviceFound(@onNull DeviceFilterPair<?> device)358     private void onDeviceFound(@NonNull DeviceFilterPair<?> device) {
359         runOnMainThread(() -> {
360             synchronized (LOCK) {
361                 if (!sDiscoveryStarted) return;
362             }
363             if (mDevicesFound.contains(device)) {
364                 // TODO: update the device instead of ignoring (new found device may contain
365                 //  additional/updated info, eg. name of the device).
366                 return;
367             }
368             Slog.i(TAG, "onDeviceFound() " + device.toShortString() + " - New device.");
369 
370             // First: make change.
371             mDevicesFound.add(device);
372             // Then: notify observers.
373             sScanResultsLiveData.setValue(mDevicesFound);
374             // Stop discovery when there's one device found for singleDevice.
375             if (mStopAfterFirstMatch) {
376                 stopDiscoveryAndFinish(/* timeout */ false);
377             }
378         });
379     }
380 
onDeviceLost(@onNull DeviceFilterPair<?> device)381     private void onDeviceLost(@NonNull DeviceFilterPair<?> device) {
382         runOnMainThread(() -> {
383             Slog.i(TAG, "onDeviceLost(), device=" + device.toShortString());
384 
385             // First: make change.
386             mDevicesFound.remove(device);
387             // Then: notify observers.
388             sScanResultsLiveData.setValue(mDevicesFound);
389         });
390     }
391 
scheduleTimeout()392     private void scheduleTimeout() {
393         long timeout = SystemProperties.getLong(SYS_PROP_DEBUG_TIMEOUT, -1);
394         if (timeout <= 0) {
395             // 0 or negative values indicate that the sysprop was never set or should be ignored.
396             timeout = TIMEOUT_DEFAULT;
397         } else {
398             timeout = min(timeout, TIMEOUT_MAX); // should be <= 1 min (TIMEOUT_MAX)
399             timeout = max(timeout, TIMEOUT_MIN); // should be >= 1 sec (TIMEOUT_MIN)
400         }
401 
402         Handler.getMain().postDelayed(mTimeoutRunnable, timeout);
403     }
404 
timeout()405     private void timeout() {
406         stopDiscoveryAndFinish(/* timeout */ true);
407     }
408 
409     @Override
onBind(Intent intent)410     public IBinder onBind(Intent intent) {
411         return null;
412     }
413 
414     private class BLEScanCallback extends ScanCallback {
415         final List<BluetoothLeDeviceFilter> mFilters;
416 
BLEScanCallback(List<BluetoothLeDeviceFilter> filters)417         BLEScanCallback(List<BluetoothLeDeviceFilter> filters) {
418             mFilters = filters;
419         }
420 
421         @Override
onScanResult(int callbackType, ScanResult result)422         public void onScanResult(int callbackType, ScanResult result) {
423             final DeviceFilterPair<ScanResult> match = findMatch(result, mFilters);
424             if (match == null) return;
425 
426             if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) {
427                 onDeviceLost(match);
428             } else {
429                 // TODO: check this logic.
430                 onDeviceFound(match);
431             }
432         }
433     }
434 
435     private class BluetoothBroadcastReceiver extends BroadcastReceiver {
436         final List<BluetoothDeviceFilter> mFilters;
437 
BluetoothBroadcastReceiver(List<BluetoothDeviceFilter> filters)438         BluetoothBroadcastReceiver(List<BluetoothDeviceFilter> filters) {
439             this.mFilters = filters;
440         }
441 
442         @Override
onReceive(Context context, Intent intent)443         public void onReceive(Context context, Intent intent) {
444             final String action = intent.getAction();
445             final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
446 
447             if (action == null) return;
448 
449             final DeviceFilterPair<BluetoothDevice> match = findMatch(device, mFilters);
450             if (match == null) return;
451 
452             if (action.equals(BluetoothDevice.ACTION_FOUND)) {
453                 onDeviceFound(match);
454             } else {
455                 // TODO: check this logic.
456                 onDeviceLost(match);
457             }
458         }
459     }
460 
461     private class WifiBroadcastReceiver extends BroadcastReceiver {
462         final List<WifiDeviceFilter> mFilters;
463 
WifiBroadcastReceiver(List<WifiDeviceFilter> filters)464         private WifiBroadcastReceiver(List<WifiDeviceFilter> filters) {
465             this.mFilters = filters;
466         }
467 
468         @Override
onReceive(Context context, Intent intent)469         public void onReceive(Context context, Intent intent) {
470             if (!Objects.equals(intent.getAction(), WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
471                 return;
472             }
473 
474             final List<android.net.wifi.ScanResult> scanResults = mWifiManager.getScanResults();
475 
476             for (int i = 0; i < scanResults.size(); i++) {
477                 final android.net.wifi.ScanResult scanResult = scanResults.get(i);
478                 final DeviceFilterPair<?> match = findMatch(scanResult, mFilters);
479                 if (match != null) {
480                     onDeviceFound(match);
481                 }
482             }
483         }
484     }
485 
486     /**
487      * {@code (device, null)} if the filters list is empty or null
488      * {@code null} if none of the provided filters match the device
489      * {@code (device, filter)} where filter is among the list of filters and matches the device
490      */
491     @Nullable
findMatch( T dev, @Nullable List<? extends DeviceFilter<T>> filters)492     public static <T extends Parcelable> DeviceFilterPair<T> findMatch(
493             T dev, @Nullable List<? extends DeviceFilter<T>> filters) {
494         if (isEmpty(filters)) return new DeviceFilterPair<>(dev, null);
495         final DeviceFilter<T> matchingFilter = find(filters, f -> f.matches(dev));
496 
497         DeviceFilterPair<T> result = matchingFilter != null
498                 ? new DeviceFilterPair<>(dev, matchingFilter) : null;
499 
500         return result;
501     }
502 }
503