1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tv.settings.accessories; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothHidHost; 22 import android.bluetooth.BluetoothProfile; 23 import android.content.Context; 24 import android.hardware.input.InputManager; 25 import android.os.Handler; 26 import android.util.Log; 27 28 /** 29 * Manages process of pairing and connecting of input devices. 30 */ 31 public class BluetoothInputDeviceConnector implements BluetoothDevicePairer.BluetoothConnector { 32 33 public static final String TAG = "BtInputDeviceConnector"; 34 35 private static final boolean DEBUG = false; 36 37 private static final String[] INVALID_INPUT_KEYBOARD_DEVICE_NAMES = { 38 "gpio-keypad", "cec_keyboard", "Virtual", "athome_remote" 39 }; 40 41 private BluetoothProfile.ServiceListener mServiceConnection = 42 new BluetoothProfile.ServiceListener() { 43 44 @Override 45 public void onServiceDisconnected(int profile) { 46 Log.w(TAG, "Service disconnected, perhaps unexpectedly"); 47 unregisterInputMethodMonitor(); 48 closeInputProfileProxy(); 49 mOpenConnectionCallback.failed(); 50 } 51 52 @Override 53 public void onServiceConnected(int profile, BluetoothProfile proxy) { 54 if (DEBUG) { 55 Log.d(TAG, "Connection made to bluetooth proxy."); 56 } 57 mInputProxy = (BluetoothHidHost) proxy; 58 if (mTarget != null) { 59 registerInputMethodMonitor(); 60 if (DEBUG) { 61 Log.d(TAG, "Connecting to target: " + mTarget.getAddress()); 62 } 63 // TODO need to start a timer, otherwise if the connection fails we might be 64 // stuck here forever 65 mInputProxy.connect(mTarget); 66 67 // must set PRIORITY_AUTO_CONNECT or auto-connection will not 68 // occur, however this setting does not appear to be sticky 69 // across a reboot 70 mInputProxy.setPriority(mTarget, BluetoothProfile.PRIORITY_AUTO_CONNECT); 71 } 72 } 73 }; 74 75 private BluetoothHidHost mInputProxy; 76 private boolean mInputMethodMonitorRegistered = false; 77 78 private BluetoothDevice mTarget; 79 private Context mContext; 80 private Handler mHandler; 81 private BluetoothDevicePairer.OpenConnectionCallback mOpenConnectionCallback; 82 registerInputMethodMonitor()83 private void registerInputMethodMonitor() { 84 InputManager inputManager = (InputManager) mContext.getSystemService(Context.INPUT_SERVICE); 85 inputManager.registerInputDeviceListener(mInputListener, mHandler); 86 87 // TO DO: The line below is a workaround for an issue in InputManager. 88 // The manager doesn't actually registers itself with the InputService 89 // unless we query it for input devices. We should remove this once 90 // the problem is fixed in InputManager. 91 // Reference bug in Frameworks: b/10415556 92 int[] inputDevices = inputManager.getInputDeviceIds(); 93 94 mInputMethodMonitorRegistered = true; 95 } 96 97 private InputManager.InputDeviceListener mInputListener = 98 new InputManager.InputDeviceListener() { 99 @Override 100 public void onInputDeviceRemoved(int deviceId) { 101 // ignored 102 } 103 104 @Override 105 public void onInputDeviceChanged(int deviceId) { 106 // ignored 107 } 108 109 @Override 110 public void onInputDeviceAdded(int deviceId) { 111 if (BluetoothDevicePairer.hasValidInputDevice(mContext, new int[] {deviceId})) { 112 onInputAdded(); 113 } 114 } 115 }; 116 onInputAdded()117 private void onInputAdded() { 118 unregisterInputMethodMonitor(); 119 closeInputProfileProxy(); 120 mOpenConnectionCallback.succeeded(); 121 } 122 unregisterInputMethodMonitor()123 private void unregisterInputMethodMonitor() { 124 if (mInputMethodMonitorRegistered) { 125 InputManager inputManager = (InputManager) mContext.getSystemService(Context.INPUT_SERVICE); 126 inputManager.unregisterInputDeviceListener(mInputListener); 127 mInputMethodMonitorRegistered = false; 128 } 129 } 130 closeInputProfileProxy()131 private void closeInputProfileProxy() { 132 if (mInputProxy != null) { 133 try { 134 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 135 adapter.closeProfileProxy(BluetoothProfile.HID_HOST, mInputProxy); 136 mInputProxy = null; 137 } catch (Throwable t) { 138 Log.w(TAG, "Error cleaning up input profile proxy", t); 139 } 140 } 141 } 142 BluetoothInputDeviceConnector()143 private BluetoothInputDeviceConnector() { 144 } 145 BluetoothInputDeviceConnector(Context context, BluetoothDevice target, Handler handler, BluetoothDevicePairer.OpenConnectionCallback callback)146 public BluetoothInputDeviceConnector(Context context, BluetoothDevice target, Handler handler, 147 BluetoothDevicePairer.OpenConnectionCallback callback) { 148 mContext = context; 149 mTarget = target; 150 mHandler = handler; 151 mOpenConnectionCallback = callback; 152 } 153 154 @Override openConnection(BluetoothAdapter adapter)155 public void openConnection(BluetoothAdapter adapter) { 156 if (!adapter.getProfileProxy(mContext, mServiceConnection, BluetoothProfile.HID_HOST)) { 157 mOpenConnectionCallback.failed(); 158 } 159 } 160 } 161