165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane/*
265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Copyright (C) 2014 The Android Open Source Project
365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *
465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Licensed under the Apache License, Version 2.0 (the "License");
565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * you may not use this file except in compliance with the License.
665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * You may obtain a copy of the License at
765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *
865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *      http://www.apache.org/licenses/LICENSE-2.0
965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *
1065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Unless required by applicable law or agreed to in writing, software
1165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * distributed under the License is distributed on an "AS IS" BASIS,
1265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * See the License for the specific language governing permissions and
1465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * limitations under the License.
1565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane */
1665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
1765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lanepackage com.android.tv.settings.util.bluetooth;
1865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
1965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.bluetooth.BluetoothAdapter;
2065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.bluetooth.BluetoothDevice;
2165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.content.BroadcastReceiver;
2265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.content.Context;
2365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.content.Intent;
2465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.content.IntentFilter;
2565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.os.Handler;
2665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.util.Log;
2765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
286e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantlerimport java.util.ArrayList;
29510203ee5e3350758dae4bf9af05b1585029af7dWally Yauimport java.util.List;
306e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler
3165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane/**
3265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Listens for unconfigured or problematic devices to show up on
3365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * bluetooth and returns lists of them.  Also manages their colors.
3465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane */
3565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lanepublic class BluetoothScanner {
3665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final String TAG = "BluetoothScanner";
3765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final boolean DEBUG = false;
3865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
3965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final int FOUND_ON_SCAN = -1;
4065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final int CONSECUTIVE_MISS_THRESHOLD = 4;
4165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final int FAILED_SETTING_NAME = CONSECUTIVE_MISS_THRESHOLD + 1;
4265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final int SCAN_DELAY = 4000;
4365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
4465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static Receiver sReceiver;
4565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
4665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static class Device {
4765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public BluetoothDevice btDevice;
4865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public String address;
4965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public String btName;
5065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public String name = "";
5165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public LedConfiguration leds;
5265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public int consecutiveMisses;
5365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // the type of configuration this device needs, or -1 if the device does not
5465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // specify a configuration type
5565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public int configurationType = 0;
5665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
5765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
5865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public String toString() {
5965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            StringBuilder str = new StringBuilder();
6065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            str.append("Device(addr=");
6165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            str.append(address);
6265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            str.append(" name=\"");
6365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            str.append(name);
6465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            str.append("\" leds=");
6565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            str.append(leds);
6665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            str.append("\" configuration_type=");
6765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            str.append(configurationType);
6865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            str.append(")");
6965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return str.toString();
7065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
7165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
7265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public String getNameString() {
7365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return String.format("\"%s\" (%s)", this.name,
7465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    this.leds == null ? "" : this.leds.getNameString());
7565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
7665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
7765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public boolean setNameString(String str) {
7865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            this.btName = str;
7965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (str == null || !BluetoothNameUtils.isValidName(str)) {
8065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                this.name = "";
8165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                this.leds = null;
8265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return false;
8365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
8465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
8565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            this.leds = BluetoothNameUtils.getColorConfiguration(str);
8665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            this.configurationType = BluetoothNameUtils.getSetupType(str);
8765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return true;
8865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
8965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
9065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public boolean hasConfigurationType() {
9165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return configurationType != 0;
9265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
9365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
9465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
9565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static class Listener {
9665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void onScanningStarted() {
9765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
9865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void onScanningStopped(ArrayList<Device> devices) {
9965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
10065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void onDeviceAdded(Device device) {
10165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
10265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void onDeviceChanged(Device device) {
10365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
10465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void onDeviceRemoved(Device device) {
10565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
10665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
10765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
10865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private BluetoothScanner() {
10965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        throw new RuntimeException("do not instantiate");
11065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
11165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
11265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
11365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Starts listening.  Will call onto listener with any devices we have
11465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * cached before this call returns.
11565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
11665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static void startListening(Context context, Listener listener,
117510203ee5e3350758dae4bf9af05b1585029af7dWally Yau            List<BluetoothDeviceCriteria> criteria) {
11865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (sReceiver == null) {
11965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            sReceiver = new Receiver(context.getApplicationContext());
12065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
12165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        sReceiver.startListening(listener, criteria);
12265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        Log.d(TAG, "startListening");
12365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
12465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
12565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
12665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Removes the listener now, so there will be no more callbacks, but
12765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * leaves the scan running for 20 seconds to keep the cache warm just
12865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * in case it's needed again.
12965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
1300678a03fc59624074a7d739d6e934f1a45c63d2cTony Mantler    public static boolean stopListening(Listener listener) {
13165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        Log.d(TAG, "stopListening sReceiver=" + sReceiver);
13265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (sReceiver != null) {
1330678a03fc59624074a7d739d6e934f1a45c63d2cTony Mantler            return sReceiver.stopListening(listener);
13465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
1350678a03fc59624074a7d739d6e934f1a45c63d2cTony Mantler        return false;
13665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
13765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
13865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
13965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Initiates a scan right now.
14065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
14165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static void scanNow() {
14265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (sReceiver != null) {
14365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            sReceiver.scanNow();
14465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
14565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
14665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
14765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static void stopNow() {
14865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (sReceiver != null) {
14965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            sReceiver.stopNow();
15065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
15165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
15265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
15365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static void removeDevice(Device device) {
15465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        removeDevice(device.address);
15565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
15665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
15765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static void removeDevice(String btAddress) {
15865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (sReceiver != null) {
15965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            sReceiver.removeDevice(btAddress);
16065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
16165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
16265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
16365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static class ClientRecord {
1646e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler        public final Listener listener;
1656e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler        public final ArrayList<Device> devices;
166510203ee5e3350758dae4bf9af05b1585029af7dWally Yau        public final List<BluetoothDeviceCriteria> matchers;
16765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
168510203ee5e3350758dae4bf9af05b1585029af7dWally Yau        public ClientRecord(Listener listener, List<BluetoothDeviceCriteria> matchers) {
16965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            this.listener = listener;
1706e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler            devices = new ArrayList<>();
171510203ee5e3350758dae4bf9af05b1585029af7dWally Yau            this.matchers = matchers;
17265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
17365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
17465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
17565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static class Receiver extends BroadcastReceiver {
17665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private final Handler mHandler = new Handler();
17765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // TODO mListenerLock should probably now protect mClients
1786e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler        private final ArrayList<ClientRecord> mClients = new ArrayList<>();
1796e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler        private final ArrayList<Device> mPresentDevices = new ArrayList<>();
1806e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler        private final Context mContext;
1816e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler        private final BluetoothAdapter mBtAdapter;
18265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private static boolean mKeepScanning;
18365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private boolean mRegistered = false;
1846e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler        private final Object mListenerLock = new Object();
18565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
18665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public Receiver(Context context) {
18765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mContext = context;
18865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
18965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // Bluetooth
19065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mBtAdapter = BluetoothAdapter.getDefaultAdapter();
19165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
19265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
19365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        /**
19465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane         * @param listener
195510203ee5e3350758dae4bf9af05b1585029af7dWally Yau         * @param matchers Pattern matchers to determine whether this listener
19665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane         * will be notified about changes in status of a discovered device. Note
19765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane         * that the matcher is only run against the device when the device is
19865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane         * first discovered, not each time it appears in scan results. Device
19965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane         * properties are assumed to be stable.
20065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane         */
201510203ee5e3350758dae4bf9af05b1585029af7dWally Yau        public void startListening(Listener listener, List<BluetoothDeviceCriteria> matchers) {
20265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int size = 0;
203510203ee5e3350758dae4bf9af05b1585029af7dWally Yau            ClientRecord newClient = new ClientRecord(listener, matchers);
20465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            synchronized (mListenerLock) {
20565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (int ptr = mClients.size() - 1; ptr > -1; ptr--) {
20665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (mClients.get(ptr).listener == listener) {
20765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        throw new RuntimeException("Listener already registered: " + listener);
20865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
20965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
21065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
21165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // Save this listener in the list
21265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mClients.add(newClient);
21365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                size = mClients.size();
21465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
21565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
21665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // Register for broadcasts when a device is discovered
21765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // and broadcasts when discovery has finished
21865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (size == 1) {
21965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mPresentDevices.clear();
22065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
22165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
22265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mContext.registerReceiver(this, filter);
22365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mRegistered = true;
22465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
22565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
22665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // Keep retrying until we say stop
22765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mKeepScanning = true;
22865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
22965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // Call back with the ones we have already
23065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            final int N = mPresentDevices.size();
23165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            for (int i=0; i<N; i++) {
23265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                Device target = mPresentDevices.get(i);
233510203ee5e3350758dae4bf9af05b1585029af7dWally Yau                for (BluetoothDeviceCriteria matcher : newClient.matchers) {
234510203ee5e3350758dae4bf9af05b1585029af7dWally Yau                    if (matcher.isMatchingDevice(target.btDevice)) {
235510203ee5e3350758dae4bf9af05b1585029af7dWally Yau                        newClient.devices.add(target);
236510203ee5e3350758dae4bf9af05b1585029af7dWally Yau                        newClient.listener.onDeviceAdded(target);
237510203ee5e3350758dae4bf9af05b1585029af7dWally Yau                        break;
238510203ee5e3350758dae4bf9af05b1585029af7dWally Yau                    }
23965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
24065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
24165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
24265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // If we have a pending stop, cancel that.
24365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mHandler.removeCallbacks(mStopTask);
24465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
24565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // If there is a pending scan, we'll do one now, so we can scan any
24665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // pending ones.
24765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mHandler.removeCallbacks(mScanTask);
24865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
24965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            scanNow();
25065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
25165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
2520678a03fc59624074a7d739d6e934f1a45c63d2cTony Mantler        public boolean stopListening(Listener listener) {
2536e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler            final int size;
2540678a03fc59624074a7d739d6e934f1a45c63d2cTony Mantler            boolean stopped = false;
25565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            synchronized (mListenerLock) {
25665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (int ptr = mClients.size() - 1; ptr > -1; ptr--) {
25765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    ClientRecord client = mClients.get(ptr);
25865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (client.listener == listener) {
25965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        mClients.remove(ptr);
2600678a03fc59624074a7d739d6e934f1a45c63d2cTony Mantler                        stopped = true;
26165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
26265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
26365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
26465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                size = mClients.size();
26565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
26665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (size == 0) {
26765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mHandler.removeCallbacks(mStopTask);
26865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mHandler.postDelayed(mStopTask, 20 * 1000 /* ms */);
26965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
2700678a03fc59624074a7d739d6e934f1a45c63d2cTony Mantler            return stopped;
27165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
27265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
27365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void scanNow() {
27465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // If we're already discovering, stop it.
27565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mBtAdapter.isDiscovering()) {
27665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mBtAdapter.cancelDiscovery();
27765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
27865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
27965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            sendScanningStarted();
28065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
28165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // Request discover from BluetoothAdapter
28265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mBtAdapter.startDiscovery();
28365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
28465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
28565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void stopNow() {
2866e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler            final int size;
28765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            synchronized (mListenerLock) {
28865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                size = mClients.size();
28965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
29065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (size == 0) {
29165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                Log.d(TAG, "mStopTask.run()");
29265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
29365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // cancel any pending scans
29465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mHandler.removeCallbacks(mScanTask);
29565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
29665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // If there is a pending stop, cancel it
29765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mHandler.removeCallbacks(mStopTask);
29865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
29965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // Make sure we're not doing discovery anymore
30065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (mBtAdapter != null) {
30165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mBtAdapter.cancelDiscovery();
30265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
30365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
30465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // shut down discovery and prevent it from restarting
30565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mKeepScanning = false;
30665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
30765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // if the Bluetooth adapter is enabled, we're listening for discovery events and
30865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // should stop
30965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (BluetoothAdapter.getDefaultAdapter().isEnabled() && mRegistered) {
31065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mContext.unregisterReceiver(Receiver.this);
31165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mRegistered = false;
31265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
31365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
31465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
31565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
31665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void removeDevice(String btAddress) {
31765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int count = mPresentDevices.size();
31865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            for (int i = 0; i < count; i++) {
31965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                Device d = mPresentDevices.get(i);
32065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (btAddress.equals(d.address)) {
32165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mPresentDevices.remove(d);
32265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    break;
32365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
32465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
32565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
32665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            for (int ptr = mClients.size() - 1; ptr > -1; ptr--) {
32765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                ClientRecord client = mClients.get(ptr);
32865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (int devPtr = client.devices.size() - 1; devPtr > -1; devPtr--) {
32965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    Device d = client.devices.get(devPtr);
33065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (btAddress.equals(d.address)) {
33165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        client.devices.remove(devPtr);
33265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
33365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
33465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
33565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
33665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
33765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
33865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private final Runnable mStopTask = new Runnable() {
33965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            @Override
34065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            public void run() {
34165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                synchronized (mListenerLock) {
34265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (mClients.size() != 0) {
34365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        throw new RuntimeException("mStopTask running with mListeners.size="
34465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                + mClients.size());
34565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
34665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
34765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                stopNow();
34865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
34965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        };
35065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
35165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private final Runnable mScanTask = new Runnable() {
35265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            @Override
35365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            public void run() {
35465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // If there is a pending scan request, cancel it
35565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mHandler.removeCallbacks(mScanTask);
35665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
35765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                scanNow();
35865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
35965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        };
36065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
36165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
36265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void onReceive(Context context, Intent intent) {
36365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            final String action = intent.getAction();
36465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
36565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
36665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
36765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // When discovery finds a device
36865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
36965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // Get the BluetoothDevice object from the Intent
37065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
37165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                final String address = btDevice.getAddress();
37265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                String name = btDevice.getName();
37365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
37465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (DEBUG) {
37565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    Log.d(TAG, "Device found, address: " + address + " name: \"" + name + "\"");
37665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
37765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
37865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (address == null || name == null) {
37965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    return;
38065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
38165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
38265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // Older Bluetooth stacks may append a null character to a device name
38365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (name.endsWith("\0")) {
38465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    name = name.substring(0, name.length() - 1);
38565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
38665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
38765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // See if this is a device we already know about
38865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                Device device = null;
38965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                final int N = mPresentDevices.size();
39065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (int i=0; i<N; i++) {
39165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    final Device d = mPresentDevices.get(i);
39265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (address.equals(d.address)) {
39365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        device = d;
39465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
39565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
39665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
39765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
39865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (device == null) {
39965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (DEBUG) {
40065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        Log.d(TAG, "Device is a new device.");
40165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
40265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    // New device.
40365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    device = new Device();
40465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    device.btDevice = btDevice;
40565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    device.address = address;
40665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    device.consecutiveMisses = -1;
40765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
40865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    device.setNameString(name);
409510203ee5e3350758dae4bf9af05b1585029af7dWally Yau                    // Save it
410510203ee5e3350758dae4bf9af05b1585029af7dWally Yau                    mPresentDevices.add(device);
41165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
412510203ee5e3350758dae4bf9af05b1585029af7dWally Yau                    // Tell the listeners
413510203ee5e3350758dae4bf9af05b1585029af7dWally Yau                    sendDeviceAdded(device);
41465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } else {
41565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (DEBUG) {
41665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        Log.d(TAG, "Device is an existing device.");
41765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
41865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    // Existing device: update miss count.
41965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    device.consecutiveMisses = FOUND_ON_SCAN;
42065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (device.btName == name
42165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            || (device.btName != null && device.btName.equals(name))) {
42265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        // Name hasn't changed
42365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        return;
42465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    } else {
42565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        device.setNameString(name);
42665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        sendDeviceChanged(device);
42765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        // If we can't parse it properly, treat it as a delete
42865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        // when we iterate through them again.
42965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
43065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
43165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
43265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // Clear any devices that have disappeared since the last scan completed
43365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                final int N = mPresentDevices.size();
43465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (int i=N-1; i>=0; i--) {
43565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    Device device = mPresentDevices.get(i);
43665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (device.consecutiveMisses < 0) {
43765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        // -1 means found on this scan, raise to 0 for next time
43865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        if (DEBUG) Log.d(TAG, device.address + " -- Found");
43965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        device.consecutiveMisses = 0;
44065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
44165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    } else if (device.consecutiveMisses >= CONSECUTIVE_MISS_THRESHOLD) {
44265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        // Too many failures
44365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        if (DEBUG) Log.d(TAG, device.address + " -- Removing");
44465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        mPresentDevices.remove(i);
44565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        sendDeviceRemoved(device);
44665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
44765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    } else {
44865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        // Didn't see it this time, but not ready to delete it yet
44965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        device.consecutiveMisses++;
45065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        if (DEBUG) {
45165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            Log.d(TAG, device.address + " -- Missed consecutiveMisses="
45265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                    + device.consecutiveMisses);
45365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        }
45465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
45565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
45665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
45765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // Show status when scanning is completed.
45865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                sendScanningStopped();
45965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
46065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (mKeepScanning) {
46165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    // Try again in SCAN_DELAY ms.
46265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mHandler.postDelayed(mScanTask, SCAN_DELAY);
46365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
46465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
46565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
46665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
46765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private void sendScanningStarted() {
46865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            synchronized (mListenerLock) {
46965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                final int N = mClients.size();
47065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (int i = 0; i < N; i++) {
47165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mClients.get(i).listener.onScanningStarted();
47265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
47365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
47465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
47565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
47665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private void sendScanningStopped() {
47765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            synchronized (mListenerLock) {
47865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                final int N = mClients.size();
47965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // Loop backwards through the list in case a client wants to
48065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // remove its listener in this callback.
48165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (int i = N - 1; i >= 0; --i) {
48265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    ClientRecord client = mClients.get(i);
48365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    client.listener.onScanningStopped(client.devices);
48465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
48565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
48665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
48765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
48865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private void sendDeviceAdded(Device device) {
48965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            synchronized (mListenerLock) {
49065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (int ptr = mClients.size() - 1; ptr > -1; ptr--) {
49165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    ClientRecord client = mClients.get(ptr);
492510203ee5e3350758dae4bf9af05b1585029af7dWally Yau                    for (BluetoothDeviceCriteria matcher : client.matchers) {
493510203ee5e3350758dae4bf9af05b1585029af7dWally Yau                        if (matcher.isMatchingDevice(device.btDevice)) {
494510203ee5e3350758dae4bf9af05b1585029af7dWally Yau                            client.devices.add(device);
495510203ee5e3350758dae4bf9af05b1585029af7dWally Yau                            client.listener.onDeviceAdded(device);
496510203ee5e3350758dae4bf9af05b1585029af7dWally Yau                            break;
497510203ee5e3350758dae4bf9af05b1585029af7dWally Yau                        }
49865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
49965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
50065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
50165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
50265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
50365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private void sendDeviceChanged(Device device) {
50465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            synchronized (mListenerLock) {
50565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                final int N = mClients.size();
50665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (int i = 0; i < N; i++) {
50765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    ClientRecord client = mClients.get(i);
50865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    for (int ptr = client.devices.size() - 1; ptr > -1; ptr--) {
50965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        Device d = client.devices.get(ptr);
51065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        if (d.btDevice.getAddress().equals(device.btDevice.getAddress())) {
51165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            client.listener.onDeviceChanged(device);
51265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            break;
51365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        }
51465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
51565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
51665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
51765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
51865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
51965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private void sendDeviceRemoved(Device device) {
52065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            synchronized (mListenerLock) {
52165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (int ptr = mClients.size() - 1; ptr > -1; ptr--) {
52265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    ClientRecord client = mClients.get(ptr);
52365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    for (int devPtr = client.devices.size() - 1; devPtr > -1; devPtr--) {
52465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        Device d = client.devices.get(devPtr);
52565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        if (d.btDevice.getAddress().equals(device.btDevice.getAddress())) {
52665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            client.devices.remove(devPtr);
52765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            client.listener.onDeviceRemoved(device);
52865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            break;
52965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        }
53065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
53165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
53265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
53365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
53465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
53565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane}
536