• 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 android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameInternal;
20 import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
21 
22 import static com.android.internal.util.ArrayUtils.isEmpty;
23 import static com.android.internal.util.CollectionUtils.emptyIfNull;
24 import static com.android.internal.util.CollectionUtils.size;
25 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
26 
27 import android.annotation.MainThread;
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.app.PendingIntent;
31 import android.app.Service;
32 import android.bluetooth.BluetoothAdapter;
33 import android.bluetooth.BluetoothDevice;
34 import android.bluetooth.BluetoothManager;
35 import android.bluetooth.BluetoothProfile;
36 import android.bluetooth.le.BluetoothLeScanner;
37 import android.bluetooth.le.ScanCallback;
38 import android.bluetooth.le.ScanFilter;
39 import android.bluetooth.le.ScanResult;
40 import android.bluetooth.le.ScanSettings;
41 import android.companion.Association;
42 import android.companion.AssociationRequest;
43 import android.companion.BluetoothDeviceFilter;
44 import android.companion.BluetoothLeDeviceFilter;
45 import android.companion.DeviceFilter;
46 import android.companion.ICompanionDeviceDiscoveryService;
47 import android.companion.IFindDeviceCallback;
48 import android.companion.WifiDeviceFilter;
49 import android.content.BroadcastReceiver;
50 import android.content.Context;
51 import android.content.Intent;
52 import android.content.IntentFilter;
53 import android.net.wifi.WifiManager;
54 import android.os.Handler;
55 import android.os.IBinder;
56 import android.os.Parcelable;
57 import android.os.RemoteException;
58 import android.text.TextUtils;
59 import android.util.Log;
60 
61 import com.android.internal.infra.AndroidFuture;
62 import com.android.internal.util.ArrayUtils;
63 import com.android.internal.util.CollectionUtils;
64 import com.android.internal.util.Preconditions;
65 
66 import java.util.ArrayList;
67 import java.util.List;
68 import java.util.Objects;
69 
70 public class CompanionDeviceDiscoveryService extends Service {
71 
72     private static final boolean DEBUG = false;
73     private static final String LOG_TAG = CompanionDeviceDiscoveryService.class.getSimpleName();
74 
75     private static final long SCAN_TIMEOUT = 20000;
76 
77     static CompanionDeviceDiscoveryService sInstance;
78 
79     private BluetoothManager mBluetoothManager;
80     private BluetoothAdapter mBluetoothAdapter;
81     private WifiManager mWifiManager;
82     @Nullable private BluetoothLeScanner mBLEScanner;
83     private ScanSettings mDefaultScanSettings = new ScanSettings.Builder()
84             .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
85             .build();
86 
87     private List<DeviceFilter<?>> mFilters;
88     private List<BluetoothLeDeviceFilter> mBLEFilters;
89     private List<BluetoothDeviceFilter> mBluetoothFilters;
90     private List<WifiDeviceFilter> mWifiFilters;
91     private List<ScanFilter> mBLEScanFilters;
92 
93     AssociationRequest mRequest;
94     List<DeviceFilterPair> mDevicesFound;
95     DeviceFilterPair mSelectedDevice;
96     IFindDeviceCallback mFindCallback;
97 
98     AndroidFuture<Association> mServiceCallback;
99     boolean mIsScanning = false;
100     @Nullable
101     CompanionDeviceActivity mActivity = null;
102 
103     private final ICompanionDeviceDiscoveryService mBinder =
104             new ICompanionDeviceDiscoveryService.Stub() {
105         @Override
106         public void startDiscovery(AssociationRequest request,
107                 String callingPackage,
108                 IFindDeviceCallback findCallback,
109                 AndroidFuture serviceCallback) {
110             Log.i(LOG_TAG,
111                     "startDiscovery() called with: filter = [" + request
112                             + "], findCallback = [" + findCallback + "]"
113                             + "], serviceCallback = [" + serviceCallback + "]");
114             mFindCallback = findCallback;
115             mServiceCallback = serviceCallback;
116             Handler.getMain().sendMessage(obtainMessage(
117                     CompanionDeviceDiscoveryService::startDiscovery,
118                     CompanionDeviceDiscoveryService.this, request));
119         }
120     };
121 
122     private ScanCallback mBLEScanCallback;
123     private BluetoothBroadcastReceiver mBluetoothBroadcastReceiver;
124     private WifiBroadcastReceiver mWifiBroadcastReceiver;
125 
126     @Override
onBind(Intent intent)127     public IBinder onBind(Intent intent) {
128         Log.i(LOG_TAG, "onBind(" + intent + ")");
129         return mBinder.asBinder();
130     }
131 
132     @Override
onCreate()133     public void onCreate() {
134         super.onCreate();
135 
136         Log.i(LOG_TAG, "onCreate()");
137 
138         mBluetoothManager = getSystemService(BluetoothManager.class);
139         mBluetoothAdapter = mBluetoothManager.getAdapter();
140         mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
141         mWifiManager = getSystemService(WifiManager.class);
142 
143         mDevicesFound = new ArrayList<>();
144 
145         sInstance = this;
146     }
147 
148     @MainThread
startDiscovery(AssociationRequest request)149     private void startDiscovery(AssociationRequest request) {
150         if (!request.equals(mRequest)) {
151             mRequest = request;
152 
153             mFilters = request.getDeviceFilters();
154             mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class);
155             mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class);
156             mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLeDeviceFilter.class);
157             mBLEScanFilters
158                     = CollectionUtils.map(mBLEFilters, BluetoothLeDeviceFilter::getScanFilter);
159 
160             reset();
161         } else {
162             Log.i(LOG_TAG, "startDiscovery: duplicate request: " + request);
163         }
164 
165         if (!ArrayUtils.isEmpty(mDevicesFound)) {
166             onReadyToShowUI();
167         }
168 
169         // If filtering to get single device by mac address, also search in the set of already
170         // bonded devices to allow linking those directly
171         String singleMacAddressFilter = null;
172         if (mRequest.isSingleDevice()) {
173             int numFilters = size(mBluetoothFilters);
174             for (int i = 0; i < numFilters; i++) {
175                 BluetoothDeviceFilter filter = mBluetoothFilters.get(i);
176                 if (!TextUtils.isEmpty(filter.getAddress())) {
177                     singleMacAddressFilter = filter.getAddress();
178                     break;
179                 }
180             }
181         }
182         if (singleMacAddressFilter != null) {
183             for (BluetoothDevice dev : emptyIfNull(mBluetoothAdapter.getBondedDevices())) {
184                 onDeviceFound(DeviceFilterPair.findMatch(dev, mBluetoothFilters));
185             }
186             for (BluetoothDevice dev : emptyIfNull(
187                     mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT))) {
188                 onDeviceFound(DeviceFilterPair.findMatch(dev, mBluetoothFilters));
189             }
190             for (BluetoothDevice dev : emptyIfNull(
191                     mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT_SERVER))) {
192                 onDeviceFound(DeviceFilterPair.findMatch(dev, mBluetoothFilters));
193             }
194         }
195 
196         if (shouldScan(mBluetoothFilters)) {
197             final IntentFilter intentFilter = new IntentFilter();
198             intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
199 
200             Log.i(LOG_TAG, "registerReceiver(BluetoothDevice.ACTION_FOUND)");
201             mBluetoothBroadcastReceiver = new BluetoothBroadcastReceiver();
202             registerReceiver(mBluetoothBroadcastReceiver, intentFilter);
203             mBluetoothAdapter.startDiscovery();
204         }
205 
206         if (shouldScan(mBLEFilters) && mBLEScanner != null) {
207             Log.i(LOG_TAG, "BLEScanner.startScan");
208             mBLEScanCallback = new BLEScanCallback();
209             mBLEScanner.startScan(mBLEScanFilters, mDefaultScanSettings, mBLEScanCallback);
210         }
211 
212         if (shouldScan(mWifiFilters)) {
213             Log.i(LOG_TAG, "registerReceiver(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)");
214             mWifiBroadcastReceiver = new WifiBroadcastReceiver();
215             registerReceiver(mWifiBroadcastReceiver,
216                     new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
217             mWifiManager.startScan();
218         }
219         mIsScanning = true;
220         Handler.getMain().sendMessageDelayed(
221                 obtainMessage(CompanionDeviceDiscoveryService::stopScan, this),
222                 SCAN_TIMEOUT);
223     }
224 
shouldScan(List<? extends DeviceFilter> mediumSpecificFilters)225     private boolean shouldScan(List<? extends DeviceFilter> mediumSpecificFilters) {
226         return !isEmpty(mediumSpecificFilters) || isEmpty(mFilters);
227     }
228 
229     @MainThread
reset()230     private void reset() {
231         Log.i(LOG_TAG, "reset()");
232         stopScan();
233         mDevicesFound.clear();
234         mSelectedDevice = null;
235         CompanionDeviceActivity.notifyDevicesChanged();
236     }
237 
238     @Override
onUnbind(Intent intent)239     public boolean onUnbind(Intent intent) {
240         Log.i(LOG_TAG, "onUnbind(intent = " + intent + ")");
241         stopScan();
242         return super.onUnbind(intent);
243     }
244 
stopScan()245     private void stopScan() {
246         Log.i(LOG_TAG, "stopScan()");
247 
248         if (!mIsScanning) return;
249         mIsScanning = false;
250 
251         CompanionDeviceActivity activity = mActivity;
252         if (activity != null) {
253             if (activity.mDeviceListView != null) {
254                 activity.mDeviceListView.removeFooterView(activity.mLoadingIndicator);
255             }
256             mActivity = null;
257         }
258 
259         mBluetoothAdapter.cancelDiscovery();
260         if (mBluetoothBroadcastReceiver != null) {
261             unregisterReceiver(mBluetoothBroadcastReceiver);
262             mBluetoothBroadcastReceiver = null;
263         }
264         if (mBLEScanner != null) mBLEScanner.stopScan(mBLEScanCallback);
265         if (mWifiBroadcastReceiver != null) {
266             unregisterReceiver(mWifiBroadcastReceiver);
267             mWifiBroadcastReceiver = null;
268         }
269     }
270 
onDeviceFound(@ullable DeviceFilterPair device)271     private void onDeviceFound(@Nullable DeviceFilterPair device) {
272         if (device == null) return;
273 
274         Handler.getMain().sendMessage(obtainMessage(
275                 CompanionDeviceDiscoveryService::onDeviceFoundMainThread, this, device));
276     }
277 
278     @MainThread
onDeviceFoundMainThread(@onNull DeviceFilterPair device)279     void onDeviceFoundMainThread(@NonNull DeviceFilterPair device) {
280         if (mDevicesFound.contains(device)) {
281             Log.i(LOG_TAG, "Skipping device " + device + " - already among found devices");
282             return;
283         }
284 
285         Log.i(LOG_TAG, "Found device " + device);
286 
287         if (mDevicesFound.isEmpty()) {
288             onReadyToShowUI();
289         }
290         mDevicesFound.add(device);
291         CompanionDeviceActivity.notifyDevicesChanged();
292     }
293 
294     //TODO also, on timeout -> call onFailure
onReadyToShowUI()295     private void onReadyToShowUI() {
296         try {
297             mFindCallback.onSuccess(PendingIntent.getActivity(
298                     this, 0,
299                     new Intent(this, CompanionDeviceActivity.class),
300                     PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT
301                             | PendingIntent.FLAG_IMMUTABLE));
302         } catch (RemoteException e) {
303             throw new RuntimeException(e);
304         }
305     }
306 
onDeviceLost(@ullable DeviceFilterPair device)307     private void onDeviceLost(@Nullable DeviceFilterPair device) {
308         Log.i(LOG_TAG, "Lost device " + device.getDisplayName());
309         Handler.getMain().sendMessage(obtainMessage(
310                 CompanionDeviceDiscoveryService::onDeviceLostMainThread, this, device));
311     }
312 
313     @MainThread
onDeviceLostMainThread(@ullable DeviceFilterPair device)314     void onDeviceLostMainThread(@Nullable DeviceFilterPair device) {
315         mDevicesFound.remove(device);
316         CompanionDeviceActivity.notifyDevicesChanged();
317     }
318 
onDeviceSelected(String callingPackage, String deviceAddress)319     void onDeviceSelected(String callingPackage, String deviceAddress) {
320         if (callingPackage == null || deviceAddress == null) {
321             return;
322         }
323         mServiceCallback.complete(new Association(
324                 getUserId(), deviceAddress, callingPackage, mRequest.getDeviceProfile(), false,
325                 System.currentTimeMillis()));
326     }
327 
onCancel()328     void onCancel() {
329         if (DEBUG) Log.i(LOG_TAG, "onCancel()");
330         mServiceCallback.cancel(true);
331     }
332 
333     /**
334      * A pair of device and a filter that matched this device if any.
335      *
336      * @param <T> device type
337      */
338     static class DeviceFilterPair<T extends Parcelable> {
339         public final T device;
340         @Nullable
341         public final DeviceFilter<T> filter;
342 
DeviceFilterPair(T device, @Nullable DeviceFilter<T> filter)343         private DeviceFilterPair(T device, @Nullable DeviceFilter<T> filter) {
344             this.device = device;
345             this.filter = filter;
346         }
347 
348         /**
349          * {@code (device, null)} if the filters list is empty or null
350          * {@code null} if none of the provided filters match the device
351          * {@code (device, filter)} where filter is among the list of filters and matches the device
352          */
353         @Nullable
findMatch( T dev, @Nullable List<? extends DeviceFilter<T>> filters)354         public static <T extends Parcelable> DeviceFilterPair<T> findMatch(
355                 T dev, @Nullable List<? extends DeviceFilter<T>> filters) {
356             if (isEmpty(filters)) return new DeviceFilterPair<>(dev, null);
357             final DeviceFilter<T> matchingFilter
358                     = CollectionUtils.find(filters, f -> f.matches(dev));
359 
360             DeviceFilterPair<T> result = matchingFilter != null
361                     ? new DeviceFilterPair<>(dev, matchingFilter)
362                     : null;
363             if (DEBUG) Log.i(LOG_TAG, "findMatch(dev = " + dev + ", filters = " + filters +
364                     ") -> " + result);
365             return result;
366         }
367 
getDisplayName()368         public String getDisplayName() {
369             if (filter == null) {
370                 Preconditions.checkNotNull(device);
371                 if (device instanceof BluetoothDevice) {
372                     return getDeviceDisplayNameInternal((BluetoothDevice) device);
373                 } else if (device instanceof android.net.wifi.ScanResult) {
374                     return getDeviceDisplayNameInternal((android.net.wifi.ScanResult) device);
375                 } else if (device instanceof ScanResult) {
376                     return getDeviceDisplayNameInternal(((ScanResult) device).getDevice());
377                 } else {
378                     throw new IllegalArgumentException("Unknown device type: " + device.getClass());
379                 }
380             }
381             return filter.getDeviceDisplayName(device);
382         }
383 
384         @Override
equals(Object o)385         public boolean equals(Object o) {
386             if (this == o) return true;
387             if (o == null || getClass() != o.getClass()) return false;
388             DeviceFilterPair<?> that = (DeviceFilterPair<?>) o;
389             return Objects.equals(getDeviceMacAddress(device), getDeviceMacAddress(that.device));
390         }
391 
392         @Override
hashCode()393         public int hashCode() {
394             return Objects.hash(getDeviceMacAddress(device));
395         }
396 
397         @Override
toString()398         public String toString() {
399             return "DeviceFilterPair{"
400                     + "device=" + device + " " + getDisplayName()
401                     + ", filter=" + filter
402                     + '}';
403         }
404     }
405 
406     private class BLEScanCallback extends ScanCallback {
407 
BLEScanCallback()408         public BLEScanCallback() {
409             if (DEBUG) Log.i(LOG_TAG, "new BLEScanCallback() -> " + this);
410         }
411 
412         @Override
onScanResult(int callbackType, ScanResult result)413         public void onScanResult(int callbackType, ScanResult result) {
414             if (DEBUG) {
415                 Log.i(LOG_TAG,
416                         "BLE.onScanResult(callbackType = " + callbackType + ", result = " + result
417                                 + ")");
418             }
419             final DeviceFilterPair<ScanResult> deviceFilterPair
420                     = DeviceFilterPair.findMatch(result, mBLEFilters);
421             if (deviceFilterPair == null) return;
422             if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) {
423                 onDeviceLost(deviceFilterPair);
424             } else {
425                 onDeviceFound(deviceFilterPair);
426             }
427         }
428     }
429 
430     private class BluetoothBroadcastReceiver extends BroadcastReceiver {
431         @Override
onReceive(Context context, Intent intent)432         public void onReceive(Context context, Intent intent) {
433             if (DEBUG) {
434                 Log.i(LOG_TAG,
435                         "BL.onReceive(context = " + context + ", intent = " + intent + ")");
436             }
437             final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
438             final DeviceFilterPair<BluetoothDevice> deviceFilterPair
439                     = DeviceFilterPair.findMatch(device, mBluetoothFilters);
440             if (deviceFilterPair == null) return;
441             if (intent.getAction().equals(BluetoothDevice.ACTION_FOUND)) {
442                 onDeviceFound(deviceFilterPair);
443             } else {
444                 onDeviceLost(deviceFilterPair);
445             }
446         }
447     }
448 
449     private class WifiBroadcastReceiver extends BroadcastReceiver {
450         @Override
onReceive(Context context, Intent intent)451         public void onReceive(Context context, Intent intent) {
452             if (intent.getAction().equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
453                 List<android.net.wifi.ScanResult> scanResults = mWifiManager.getScanResults();
454 
455                 if (DEBUG) {
456                     Log.i(LOG_TAG, "Wifi scan results: " + TextUtils.join("\n", scanResults));
457                 }
458 
459                 for (int i = 0; i < scanResults.size(); i++) {
460                     onDeviceFound(DeviceFilterPair.findMatch(scanResults.get(i), mWifiFilters));
461                 }
462             }
463         }
464     }
465 }
466