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