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;
230c4a9266264d37e724f7372ef7ef932cf60c505cEugene Suslaimport static com.android.internal.util.CollectionUtils.emptyIfNull;
240c4a9266264d37e724f7372ef7ef932cf60c505cEugene Suslaimport static com.android.internal.util.CollectionUtils.size;
256ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
266ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.annotation.NonNull;
276ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.annotation.Nullable;
286ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.app.PendingIntent;
296ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.app.Service;
306ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.BluetoothAdapter;
316ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.BluetoothDevice;
326ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.BluetoothManager;
336ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.le.BluetoothLeScanner;
346ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.le.ScanCallback;
356ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.le.ScanFilter;
366ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.le.ScanResult;
376ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.le.ScanSettings;
386ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.companion.AssociationRequest;
3936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport android.companion.BluetoothDeviceFilter;
40722463ff953168ae27bedf61a586f296813fc9feEugene Suslaimport android.companion.BluetoothLeDeviceFilter;
4136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport android.companion.DeviceFilter;
42da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganovimport android.companion.ICompanionDeviceDiscoveryService;
43da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganovimport android.companion.ICompanionDeviceDiscoveryServiceCallback;
44da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganovimport android.companion.IFindDeviceCallback;
4536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport android.companion.WifiDeviceFilter;
466ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.content.BroadcastReceiver;
476ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.content.Context;
486ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.content.Intent;
496ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.content.IntentFilter;
506ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.graphics.Color;
516ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.graphics.drawable.Drawable;
5236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport android.net.wifi.WifiManager;
536ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.os.IBinder;
5436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport android.os.Parcelable;
556ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.os.RemoteException;
5636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport android.text.TextUtils;
576ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.util.Log;
586ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.view.View;
596ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.view.ViewGroup;
606ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.widget.ArrayAdapter;
616ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.widget.TextView;
626ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
63a38fbf63fd2a29884637a59387643c801ed4f663Eugene Suslaimport com.android.internal.util.ArrayUtils;
646a7006a9683ba5a79ca338050c7c50b346b04de0Eugene Suslaimport com.android.internal.util.CollectionUtils;
6536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport com.android.internal.util.Preconditions;
6636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla
676ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport java.util.ArrayList;
686ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport java.util.List;
6936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Suslaimport java.util.Objects;
706ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
716ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslapublic class DeviceDiscoveryService extends Service {
726ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
736ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private static final boolean DEBUG = false;
746ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private static final String LOG_TAG = "DeviceDiscoveryService";
756ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
766ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    static DeviceDiscoveryService sInstance;
776ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
786ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private BluetoothAdapter mBluetoothAdapter;
7936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla    private WifiManager mWifiManager;
807a090a11ed00aa492684800de890f4df633409d8Eugene Susla    @Nullable private BluetoothLeScanner mBLEScanner;
816ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private ScanSettings mDefaultScanSettings = new ScanSettings.Builder().build();
82a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla
8336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla    private List<DeviceFilter<?>> mFilters;
84722463ff953168ae27bedf61a586f296813fc9feEugene Susla    private List<BluetoothLeDeviceFilter> mBLEFilters;
8536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla    private List<BluetoothDeviceFilter> mBluetoothFilters;
8636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla    private List<WifiDeviceFilter> mWifiFilters;
8736e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla    private List<ScanFilter> mBLEScanFilters;
88a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla
8936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla    AssociationRequest mRequest;
9036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla    List<DeviceFilterPair> mDevicesFound;
9136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla    DeviceFilterPair mSelectedDevice;
926ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    DevicesAdapter mDevicesAdapter;
93da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov    IFindDeviceCallback mFindCallback;
94a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla
95da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov    ICompanionDeviceDiscoveryServiceCallback mServiceCallback;
966ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
97da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov    private final ICompanionDeviceDiscoveryService mBinder =
98da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov            new ICompanionDeviceDiscoveryService.Stub() {
996ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        @Override
1006ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        public void startDiscovery(AssociationRequest request,
101da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov                String callingPackage,
102da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov                IFindDeviceCallback findCallback,
103da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov                ICompanionDeviceDiscoveryServiceCallback serviceCallback) {
1046ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            if (DEBUG) {
1056ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                Log.i(LOG_TAG,
106da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov                        "startDiscovery() called with: filter = [" + request
107da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov                                + "], findCallback = [" + findCallback + "]"
108da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov                                + "], serviceCallback = [" + serviceCallback + "]");
1096ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            }
110da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov            mFindCallback = findCallback;
111da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov            mServiceCallback = serviceCallback;
1126ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            DeviceDiscoveryService.this.startDiscovery(request);
1136ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
1146ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    };
1156ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
116a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla    private ScanCallback mBLEScanCallback;
117a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla    private BluetoothBroadcastReceiver mBluetoothBroadcastReceiver;
118a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla    private WifiBroadcastReceiver mWifiBroadcastReceiver;
1196ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1206ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    @Override
1216ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    public IBinder onBind(Intent intent) {
1226ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        if (DEBUG) Log.i(LOG_TAG, "onBind(" + intent + ")");
1236ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        return mBinder.asBinder();
1246ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
1256ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1266ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    @Override
1276ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    public void onCreate() {
1286ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        super.onCreate();
1296ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1306ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        if (DEBUG) Log.i(LOG_TAG, "onCreate()");
1316ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1326ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mBluetoothAdapter = getSystemService(BluetoothManager.class).getAdapter();
1336ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
13436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        mWifiManager = getSystemService(WifiManager.class);
1356ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1366ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mDevicesFound = new ArrayList<>();
1376ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mDevicesAdapter = new DevicesAdapter();
1386ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1396ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        sInstance = this;
1406ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
1416ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
14236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla    private void startDiscovery(AssociationRequest request) {
143a38fbf63fd2a29884637a59387643c801ed4f663Eugene Susla        if (!request.equals(mRequest)) {
144a38fbf63fd2a29884637a59387643c801ed4f663Eugene Susla            mRequest = request;
14536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla
146a38fbf63fd2a29884637a59387643c801ed4f663Eugene Susla            mFilters = request.getDeviceFilters();
147a38fbf63fd2a29884637a59387643c801ed4f663Eugene Susla            mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class);
148a38fbf63fd2a29884637a59387643c801ed4f663Eugene Susla            mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class);
149722463ff953168ae27bedf61a586f296813fc9feEugene Susla            mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLeDeviceFilter.class);
150722463ff953168ae27bedf61a586f296813fc9feEugene Susla            mBLEScanFilters = CollectionUtils.map(mBLEFilters, BluetoothLeDeviceFilter::getScanFilter);
1516ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
152a38fbf63fd2a29884637a59387643c801ed4f663Eugene Susla            reset();
1533c9aa1767ca0ddc881f53c2ce7eb3135670efef3Eugene Susla        } else if (DEBUG) Log.i(LOG_TAG, "startDiscovery: duplicate request: " + request);
1543c9aa1767ca0ddc881f53c2ce7eb3135670efef3Eugene Susla
155a38fbf63fd2a29884637a59387643c801ed4f663Eugene Susla        if (!ArrayUtils.isEmpty(mDevicesFound)) {
156a38fbf63fd2a29884637a59387643c801ed4f663Eugene Susla            onReadyToShowUI();
157a38fbf63fd2a29884637a59387643c801ed4f663Eugene Susla        }
1586ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1590c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla        // If filtering to get single device by mac address, also search in the set of already
1600c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla        // bonded devices to allow linking those directly
1610c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla        String singleMacAddressFilter = null;
1620c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla        if (mRequest.isSingleDevice()) {
1630c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla            int numFilters = size(mBluetoothFilters);
1640c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla            for (int i = 0; i < numFilters; i++) {
1650c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla                BluetoothDeviceFilter filter = mBluetoothFilters.get(i);
1660c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla                if (!TextUtils.isEmpty(filter.getAddress())) {
1670c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla                    singleMacAddressFilter = filter.getAddress();
1680c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla                    break;
1690c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla                }
1700c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla            }
1710c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla        }
1720c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla        if (singleMacAddressFilter != null) {
1730c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla            for (BluetoothDevice dev : emptyIfNull(mBluetoothAdapter.getBondedDevices())) {
1740c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla                onDeviceFound(DeviceFilterPair.findMatch(dev, mBluetoothFilters));
1750c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla            }
1760c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla        }
1770c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla
17836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        if (shouldScan(mBluetoothFilters)) {
17936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla            final IntentFilter intentFilter = new IntentFilter();
18036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla            intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
18136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla            intentFilter.addAction(BluetoothDevice.ACTION_DISAPPEARED);
1826ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
183a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            mBluetoothBroadcastReceiver = new BluetoothBroadcastReceiver();
184a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            registerReceiver(mBluetoothBroadcastReceiver, intentFilter);
18536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla            mBluetoothAdapter.startDiscovery();
18636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        }
1876ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1887a090a11ed00aa492684800de890f4df633409d8Eugene Susla        if (shouldScan(mBLEFilters) && mBLEScanner != null) {
189a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            mBLEScanCallback = new BLEScanCallback();
19036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla            mBLEScanner.startScan(mBLEScanFilters, mDefaultScanSettings, mBLEScanCallback);
19136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        }
19236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla
19336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        if (shouldScan(mWifiFilters)) {
194a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            mWifiBroadcastReceiver = new WifiBroadcastReceiver();
195a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            registerReceiver(mWifiBroadcastReceiver,
19636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla                    new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
19736e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla            mWifiManager.startScan();
19836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        }
19936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla    }
20036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla
20136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla    private boolean shouldScan(List<? extends DeviceFilter> mediumSpecificFilters) {
20236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        return !isEmpty(mediumSpecificFilters) || isEmpty(mFilters);
2036ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
2046ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
2056ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private void reset() {
2063c9aa1767ca0ddc881f53c2ce7eb3135670efef3Eugene Susla        if (DEBUG) Log.i(LOG_TAG, "reset()");
207a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        stopScan();
2086ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mDevicesFound.clear();
2096ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mSelectedDevice = null;
2106ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mDevicesAdapter.notifyDataSetChanged();
2116ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
2126ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
2136ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    @Override
2146ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    public boolean onUnbind(Intent intent) {
2156ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        stopScan();
2166ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        return super.onUnbind(intent);
2176ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
2186ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
2196ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private void stopScan() {
220a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        if (DEBUG) Log.i(LOG_TAG, "stopScan()");
221a38fbf63fd2a29884637a59387643c801ed4f663Eugene Susla
222a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        mBluetoothAdapter.cancelDiscovery();
223a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        if (mBluetoothBroadcastReceiver != null) {
224a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            unregisterReceiver(mBluetoothBroadcastReceiver);
225a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            mBluetoothBroadcastReceiver = null;
226a38fbf63fd2a29884637a59387643c801ed4f663Eugene Susla        }
2277a090a11ed00aa492684800de890f4df633409d8Eugene Susla        if (mBLEScanner != null) mBLEScanner.stopScan(mBLEScanCallback);
228a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        if (mWifiBroadcastReceiver != null) {
229a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            unregisterReceiver(mWifiBroadcastReceiver);
230a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            mWifiBroadcastReceiver = null;
231a38fbf63fd2a29884637a59387643c801ed4f663Eugene Susla        }
2326ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
2336ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
23436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla    private void onDeviceFound(@Nullable DeviceFilterPair device) {
2350c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla        if (device == null) return;
2360c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla
2376ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        if (mDevicesFound.contains(device)) {
2386ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            return;
2396ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
2406ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
241a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        if (DEBUG) Log.i(LOG_TAG, "Found device " + device);
2426ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
2436ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        if (mDevicesFound.isEmpty()) {
2446ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            onReadyToShowUI();
2456ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
2466ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mDevicesFound.add(device);
2476ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mDevicesAdapter.notifyDataSetChanged();
2486ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
2496ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
2506ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    //TODO also, on timeout -> call onFailure
2516ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private void onReadyToShowUI() {
2526ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        try {
253da0acdf938f1d6e7a978e143d5d80d8dd5af52adSvet Ganov            mFindCallback.onSuccess(PendingIntent.getActivity(
2546ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                    this, 0,
2556ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                    new Intent(this, DeviceChooserActivity.class),
2566ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                    PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT
2576ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                            | PendingIntent.FLAG_IMMUTABLE));
2586ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        } catch (RemoteException e) {
2596ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            throw new RuntimeException(e);
2606ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
2616ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
2626ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
26336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla    private void onDeviceLost(@Nullable DeviceFilterPair device) {
2646ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mDevicesFound.remove(device);
2656ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mDevicesAdapter.notifyDataSetChanged();
26636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        if (DEBUG) Log.i(LOG_TAG, "Lost device " + device.getDisplayName());
2676ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
2686ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
26947aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla    void onDeviceSelected(String callingPackage, String deviceAddress) {
27047aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla        try {
27147aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla            mServiceCallback.onDeviceSelected(
27247aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla                    //TODO is this the right userId?
27347aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla                    callingPackage, getUserId(), deviceAddress);
27447aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla        } catch (RemoteException e) {
27547aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla            Log.e(LOG_TAG, "Failed to record association: "
27647aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla                    + callingPackage + " <-> " + deviceAddress);
27747aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla        }
27847aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla    }
27947aafbe033202ccc2c0ea9af2b0f1596ebed4373Eugene Susla
280200c37f4136e0d49fd853122900d2209e424701cEugene Susla    void onCancel() {
281a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        if (DEBUG) Log.i(LOG_TAG, "onCancel()");
282200c37f4136e0d49fd853122900d2209e424701cEugene Susla        try {
283200c37f4136e0d49fd853122900d2209e424701cEugene Susla            mServiceCallback.onDeviceSelectionCancel();
284200c37f4136e0d49fd853122900d2209e424701cEugene Susla        } catch (RemoteException e) {
285200c37f4136e0d49fd853122900d2209e424701cEugene Susla            throw new RuntimeException(e);
286200c37f4136e0d49fd853122900d2209e424701cEugene Susla        }
287200c37f4136e0d49fd853122900d2209e424701cEugene Susla    }
288200c37f4136e0d49fd853122900d2209e424701cEugene Susla
28936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla    class DevicesAdapter extends ArrayAdapter<DeviceFilterPair> {
2906ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        private Drawable BLUETOOTH_ICON = icon(android.R.drawable.stat_sys_data_bluetooth);
291254b3730df8daf7d7cc1017a473dedc3ca4cec5bEugene Susla        private Drawable WIFI_ICON = icon(com.android.internal.R.drawable.ic_wifi_signal_3);
2926ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
2936ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        private Drawable icon(int drawableRes) {
2946ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            Drawable icon = getResources().getDrawable(drawableRes, null);
2956ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            icon.setTint(Color.DKGRAY);
2966ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            return icon;
2976ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
2986ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
2996ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        public DevicesAdapter() {
3006ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            super(DeviceDiscoveryService.this, 0, mDevicesFound);
3016ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
3026ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
3036ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        @Override
3046ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        public View getView(
3056ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                int position,
3066ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                @Nullable View convertView,
3076ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                @NonNull ViewGroup parent) {
3086ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            TextView view = convertView instanceof TextView
3096ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                    ? (TextView) convertView
3106ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                    : newView();
3116ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            bind(view, getItem(position));
3126ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            return view;
3136ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
3146ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
31536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        private void bind(TextView textView, DeviceFilterPair device) {
31636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla            textView.setText(device.getDisplayName());
3176ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            textView.setBackgroundColor(
3186ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                    device.equals(mSelectedDevice)
3196ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                            ? Color.GRAY
3206ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                            : Color.TRANSPARENT);
321254b3730df8daf7d7cc1017a473dedc3ca4cec5bEugene Susla            textView.setCompoundDrawablesWithIntrinsicBounds(
322254b3730df8daf7d7cc1017a473dedc3ca4cec5bEugene Susla                    device.device instanceof android.net.wifi.ScanResult
323254b3730df8daf7d7cc1017a473dedc3ca4cec5bEugene Susla                        ? WIFI_ICON
324254b3730df8daf7d7cc1017a473dedc3ca4cec5bEugene Susla                        : BLUETOOTH_ICON,
325254b3730df8daf7d7cc1017a473dedc3ca4cec5bEugene Susla                    null, null, null);
3266ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            textView.setOnClickListener((view) -> {
3276ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                mSelectedDevice = device;
3286ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                notifyDataSetChanged();
3296ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            });
3306ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
3316ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
3326ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        //TODO move to a layout file
3336ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        private TextView newView() {
3346ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            final TextView textView = new TextView(DeviceDiscoveryService.this);
3356ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            textView.setTextColor(Color.BLACK);
3366ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            final int padding = DeviceChooserActivity.getPadding(getResources());
3376ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            textView.setPadding(padding, padding, padding, padding);
3386ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            textView.setCompoundDrawablePadding(padding);
3396ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            return textView;
3406ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
3416ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
34236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla
34336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla    /**
34436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla     * A pair of device and a filter that matched this device if any.
34536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla     *
34636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla     * @param <T> device type
34736e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla     */
34836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla    static class DeviceFilterPair<T extends Parcelable> {
34936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        public final T device;
35036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        @Nullable
35136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        public final DeviceFilter<T> filter;
35236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla
35336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        private DeviceFilterPair(T device, @Nullable DeviceFilter<T> filter) {
35436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla            this.device = device;
35536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla            this.filter = filter;
35636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        }
35736e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla
35836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        /**
35936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla         * {@code (device, null)} if the filters list is empty or null
36036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla         * {@code null} if none of the provided filters match the device
36136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla         * {@code (device, filter)} where filter is among the list of filters and matches the device
36236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla         */
36336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        @Nullable
36436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        public static <T extends Parcelable> DeviceFilterPair<T> findMatch(
36536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla                T dev, @Nullable List<? extends DeviceFilter<T>> filters) {
36636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla            if (isEmpty(filters)) return new DeviceFilterPair<>(dev, null);
3673c9aa1767ca0ddc881f53c2ce7eb3135670efef3Eugene Susla            final DeviceFilter<T> matchingFilter
3683c9aa1767ca0ddc881f53c2ce7eb3135670efef3Eugene Susla                    = CollectionUtils.find(filters, f -> f.matches(dev));
3693c9aa1767ca0ddc881f53c2ce7eb3135670efef3Eugene Susla
3703c9aa1767ca0ddc881f53c2ce7eb3135670efef3Eugene Susla            DeviceFilterPair<T> result = matchingFilter != null
3713c9aa1767ca0ddc881f53c2ce7eb3135670efef3Eugene Susla                    ? new DeviceFilterPair<>(dev, matchingFilter)
3723c9aa1767ca0ddc881f53c2ce7eb3135670efef3Eugene Susla                    : null;
3733c9aa1767ca0ddc881f53c2ce7eb3135670efef3Eugene Susla            if (DEBUG) Log.i(LOG_TAG, "findMatch(dev = " + dev + ", filters = " + filters +
3743c9aa1767ca0ddc881f53c2ce7eb3135670efef3Eugene Susla                    ") -> " + result);
3753c9aa1767ca0ddc881f53c2ce7eb3135670efef3Eugene Susla            return result;
37636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        }
37736e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla
37836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        public String getDisplayName() {
37936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla            if (filter == null) {
38036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla                Preconditions.checkNotNull(device);
38136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla                if (device instanceof BluetoothDevice) {
38236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla                    return getDeviceDisplayNameInternal((BluetoothDevice) device);
38336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla                } else if (device instanceof android.net.wifi.ScanResult) {
38436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla                    return getDeviceDisplayNameInternal((android.net.wifi.ScanResult) device);
38536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla                } else if (device instanceof ScanResult) {
38636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla                    return getDeviceDisplayNameInternal(((ScanResult) device).getDevice());
38736e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla                } else {
38836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla                    throw new IllegalArgumentException("Unknown device type: " + device.getClass());
38936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla                }
39036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla            }
39136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla            return filter.getDeviceDisplayName(device);
39236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        }
39336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla
39436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        @Override
39536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        public boolean equals(Object o) {
39636e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla            if (this == o) return true;
39736e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla            if (o == null || getClass() != o.getClass()) return false;
39836e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla            DeviceFilterPair<?> that = (DeviceFilterPair<?>) o;
39936e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla            return Objects.equals(getDeviceMacAddress(device), getDeviceMacAddress(that.device));
40036e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        }
40136e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla
40236e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        @Override
40336e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        public int hashCode() {
40436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla            return Objects.hash(getDeviceMacAddress(device));
40536e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla        }
406a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla
407a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        @Override
408a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        public String toString() {
409a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            return "DeviceFilterPair{" +
410a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                    "device=" + device +
411a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                    ", filter=" + filter +
412a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                    '}';
413a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        }
414a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla    }
415a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla
416a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla    private class BLEScanCallback extends ScanCallback {
417a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla
418a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        public BLEScanCallback() {
419a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            if (DEBUG) Log.i(LOG_TAG, "new BLEScanCallback() -> " + this);
420a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        }
421a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla
422a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        @Override
423a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        public void onScanResult(int callbackType, ScanResult result) {
424a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            if (DEBUG) {
425a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                Log.i(LOG_TAG,
426a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                        "BLE.onScanResult(callbackType = " + callbackType + ", result = " + result
427a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                                + ")");
428a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            }
429a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            final DeviceFilterPair<ScanResult> deviceFilterPair
430a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                    = DeviceFilterPair.findMatch(result, mBLEFilters);
431a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            if (deviceFilterPair == null) return;
432a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) {
433a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                onDeviceLost(deviceFilterPair);
434a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            } else {
435a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                onDeviceFound(deviceFilterPair);
436a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            }
437a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        }
438a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla    }
439a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla
440a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla    private class BluetoothBroadcastReceiver extends BroadcastReceiver {
441a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        @Override
442a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        public void onReceive(Context context, Intent intent) {
443a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            if (DEBUG) {
444a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                Log.i(LOG_TAG,
445a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                        "BL.onReceive(context = " + context + ", intent = " + intent + ")");
446a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            }
447a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
448a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            final DeviceFilterPair<BluetoothDevice> deviceFilterPair
449a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                    = DeviceFilterPair.findMatch(device, mBluetoothFilters);
450a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            if (deviceFilterPair == null) return;
451a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            if (intent.getAction().equals(BluetoothDevice.ACTION_FOUND)) {
452a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                onDeviceFound(deviceFilterPair);
453a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            } else {
454a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                onDeviceLost(deviceFilterPair);
455a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            }
456a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        }
457a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla    }
458a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla
459a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla    private class WifiBroadcastReceiver extends BroadcastReceiver {
460a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        @Override
461a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        public void onReceive(Context context, Intent intent) {
462a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            if (intent.getAction().equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
463a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                List<android.net.wifi.ScanResult> scanResults = mWifiManager.getScanResults();
464a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla
465a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                if (DEBUG) {
466a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                    Log.i(LOG_TAG, "Wifi scan results: " + TextUtils.join("\n", scanResults));
467a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                }
468a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla
469a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                for (int i = 0; i < scanResults.size(); i++) {
4700c4a9266264d37e724f7372ef7ef932cf60c505cEugene Susla                    onDeviceFound(DeviceFilterPair.findMatch(scanResults.get(i), mWifiFilters));
471a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla                }
472a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla            }
473a7717e3072acad6cd6256ce3fdbb2bb94ecb06caEugene Susla        }
47436e866b8e0ec08e45b5e7fbc65aeeb3a9bb7b11eEugene Susla    }
4756ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla}
476