DeviceDiscoveryService.java revision 6a7006a9683ba5a79ca338050c7c50b346b04de0
16ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla/* 26ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla * Copyright (C) 2013 The Android Open Source Project 36ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla * 46ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla * Licensed under the Apache License, Version 2.0 (the "License"); 56ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla * you may not use this file except in compliance with the License. 66ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla * You may obtain a copy of the License at 76ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla * 86ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla * http://www.apache.org/licenses/LICENSE-2.0 96ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla * 106ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla * Unless required by applicable law or agreed to in writing, software 116ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla * distributed under the License is distributed on an "AS IS" BASIS, 126ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 136ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla * See the License for the specific language governing permissions and 146ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla * limitations under the License. 156ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla */ 166ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 176ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslapackage com.android.companiondevicemanager; 186ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 1936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameInternal; 2036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress; 2136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla 2236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport static com.android.internal.util.ArrayUtils.isEmpty; 236ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 246ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.annotation.NonNull; 256ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.annotation.Nullable; 266ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.app.PendingIntent; 276ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.app.Service; 286ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.BluetoothAdapter; 296ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.BluetoothDevice; 306ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.BluetoothManager; 316ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.le.BluetoothLeScanner; 326ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.le.ScanCallback; 336ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.le.ScanFilter; 346ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.le.ScanResult; 356ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.le.ScanSettings; 366ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.companion.AssociationRequest; 3736e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport android.companion.BluetoothDeviceFilter; 386ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.companion.BluetoothLEDeviceFilter; 3936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport android.companion.DeviceFilter; 40da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganovimport android.companion.ICompanionDeviceDiscoveryService; 41da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganovimport android.companion.ICompanionDeviceDiscoveryServiceCallback; 42da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganovimport android.companion.IFindDeviceCallback; 4336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport android.companion.WifiDeviceFilter; 446ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.content.BroadcastReceiver; 456ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.content.Context; 466ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.content.Intent; 476ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.content.IntentFilter; 486ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.graphics.Color; 496ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.graphics.drawable.Drawable; 5036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport android.net.wifi.WifiManager; 516ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.os.IBinder; 5236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport android.os.Parcelable; 536ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.os.RemoteException; 5436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport android.text.TextUtils; 556ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.util.Log; 566ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.view.View; 576ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.view.ViewGroup; 586ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.widget.ArrayAdapter; 596ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.widget.TextView; 606ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 616a7006a9683ba5a79ca338050c7c50b346b04de0Eugene Suslaimport com.android.internal.util.CollectionUtils; 6236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport com.android.internal.util.Preconditions; 6336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla 646ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport java.util.ArrayList; 656ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport java.util.List; 6636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport java.util.Objects; 676ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 686ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslapublic class DeviceDiscoveryService extends Service { 696ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 706ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla private static final boolean DEBUG = false; 716ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla private static final String LOG_TAG = "DeviceDiscoveryService"; 726ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 736ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla static DeviceDiscoveryService sInstance; 746ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 756ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla private BluetoothAdapter mBluetoothAdapter; 7636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla private WifiManager mWifiManager; 776ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla private ScanSettings mDefaultScanSettings = new ScanSettings.Builder().build(); 7836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla private List<DeviceFilter<?>> mFilters; 7936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla private List<BluetoothLEDeviceFilter> mBLEFilters; 8036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla private List<BluetoothDeviceFilter> mBluetoothFilters; 8136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla private List<WifiDeviceFilter> mWifiFilters; 8236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla private List<ScanFilter> mBLEScanFilters; 8336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla AssociationRequest mRequest; 8436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla List<DeviceFilterPair> mDevicesFound; 8536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla DeviceFilterPair mSelectedDevice; 866ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla DevicesAdapter mDevicesAdapter; 87da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov IFindDeviceCallback mFindCallback; 88da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov ICompanionDeviceDiscoveryServiceCallback mServiceCallback; 896ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 90da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov private final ICompanionDeviceDiscoveryService mBinder = 91da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov new ICompanionDeviceDiscoveryService.Stub() { 926ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla @Override 936ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla public void startDiscovery(AssociationRequest request, 94da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov String callingPackage, 95da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov IFindDeviceCallback findCallback, 96da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov ICompanionDeviceDiscoveryServiceCallback serviceCallback) { 976ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla if (DEBUG) { 986ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla Log.i(LOG_TAG, 99da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov "startDiscovery() called with: filter = [" + request 100da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov + "], findCallback = [" + findCallback + "]" 101da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov + "], serviceCallback = [" + serviceCallback + "]"); 1026ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 103da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov mFindCallback = findCallback; 104da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov mServiceCallback = serviceCallback; 1056ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla DeviceDiscoveryService.this.startDiscovery(request); 1066ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 1076ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla }; 1086ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 1096ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla private final ScanCallback mBLEScanCallback = new ScanCallback() { 1106ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla @Override 1116ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla public void onScanResult(int callbackType, ScanResult result) { 11236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla final DeviceFilterPair<ScanResult> deviceFilterPair 11336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla = DeviceFilterPair.findMatch(result, mBLEFilters); 11436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla if (deviceFilterPair == null) return; 1156ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) { 11636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla onDeviceLost(deviceFilterPair); 1176ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } else { 11836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla onDeviceFound(deviceFilterPair); 1196ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 1206ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 1216ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla }; 1226ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 1236ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla private BluetoothLeScanner mBLEScanner; 1246ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 1256ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla private BroadcastReceiver mBluetoothDeviceFoundBroadcastReceiver = new BroadcastReceiver() { 1266ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla @Override 1276ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla public void onReceive(Context context, Intent intent) { 12836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 12936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla final DeviceFilterPair<BluetoothDevice> deviceFilterPair 13036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla = DeviceFilterPair.findMatch(device, mBluetoothFilters); 13136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla if (deviceFilterPair == null) return; 1326ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla if (intent.getAction().equals(BluetoothDevice.ACTION_FOUND)) { 13336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla onDeviceFound(deviceFilterPair); 1346ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } else { 13536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla onDeviceLost(deviceFilterPair); 13636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } 13736e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } 13836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla }; 13936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla 14036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla private BroadcastReceiver mWifiDeviceFoundBroadcastReceiver = new BroadcastReceiver() { 14136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla @Override 14236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla public void onReceive(Context context, Intent intent) { 14336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla if (intent.getAction().equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { 14436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla List<android.net.wifi.ScanResult> scanResults = mWifiManager.getScanResults(); 14536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla 14636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla if (DEBUG) { 14736e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla Log.i(LOG_TAG, "Wifi scan results: " + TextUtils.join("\n", scanResults)); 14836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } 14936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla 15036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla for (int i = 0; i < scanResults.size(); i++) { 15136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla DeviceFilterPair<android.net.wifi.ScanResult> deviceFilterPair = 15236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla DeviceFilterPair.findMatch(scanResults.get(i), mWifiFilters); 15336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla if (deviceFilterPair != null) onDeviceFound(deviceFilterPair); 15436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } 1556ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 15636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla 1576ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 1586ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla }; 1596ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 1606ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla @Override 1616ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla public IBinder onBind(Intent intent) { 1626ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla if (DEBUG) Log.i(LOG_TAG, "onBind(" + intent + ")"); 1636ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla return mBinder.asBinder(); 1646ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 1656ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 1666ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla @Override 1676ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla public void onCreate() { 1686ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla super.onCreate(); 1696ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 1706ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla if (DEBUG) Log.i(LOG_TAG, "onCreate()"); 1716ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 1726ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla mBluetoothAdapter = getSystemService(BluetoothManager.class).getAdapter(); 1736ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner(); 17436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla mWifiManager = getSystemService(WifiManager.class); 1756ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 1766ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla mDevicesFound = new ArrayList<>(); 1776ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla mDevicesAdapter = new DevicesAdapter(); 1786ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 1796ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla sInstance = this; 1806ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 1816ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 18236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla private void startDiscovery(AssociationRequest request) { 183d44f9334ed8b4afc08f70099c46301525b1f8d71Eugene Susla mRequest = request; 18436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla 18536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla mFilters = request.getDeviceFilters(); 1866a7006a9683ba5a79ca338050c7c50b346b04de0Eugene Susla mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class); 1876a7006a9683ba5a79ca338050c7c50b346b04de0Eugene Susla mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class); 1886a7006a9683ba5a79ca338050c7c50b346b04de0Eugene Susla mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLEDeviceFilter.class); 1896a7006a9683ba5a79ca338050c7c50b346b04de0Eugene Susla mBLEScanFilters = CollectionUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter); 1906ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 1916ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla reset(); 1926ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 19336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla if (shouldScan(mBluetoothFilters)) { 19436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla final IntentFilter intentFilter = new IntentFilter(); 19536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla intentFilter.addAction(BluetoothDevice.ACTION_FOUND); 19636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla intentFilter.addAction(BluetoothDevice.ACTION_DISAPPEARED); 1976ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 19836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla registerReceiver(mBluetoothDeviceFoundBroadcastReceiver, intentFilter); 19936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla mBluetoothAdapter.startDiscovery(); 20036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } 2016ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 20236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla if (shouldScan(mBLEFilters)) { 20336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla mBLEScanner.startScan(mBLEScanFilters, mDefaultScanSettings, mBLEScanCallback); 20436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } 20536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla 20636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla if (shouldScan(mWifiFilters)) { 20736e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla registerReceiver(mWifiDeviceFoundBroadcastReceiver, 20836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); 20936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla mWifiManager.startScan(); 21036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } 21136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } 21236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla 21336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla private boolean shouldScan(List<? extends DeviceFilter> mediumSpecificFilters) { 21436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla return !isEmpty(mediumSpecificFilters) || isEmpty(mFilters); 2156ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 2166ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 2176ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla private void reset() { 2186ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla mDevicesFound.clear(); 2196ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla mSelectedDevice = null; 2206ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla mDevicesAdapter.notifyDataSetChanged(); 2216ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 2226ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 2236ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla @Override 2246ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla public boolean onUnbind(Intent intent) { 2256ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla stopScan(); 2266ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla return super.onUnbind(intent); 2276ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 2286ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 2296ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla private void stopScan() { 2306ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla if (DEBUG) Log.i(LOG_TAG, "stopScan() called"); 2316ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla mBluetoothAdapter.cancelDiscovery(); 2326ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla mBLEScanner.stopScan(mBLEScanCallback); 2336ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla unregisterReceiver(mBluetoothDeviceFoundBroadcastReceiver); 23436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla unregisterReceiver(mWifiDeviceFoundBroadcastReceiver); 2356ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla stopSelf(); 2366ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 2376ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 23836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla private void onDeviceFound(@Nullable DeviceFilterPair device) { 2396ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla if (mDevicesFound.contains(device)) { 2406ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla return; 2416ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 2426ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 24336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla if (DEBUG) Log.i(LOG_TAG, "Found device " + device.getDisplayName() + " " 24436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla + getDeviceMacAddress(device.device)); 2456ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 2466ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla if (mDevicesFound.isEmpty()) { 2476ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla onReadyToShowUI(); 2486ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 2496ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla mDevicesFound.add(device); 2506ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla mDevicesAdapter.notifyDataSetChanged(); 2516ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 2526ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 2536ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla //TODO also, on timeout -> call onFailure 2546ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla private void onReadyToShowUI() { 2556ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla try { 256da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov mFindCallback.onSuccess(PendingIntent.getActivity( 2576ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla this, 0, 2586ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla new Intent(this, DeviceChooserActivity.class), 2596ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT 2606ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla | PendingIntent.FLAG_IMMUTABLE)); 2616ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } catch (RemoteException e) { 2626ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla throw new RuntimeException(e); 2636ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 2646ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 2656ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 26636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla private void onDeviceLost(@Nullable DeviceFilterPair device) { 2676ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla mDevicesFound.remove(device); 2686ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla mDevicesAdapter.notifyDataSetChanged(); 26936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla if (DEBUG) Log.i(LOG_TAG, "Lost device " + device.getDisplayName()); 2706ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 2716ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 27247aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla void onDeviceSelected(String callingPackage, String deviceAddress) { 27347aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla try { 27447aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla mServiceCallback.onDeviceSelected( 27547aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla //TODO is this the right userId? 27647aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla callingPackage, getUserId(), deviceAddress); 27747aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla } catch (RemoteException e) { 27847aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla Log.e(LOG_TAG, "Failed to record association: " 27947aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla + callingPackage + " <-> " + deviceAddress); 28047aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla } 28147aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla } 28247aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla 28336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla class DevicesAdapter extends ArrayAdapter<DeviceFilterPair> { 28436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla //TODO wifi icon 2856ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla private Drawable BLUETOOTH_ICON = icon(android.R.drawable.stat_sys_data_bluetooth); 2866ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 2876ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla private Drawable icon(int drawableRes) { 2886ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla Drawable icon = getResources().getDrawable(drawableRes, null); 2896ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla icon.setTint(Color.DKGRAY); 2906ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla return icon; 2916ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 2926ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 2936ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla public DevicesAdapter() { 2946ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla super(DeviceDiscoveryService.this, 0, mDevicesFound); 2956ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 2966ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 2976ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla @Override 2986ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla public View getView( 2996ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla int position, 3006ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla @Nullable View convertView, 3016ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla @NonNull ViewGroup parent) { 3026ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla TextView view = convertView instanceof TextView 3036ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla ? (TextView) convertView 3046ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla : newView(); 3056ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla bind(view, getItem(position)); 3066ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla return view; 3076ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 3086ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 30936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla private void bind(TextView textView, DeviceFilterPair device) { 31036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla textView.setText(device.getDisplayName()); 3116ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla textView.setBackgroundColor( 3126ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla device.equals(mSelectedDevice) 3136ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla ? Color.GRAY 3146ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla : Color.TRANSPARENT); 3156ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla textView.setOnClickListener((view) -> { 3166ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla mSelectedDevice = device; 3176ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla notifyDataSetChanged(); 3186ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla }); 3196ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 3206ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla 3216ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla //TODO move to a layout file 3226ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla private TextView newView() { 3236ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla final TextView textView = new TextView(DeviceDiscoveryService.this); 3246ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla textView.setTextColor(Color.BLACK); 3256ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla final int padding = DeviceChooserActivity.getPadding(getResources()); 3266ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla textView.setPadding(padding, padding, padding, padding); 3276ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla textView.setCompoundDrawablesWithIntrinsicBounds( 3286ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla BLUETOOTH_ICON, null, null, null); 3296ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla textView.setCompoundDrawablePadding(padding); 3306ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla return textView; 3316ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 3326ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla } 33336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla 33436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla /** 33536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla * A pair of device and a filter that matched this device if any. 33636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla * 33736e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla * @param <T> device type 33836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla */ 33936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla static class DeviceFilterPair<T extends Parcelable> { 34036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla public final T device; 34136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla @Nullable 34236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla public final DeviceFilter<T> filter; 34336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla 34436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla private DeviceFilterPair(T device, @Nullable DeviceFilter<T> filter) { 34536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla this.device = device; 34636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla this.filter = filter; 34736e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } 34836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla 34936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla /** 35036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla * {@code (device, null)} if the filters list is empty or null 35136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla * {@code null} if none of the provided filters match the device 35236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla * {@code (device, filter)} where filter is among the list of filters and matches the device 35336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla */ 35436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla @Nullable 35536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla public static <T extends Parcelable> DeviceFilterPair<T> findMatch( 35636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla T dev, @Nullable List<? extends DeviceFilter<T>> filters) { 35736e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla if (isEmpty(filters)) return new DeviceFilterPair<>(dev, null); 3586a7006a9683ba5a79ca338050c7c50b346b04de0Eugene Susla final DeviceFilter<T> matchingFilter = CollectionUtils.find(filters, (f) -> f.matches(dev)); 35936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla return matchingFilter != null ? new DeviceFilterPair<>(dev, matchingFilter) : null; 36036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } 36136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla 36236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla public String getDisplayName() { 36336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla if (filter == null) { 36436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla Preconditions.checkNotNull(device); 36536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla if (device instanceof BluetoothDevice) { 36636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla return getDeviceDisplayNameInternal((BluetoothDevice) device); 36736e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } else if (device instanceof android.net.wifi.ScanResult) { 36836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla return getDeviceDisplayNameInternal((android.net.wifi.ScanResult) device); 36936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } else if (device instanceof ScanResult) { 37036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla return getDeviceDisplayNameInternal(((ScanResult) device).getDevice()); 37136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } else { 37236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla throw new IllegalArgumentException("Unknown device type: " + device.getClass()); 37336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } 37436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } 37536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla return filter.getDeviceDisplayName(device); 37636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } 37736e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla 37836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla @Override 37936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla public boolean equals(Object o) { 38036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla if (this == o) return true; 38136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla if (o == null || getClass() != o.getClass()) return false; 38236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla DeviceFilterPair<?> that = (DeviceFilterPair<?>) o; 38336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla return Objects.equals(getDeviceMacAddress(device), getDeviceMacAddress(that.device)); 38436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } 38536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla 38636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla @Override 38736e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla public int hashCode() { 38836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla return Objects.hash(getDeviceMacAddress(device)); 38936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } 39036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla } 3916ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla} 392