DeviceDiscoveryService.java revision d44f9334ed8b4afc08f70099c46301525b1f8d71
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
196ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayName;
206ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport static android.companion.BluetoothLEDeviceFilter.nullsafe;
216ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
226ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.annotation.NonNull;
236ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.annotation.Nullable;
246ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.app.PendingIntent;
256ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.app.Service;
266ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.BluetoothAdapter;
276ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.BluetoothDevice;
286ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.BluetoothManager;
296ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.le.BluetoothLeScanner;
306ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.le.ScanCallback;
316ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.le.ScanFilter;
326ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.le.ScanResult;
336ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.bluetooth.le.ScanSettings;
346ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.companion.AssociationRequest;
356ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.companion.BluetoothDeviceFilterUtils;
366ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.companion.BluetoothLEDeviceFilter;
376ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.companion.ICompanionDeviceManagerService;
386ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.companion.IOnAssociateCallback;
396ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.content.BroadcastReceiver;
406ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.content.Context;
416ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.content.Intent;
426ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.content.IntentFilter;
436ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.graphics.Color;
446ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.graphics.PorterDuff;
456ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.graphics.drawable.Drawable;
466ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.os.IBinder;
476ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.os.RemoteException;
486ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.util.Log;
496ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.view.View;
506ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.view.ViewGroup;
516ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.widget.ArrayAdapter;
526ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport android.widget.TextView;
536ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
546ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport java.util.ArrayList;
556ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport java.util.Collections;
566ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslaimport java.util.List;
576ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
586ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Suslapublic class DeviceDiscoveryService extends Service {
596ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
606ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private static final boolean DEBUG = false;
616ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private static final String LOG_TAG = "DeviceDiscoveryService";
626ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
636ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    static DeviceDiscoveryService sInstance;
646ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
656ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private BluetoothAdapter mBluetoothAdapter;
666ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private BluetoothLEDeviceFilter mFilter;
676ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private ScanFilter mScanFilter;
686ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private ScanSettings mDefaultScanSettings = new ScanSettings.Builder().build();
69d44f9334ed8b4afc08f70099c46301525b1f8d71Eugene Susla    AssociationRequest<?> mRequest;
706ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    List<BluetoothDevice> mDevicesFound;
716ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    BluetoothDevice mSelectedDevice;
726ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    DevicesAdapter mDevicesAdapter;
736ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    IOnAssociateCallback mCallback;
746ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    String mCallingPackage;
756ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
766ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private final ICompanionDeviceManagerService mBinder =
776ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            new ICompanionDeviceManagerService.Stub() {
786ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        @Override
796ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        public void startDiscovery(AssociationRequest request,
806ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                IOnAssociateCallback callback,
816ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                String callingPackage) throws RemoteException {
826ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            if (DEBUG) {
836ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                Log.i(LOG_TAG,
846ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                        "startDiscovery() called with: filter = [" + request + "], callback = ["
856ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                                + callback + "]");
866ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            }
876ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            mCallback = callback;
886ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            mCallingPackage = callingPackage;
896ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            DeviceDiscoveryService.this.startDiscovery(request);
906ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
916ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    };
926ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
936ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private final ScanCallback mBLEScanCallback = new ScanCallback() {
946ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        @Override
956ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        public void onScanResult(int callbackType, ScanResult result) {
966ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            final BluetoothDevice device = result.getDevice();
976ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) {
986ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                onDeviceLost(device);
996ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            } else {
1006ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                onDeviceFound(device);
1016ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            }
1026ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
1036ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    };
1046ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1056ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private BluetoothLeScanner mBLEScanner;
1066ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1076ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private BroadcastReceiver mBluetoothDeviceFoundBroadcastReceiver = new BroadcastReceiver() {
1086ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        @Override
1096ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        public void onReceive(Context context, Intent intent) {
1106ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            final BluetoothDevice device = intent.getParcelableExtra(
1116ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                    BluetoothDevice.EXTRA_DEVICE);
1126ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            if (!mFilter.matches(device)) return; // ignore device
1136ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1146ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            if (intent.getAction().equals(BluetoothDevice.ACTION_FOUND)) {
1156ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                onDeviceFound(device);
1166ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            } else {
1176ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                onDeviceLost(device);
1186ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            }
1196ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
1206ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    };
1216ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1226ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    @Override
1236ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    public IBinder onBind(Intent intent) {
1246ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        if (DEBUG) Log.i(LOG_TAG, "onBind(" + intent + ")");
1256ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        return mBinder.asBinder();
1266ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
1276ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1286ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    @Override
1296ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    public void onCreate() {
1306ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        super.onCreate();
1316ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1326ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        if (DEBUG) Log.i(LOG_TAG, "onCreate()");
1336ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1346ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mBluetoothAdapter = getSystemService(BluetoothManager.class).getAdapter();
1356ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
1366ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1376ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mDevicesFound = new ArrayList<>();
1386ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mDevicesAdapter = new DevicesAdapter();
1396ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1406ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        sInstance = this;
1416ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
1426ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1436ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private void startDiscovery(AssociationRequest<?> request) {
1446ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        //TODO support other protocols as well
145d44f9334ed8b4afc08f70099c46301525b1f8d71Eugene Susla        mRequest = request;
1466ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mFilter = nullsafe((BluetoothLEDeviceFilter) request.getDeviceFilter());
1476ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mScanFilter = mFilter.getScanFilter();
1486ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1496ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        reset();
1506ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1516ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        final IntentFilter intentFilter = new IntentFilter();
1526ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
1536ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        intentFilter.addAction(BluetoothDevice.ACTION_DISAPPEARED);
1546ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1556ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        registerReceiver(mBluetoothDeviceFoundBroadcastReceiver, intentFilter);
1566ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mBluetoothAdapter.startDiscovery();
1576ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1586ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mBLEScanner.startScan(
1596ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                Collections.singletonList(mScanFilter), mDefaultScanSettings, mBLEScanCallback);
1606ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
1616ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1626ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private void reset() {
1636ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mDevicesFound.clear();
1646ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mSelectedDevice = null;
1656ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mDevicesAdapter.notifyDataSetChanged();
1666ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
1676ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1686ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    @Override
1696ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    public boolean onUnbind(Intent intent) {
1706ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        stopScan();
1716ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        return super.onUnbind(intent);
1726ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
1736ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1746ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private void stopScan() {
1756ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        if (DEBUG) Log.i(LOG_TAG, "stopScan() called");
1766ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mBluetoothAdapter.cancelDiscovery();
1776ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mBLEScanner.stopScan(mBLEScanCallback);
1786ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        unregisterReceiver(mBluetoothDeviceFoundBroadcastReceiver);
1796ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        stopSelf();
1806ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
1816ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1826ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private void onDeviceFound(BluetoothDevice device) {
1836ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        if (mDevicesFound.contains(device)) {
1846ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            return;
1856ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
1866ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1876ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        if (DEBUG) {
1886ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            Log.i(LOG_TAG, "Considering device " + getDeviceDisplayName(device));
1896ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
1906ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1916ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        if (!mFilter.matches(device)) {
1926ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            return;
1936ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
1946ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
1956ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        if (DEBUG) {
1966ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            Log.i(LOG_TAG, "Found device " + getDeviceDisplayName(device));
1976ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
1986ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        if (mDevicesFound.isEmpty()) {
1996ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            onReadyToShowUI();
2006ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
2016ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mDevicesFound.add(device);
2026ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mDevicesAdapter.notifyDataSetChanged();
2036ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
2046ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
2056ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    //TODO also, on timeout -> call onFailure
2066ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private void onReadyToShowUI() {
2076ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        try {
2086ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            mCallback.onSuccess(PendingIntent.getActivity(
2096ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                    this, 0,
2106ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                    new Intent(this, DeviceChooserActivity.class),
2116ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                    PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT
2126ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                            | PendingIntent.FLAG_IMMUTABLE));
2136ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        } catch (RemoteException e) {
2146ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            throw new RuntimeException(e);
2156ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
2166ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
2176ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
2186ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    private void onDeviceLost(BluetoothDevice device) {
2196ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mDevicesFound.remove(device);
2206ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        mDevicesAdapter.notifyDataSetChanged();
2216ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        if (DEBUG) {
2226ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            Log.i(LOG_TAG, "Lost device " + getDeviceDisplayName(device));
2236ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
2246ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
2256ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
2266ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    class DevicesAdapter extends ArrayAdapter<BluetoothDevice> {
2276ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        private Drawable BLUETOOTH_ICON = icon(android.R.drawable.stat_sys_data_bluetooth);
2286ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
2296ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        private Drawable icon(int drawableRes) {
2306ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            Drawable icon = getResources().getDrawable(drawableRes, null);
2316ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            icon.setTint(Color.DKGRAY);
2326ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            return icon;
2336ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
2346ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
2356ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        public DevicesAdapter() {
2366ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            super(DeviceDiscoveryService.this, 0, mDevicesFound);
2376ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
2386ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
2396ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        @Override
2406ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        public View getView(
2416ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                int position,
2426ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                @Nullable View convertView,
2436ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                @NonNull ViewGroup parent) {
2446ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            TextView view = convertView instanceof TextView
2456ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                    ? (TextView) convertView
2466ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                    : newView();
2476ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            bind(view, getItem(position));
2486ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            return view;
2496ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
2506ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
2516ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        private void bind(TextView textView, BluetoothDevice device) {
2526ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            textView.setText(getDeviceDisplayName(device));
2536ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            textView.setBackgroundColor(
2546ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                    device.equals(mSelectedDevice)
2556ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                            ? Color.GRAY
2566ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                            : Color.TRANSPARENT);
2576ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            textView.setOnClickListener((view) -> {
2586ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                mSelectedDevice = device;
2596ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                notifyDataSetChanged();
2606ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            });
2616ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
2626ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla
2636ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        //TODO move to a layout file
2646ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        private TextView newView() {
2656ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            final TextView textView = new TextView(DeviceDiscoveryService.this);
2666ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            textView.setTextColor(Color.BLACK);
2676ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            final int padding = DeviceChooserActivity.getPadding(getResources());
2686ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            textView.setPadding(padding, padding, padding, padding);
2696ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            textView.setCompoundDrawablesWithIntrinsicBounds(
2706ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla                    BLUETOOTH_ICON, null, null, null);
2716ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            textView.setCompoundDrawablePadding(padding);
2726ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla            return textView;
2736ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla        }
2746ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla    }
2756ed45d8cd33c297e608aba94fc1f61dace7a7ccaEugene Susla}
276