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