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.accessories;
1865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
1965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.bluetooth.BluetoothAdapter;
2065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.bluetooth.BluetoothDevice;
2165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.bluetooth.BluetoothInputDevice;
2265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.bluetooth.BluetoothProfile;
2365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.content.BroadcastReceiver;
2465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.content.Context;
2565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.content.Intent;
2665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.content.IntentFilter;
2765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.hardware.input.InputManager;
2865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.os.Handler;
2965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.os.Message;
3065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.os.SystemClock;
3165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.util.Log;
3265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.view.InputDevice;
3365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
3465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport com.android.tv.settings.util.bluetooth.BluetoothScanner;
3565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport com.android.tv.settings.R;
3665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
3765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport java.util.ArrayList;
3865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport java.util.List;
3965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
4065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane/**
4165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Monitors available Bluetooth input devices and manages process of pairing
4265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * and connecting to the device.
4365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane */
4465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lanepublic class InputPairer {
4565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
4665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
4765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * This class operates in two modes, automatic and manual.
4865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     *
4965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * AUTO MODE
5065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * In auto mode we listen for an input device that looks like it can
5165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * generate DPAD events. When one is found we wait
5265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * {@link #DELAY_AUTO_PAIRING} milliseconds before starting the process of
5365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * connecting to the device. The idea is that a UI making use of this class
5465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * would give the user a chance to cancel pairing during this window. Once
5565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * the connection process starts, it is considered uninterruptible.
5665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     *
5765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Connection is accomplished in two phases, bonding and socket connection.
5865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * First we try to create a bond to the device and listen for bond status
5965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * change broadcasts. Once the bond is made, we connect to the device.
6065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Connecting to the device actually opens a socket and hooks the device up
6165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * to the input system.
6265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     *
6365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * In auto mode if we see more than one compatible input device before
6465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * bonding with a candidate device, we stop the process. We don't want to
6565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * connect to the wrong device and it is up to the user of this class to
6665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * tell us what to connect to.
6765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     *
6865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * MANUAL MODE
6965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Manual mode is where a user of this class explicitly tells us which
7065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * device to connect to. To switch to manual mode you can call
7165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * {@link #cancelPairing()}. It is safe to call this method even if no
7265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * device connection process is underway. You would then call
7365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * {@link #start()} to resume scanning for devices. Once one is found
7465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * that you want to connect to, call {@link #startPairing(BluetoothDevice)}
7565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * to start the connection process. At this point the same process is
7665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * followed as when we start connection in auto mode.
7765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     *
7865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Even in manual mode there is a timeout before we actually start
7965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * connecting, but it is {@link #DELAY_MANUAL_PAIRING}.
8065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
8165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
8265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final String TAG = "aah.InputPairer";
8365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final int STATUS_ERROR = -1;
8465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final int STATUS_NONE = 0;
8565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final int STATUS_SCANNING = 1;
8665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
8765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * A device to pair with has been identified, we're currently in the
8865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * timeout period where the process can be cancelled.
8965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
9065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final int STATUS_WAITING_TO_PAIR = 2;
9165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
9265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Pairing is in progress.
9365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
9465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final int STATUS_PAIRING = 3;
9565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
9665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Device has been paired with, we are opening a connection to the device.
9765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
9865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final int STATUS_CONNECTING = 4;
9965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
10065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
10165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public interface EventListener {
10265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        /**
10365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane         * The status of the {@link InputPairer} changed.
10465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane         */
10565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void statusChanged();
10665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
10765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
10865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
10965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Time between when a single input device is found and pairing begins. If
11065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * one or more other input devices are found before this timeout or
11165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * {@link #cancelPairing()} is called then pairing will not proceed.
11265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
11365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final int DELAY_AUTO_PAIRING = 15 * 1000;
11465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
11565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Time between when the call to {@link #startPairing(BluetoothDevice)} is
11665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * called and when we actually start pairing. This gives the caller a
11765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * chance to change their mind.
11865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
11965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final int DELAY_MANUAL_PAIRING = 5 * 1000;
12065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
12165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * If there was an error in pairing, we will wait this long before trying
12265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * again.
12365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
12465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final int DELAY_RETRY = 5 * 1000;
12565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
12665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final int MSG_PAIR = 1;
12765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final int MSG_START = 2;
12865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
12965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final boolean DEBUG = true;
13065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
13165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final String[] INVALID_INPUT_KEYBOARD_DEVICE_NAMES = {
13265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        "gpio-keypad", "cec_keyboard", "Virtual", "athome_remote"
13365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    };
13465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
13565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private BluetoothScanner.Listener mBtListener = new BluetoothScanner.Listener() {
13665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
13765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void onDeviceAdded(BluetoothScanner.Device device) {
13865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (DEBUG) {
13965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                Log.d(TAG, "Adding device: " + device.btDevice.getAddress());
14065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
14165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            onDeviceFound(device.btDevice);
14265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
14365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
14465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
14565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void onDeviceRemoved(BluetoothScanner.Device device) {
14665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (DEBUG) {
14765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                Log.d(TAG, "Device lost: " + device.btDevice.getAddress());
14865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
14965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            onDeviceLost(device.btDevice);
15065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
15165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    };
15265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
15365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static boolean hasValidInputDevice(Context context, int[] deviceIds) {
15465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        InputManager inMan = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
15565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
15665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int ptr = deviceIds.length - 1; ptr > -1; ptr--) {
15765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            InputDevice device = inMan.getInputDevice(deviceIds[ptr]);
15865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int sources = device.getSources();
15965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
16065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            boolean isCompatible = false;
16165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
16265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
16365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                isCompatible = true;
16465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
16565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
16665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
16765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                isCompatible = true;
16865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
16965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
17065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if ((sources & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD) {
17165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                boolean isValidKeyboard = true;
17265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                String keyboardName = device.getName();
17365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (int index = 0; index < INVALID_INPUT_KEYBOARD_DEVICE_NAMES.length; ++index) {
17465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (keyboardName.equals(INVALID_INPUT_KEYBOARD_DEVICE_NAMES[index])) {
17565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        isValidKeyboard = false;
17665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
17765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
17865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
17965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
18065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (isValidKeyboard) {
18165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    isCompatible = true;
18265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
18365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
18465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
18565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (!device.isVirtual() && isCompatible) {
18665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return true;
18765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
18865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
18965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return false;
19065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
19165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
19265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static boolean hasValidInputDevice(Context context) {
19365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        InputManager inMan = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
19465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int[] inputDevices = inMan.getInputDeviceIds();
19565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
19665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return hasValidInputDevice(context, inputDevices);
19765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
19865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
19965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private BroadcastReceiver mLinkStatusReceiver = new BroadcastReceiver() {
20065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
20165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void onReceive(Context context, Intent intent) {
20265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            BluetoothDevice device = (BluetoothDevice) intent.getParcelableExtra(
20365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    BluetoothDevice.EXTRA_DEVICE);
20465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (DEBUG) {
20565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                Log.d(TAG, "There was a link status change for: " + device.getAddress());
20665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
20765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
20865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (device.equals(mTarget)) {
20965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
21065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        BluetoothDevice.BOND_NONE);
21165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int previousBondState = intent.getIntExtra(
21265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, BluetoothDevice.BOND_NONE);
21365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
21465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (DEBUG) {
21565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    Log.d(TAG, "Bond states: old = " + previousBondState + ", new = " +
21665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        bondState);
21765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
21865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
21965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (bondState == BluetoothDevice.BOND_NONE &&
22065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        previousBondState == BluetoothDevice.BOND_BONDING) {
22165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    // we seem to have reverted, this is an error
22265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    // TODO inform user, start scanning again
22365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    unregisterLinkStatusReceiver();
22465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    onBondFailed();
22565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } else if (bondState == BluetoothDevice.BOND_BONDED) {
22665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    unregisterLinkStatusReceiver();
22765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    onBonded();
22865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
22965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
23065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
23165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    };
23265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
23365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private BluetoothProfile.ServiceListener mServiceConnection =
23465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            new BluetoothProfile.ServiceListener() {
23565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
23665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
23765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void onServiceDisconnected(int profile) {
23865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // TODO handle unexpected disconnection
23965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            Log.w(TAG, "Service disconected, perhaps unexpectedly");
24065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
24165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
24265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
24365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void onServiceConnected(int profile, BluetoothProfile proxy) {
24465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (DEBUG) {
24565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                Log.d(TAG, "Connection made to bluetooth proxy.");
24665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
24765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mInputProxy = (BluetoothInputDevice) proxy;
24865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mTarget != null) {
24965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                registerInputMethodMonitor();
25065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (DEBUG) {
25165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    Log.d(TAG, "Connecting to target: " + mTarget.getAddress());
25265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
25365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // TODO need to start a timer, otherwise if the connection fails we might be
25465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // stuck here forever
25565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mInputProxy.connect(mTarget);
25665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
25765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // must set PRIORITY_AUTO_CONNECT or auto-connection will not
25865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // occur, however this setting does not appear to be sticky
25965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // across a reboot
26065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mInputProxy.setPriority(mTarget, BluetoothProfile.PRIORITY_AUTO_CONNECT);
26165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
26265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
26365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    };
26465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
26565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private InputManager.InputDeviceListener mInputListener =
26665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            new InputManager.InputDeviceListener() {
26765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
26865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void onInputDeviceRemoved(int deviceId) {
26965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // ignored
27065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
27165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
27265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
27365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void onInputDeviceChanged(int deviceId) {
27465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // ignored
27565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
27665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
27765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
27865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void onInputDeviceAdded(int deviceId) {
27965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane           if (hasValidInputDevice(mContext, new int[] {deviceId})) {
28065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane               onInputAdded();
28165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane           }
28265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
28365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    };
28465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
28565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private Runnable mStartRunnable = new Runnable() {
28665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
28765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void run() {
28865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            start();
28965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
29065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    };
29165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
29265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private Context mContext;
29365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private EventListener mListener;
29465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mStatus = STATUS_NONE;
29565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
29665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Set to {@code false} when {@link #cancelPairing()} or
29765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * {@link #startPairing(BluetoothDevice)} or
29865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * {@link #startPairing(BluetoothDevice, int)} is called. This instance
29965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * will now no longer automatically start pairing.
30065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
30165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean mAutoMode = true;
30265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private ArrayList<BluetoothDevice> mVisibleDevices = new ArrayList<BluetoothDevice>();
30365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private BluetoothDevice mTarget;
30465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private Handler mHandler;
30565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private BluetoothInputDevice mInputProxy;
30665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private long mNextStageTimestamp = -1;
30765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean mLinkReceiverRegistered = false;
30865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
30965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
31065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Should be instantiated on a thread with a Looper, perhaps the main thread!
31165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
31265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public InputPairer(Context context, EventListener listener) {
31365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mContext = context.getApplicationContext();
31465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mListener = listener;
31565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mHandler = new Handler() {
31665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            @Override
31765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            public void handleMessage(Message msg) {
31865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                switch (msg.what) {
31965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    case MSG_PAIR:
32065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        startBonding();
32165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
32265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    case MSG_START:
32365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        start();
32465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        break;
32565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    default:
32665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        Log.d(TAG, "No handler case available for message: " + msg.what);
32765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
32865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
32965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        };
33065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
33165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
33265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
33365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Start listening for devices and begin the pairing process when
33465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * criteria is met.
33565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
33665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void start() {
33765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // TODO instead of this, register a broadcast receiver to listen to
33865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // Bluetooth state
33965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (!BluetoothAdapter.getDefaultAdapter().isEnabled()) {
34065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            Log.d(TAG, "Bluetooth not enabled, delaying startup.");
34165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mHandler.removeCallbacks(mStartRunnable);
34265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mHandler.postDelayed(mStartRunnable, 1000);
34365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
34465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
34565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
34665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // set status to scanning before we start listening since
34765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // startListening may result in a transition to STATUS_WAITING_TO_PAIR
34865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // which might seem odd from a client perspective
34965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setStatus(STATUS_SCANNING);
35065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
35165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        BluetoothScanner.stopListening(mBtListener);
35265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        BluetoothScanner.startListening(mContext, mBtListener, new InputDeviceCriteria());
35365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
35465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
3558645fb6706a37e7442d95c3b2971c934bfa6e07aWilliam Bourke    public void clearDeviceList() {
3568645fb6706a37e7442d95c3b2971c934bfa6e07aWilliam Bourke        doCancel();
3578645fb6706a37e7442d95c3b2971c934bfa6e07aWilliam Bourke        mVisibleDevices.clear();
3588645fb6706a37e7442d95c3b2971c934bfa6e07aWilliam Bourke    }
3598645fb6706a37e7442d95c3b2971c934bfa6e07aWilliam Bourke
36065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
36165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Stop any pairing request that is in progress.
36265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
36365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void cancelPairing() {
36465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mAutoMode = false;
36565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        doCancel();
36665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
36765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
36865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
36965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Stop doing anything we're doing, release any resources.
37065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
37165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void dispose() {
37265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mHandler.removeCallbacksAndMessages(null);
37365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mLinkReceiverRegistered) {
37465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            unregisterLinkStatusReceiver();
37565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
37665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        stopScanning();
37765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
37865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
37965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
38065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Start pairing and connection to the specified device.
38165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * @param device
38265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
38365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void startPairing(BluetoothDevice device) {
38465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        startPairing(device, DELAY_MANUAL_PAIRING);
38565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
38665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
38765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
38865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * See {@link #startPairing(BluetoothDevice)}.
38965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * @param delay The delay before pairing starts. In this window, cancel may
39065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * be called.
39165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
39265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void startPairing(BluetoothDevice device, int delay) {
39365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        startPairing(device, delay, true);
39465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
39565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
39665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
39765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Return our state
39865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * @return One of the STATE_ constants.
39965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
40065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getStatus() {
40165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mStatus;
40265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
40365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
40465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
40565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Get the device that we're currently targeting. This will be null if
40665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * there is no device that is in the process of being connected to.
40765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
40865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public BluetoothDevice getTargetDevice() {
40965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mTarget;
41065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
41165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
41265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
41365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * When the timer to start the next stage will expire, in {@link SystemClock#elapsedRealtime()}.
41465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Will only be valid while waiting to pair and after an error from which we are restarting.
41565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
41665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public long getNextStageTime() {
41765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mNextStageTimestamp;
41865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
41965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
42065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public List<BluetoothDevice> getAvailableDevices() {
42165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ArrayList<BluetoothDevice> copy = new ArrayList<BluetoothDevice>(mVisibleDevices.size());
42265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        copy.addAll(mVisibleDevices);
42365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return copy;
42465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
42565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
42665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setListener(EventListener listener) {
42765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mListener = listener;
42865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
42965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
43065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void invalidateDevice(BluetoothDevice device) {
43165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        onDeviceLost(device);
43265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
43365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
43465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void startPairing(BluetoothDevice device, int delay, boolean isManual) {
43565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // TODO check if we're already paired/bonded to this device
43665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
43765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // cancel auto-mode if applicable
43865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mAutoMode = !isManual;
43965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
44065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mTarget = device;
44165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
44265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (isInProgress()) {
44365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            throw new RuntimeException("Pairing already in progress, you must cancel the " +
44465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    "previous request first");
44565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
44665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
44765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mHandler.removeMessages(MSG_PAIR);
44865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mHandler.removeMessages(MSG_START);
44965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
45065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mNextStageTimestamp = SystemClock.elapsedRealtime() +
45165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                (mAutoMode ? DELAY_AUTO_PAIRING : DELAY_MANUAL_PAIRING);
45265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mHandler.sendEmptyMessageDelayed(MSG_PAIR,
45365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mAutoMode ? DELAY_AUTO_PAIRING : DELAY_MANUAL_PAIRING);
45465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
45565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setStatus(STATUS_WAITING_TO_PAIR);
45665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
45765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
45865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
45965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Pairing is in progress and is no longer cancelable.
46065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
46165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public boolean isInProgress() {
46265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mStatus != STATUS_NONE && mStatus != STATUS_ERROR && mStatus != STATUS_SCANNING &&
46365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mStatus != STATUS_WAITING_TO_PAIR;
46465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
46565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
46665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void updateListener() {
46765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mListener != null) {
46865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mListener.statusChanged();
46965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
47065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
47165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
47265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void onDeviceFound(BluetoothDevice device) {
47365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (!mVisibleDevices.contains(device)) {
47465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mVisibleDevices.add(device);
47565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            Log.d(TAG, "Added device to visible list. Name = " + device.getName() + " , class = " +
47665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    device.getBluetoothClass().getDeviceClass());
47765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
47865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
47965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
48065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
48165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        updatePairingState();
48265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // update the listener because a new device is visible
48365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        updateListener();
48465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
48565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
48665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void onDeviceLost(BluetoothDevice device) {
48765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // TODO validate removal works as expected
48865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mVisibleDevices.remove(device)) {
48965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            updatePairingState();
49065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // update the listener because a device disappeared
49165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            updateListener();
49265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
49365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
49465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
49565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void updatePairingState() {
49665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mAutoMode) {
49765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (isReadyToAutoPair()) {
49865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mTarget = mVisibleDevices.get(0);
49965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                startPairing(mTarget, DELAY_AUTO_PAIRING, false);
50065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
50165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                doCancel();
50265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
50365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
50465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
50565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
50665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
50765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * @return {@code true} If there is only one visible input device.
50865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
50965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private boolean isReadyToAutoPair() {
51065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // we imagine that the conditions under which we decide to pair or
51165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // not may one day become more complicated, which is why this length
51265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // check is wrapped in a method call.
51365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mVisibleDevices.size() == 1;
51465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
51565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
51665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void doCancel() {
51765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // TODO allow cancel to be called from any state
51865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (isInProgress()) {
51965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            Log.d(TAG, "Pairing process has already begun, it can not be canceled.");
52065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
52165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
52265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
52365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // stop scanning, just in case we are
52465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        BluetoothScanner.stopListening(mBtListener);
52565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        BluetoothScanner.stopNow();
52665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
52765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // remove any callbacks
52865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mHandler.removeMessages(MSG_PAIR);
52965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
53065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // remove bond, if existing
53165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        unpairDevice(mTarget);
53265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
53365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mTarget = null;
53465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
53565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setStatus(STATUS_NONE);
53665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
53765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // resume scanning
53865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        start();
53965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
54065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
54165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
54265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Set the status and update any listener.
54365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
54465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void setStatus(int status) {
54565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mStatus = status;
54665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        updateListener();
54765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
54865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
54965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void startBonding() {
55065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        stopScanning();
55165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setStatus(STATUS_PAIRING);
55265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mTarget.getBondState() != BluetoothDevice.BOND_BONDED) {
55365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            registerLinkStatusReceiver();
55465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
55565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // create bond (pair) to the device
55665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mTarget.createBond();
55765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
55865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            onBonded();
55965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
56065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
56165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
56265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void onBonded() {
56365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        openConnection();
56465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
56565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
56665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void openConnection() {
56765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setStatus(STATUS_CONNECTING);
56865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
56965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
57065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
57165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // connect to the Bluetooth service, then registerInputListener
57265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        adapter.getProfileProxy(mContext, mServiceConnection, BluetoothProfile.INPUT_DEVICE);
57365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
57465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
57565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void onInputAdded() {
57665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        unregisterInputMethodMonitor();
57765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
57865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        adapter.closeProfileProxy(BluetoothProfile.INPUT_DEVICE, mInputProxy);
57965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setStatus(STATUS_NONE);
58065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
58165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
58265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void onBondFailed() {
58365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        Log.w(TAG, "There was an error bonding with the device.");
58465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setStatus(STATUS_ERROR);
58565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
58665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // remove bond, if existing
58765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        unpairDevice(mTarget);
58865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
58965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // TODO do we need to check Bluetooth for the device and possible delete it?
59065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mNextStageTimestamp = SystemClock.elapsedRealtime() + DELAY_RETRY;
59165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mHandler.sendEmptyMessageDelayed(MSG_START, DELAY_RETRY);
59265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
59365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
59465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void registerInputMethodMonitor() {
59565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        InputManager inputManager = (InputManager) mContext.getSystemService(Context.INPUT_SERVICE);
59665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        inputManager.registerInputDeviceListener(mInputListener, mHandler);
59765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
59865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // TO DO: The line below is a workaround for an issue in InputManager.
59965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // The manager doesn't actually registers itself with the InputService
60065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // unless we query it for input devices. We should remove this once
60165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // the problem is fixed in InputManager.
60265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // Reference bug in Frameworks: b/10415556
60365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int[] inputDevices = inputManager.getInputDeviceIds();
60465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
60565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
60665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void unregisterInputMethodMonitor() {
60765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        InputManager inputManager = (InputManager) mContext.getSystemService(Context.INPUT_SERVICE);
60865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        inputManager.unregisterInputDeviceListener(mInputListener);
60965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
61065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
61165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void registerLinkStatusReceiver() {
61265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mLinkReceiverRegistered = true;
61365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
61465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mContext.registerReceiver(mLinkStatusReceiver, filter);
61565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
61665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
61765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void unregisterLinkStatusReceiver() {
61865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mLinkReceiverRegistered = false;
61965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mContext.unregisterReceiver(mLinkStatusReceiver);
62065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
62165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
62265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void stopScanning() {
62365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        BluetoothScanner.stopListening(mBtListener);
62465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        BluetoothScanner.stopNow();
62565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
62665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
62765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public boolean unpairDevice(BluetoothDevice device) {
62865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (device != null) {
62965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int state = device.getBondState();
63065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
63165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (state == BluetoothDevice.BOND_BONDING) {
63265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                device.cancelBondProcess();
63365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
63465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
63565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (state != BluetoothDevice.BOND_NONE) {
63665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                final boolean successful = device.removeBond();
63765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (successful) {
63865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (DEBUG) {
63965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        Log.d(TAG, "Bluetooth device successfully unpaired: " + device.getName());
64065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
64165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    return true;
64265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } else {
64365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    Log.e(TAG, "Failed to unpair Bluetooth Device: " + device.getName());
64465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
64565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
64665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
64765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return false;
64865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
64965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane}
650