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 if (DEBUG) { 47 Log.d(TAG, "Service disconnected"); 48 } 49 unregisterInputMethodMonitor(); 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 if (BluetoothProfile.STATE_CONNECTED == mInputProxy.getConnectionState(mTarget)) { 60 closeInputProfileProxy(); 61 mOpenConnectionCallback.succeeded(); 62 return; 63 } 64 registerInputMethodMonitor(); 65 if (DEBUG) { 66 Log.d(TAG, "Connecting to target: " + mTarget.getAddress()); 67 } 68 // TODO need to start a timer, otherwise if the connection fails we might be 69 // stuck here forever 70 // must set CONNECTION_POLICY_ALLOWED or auto-connection will not 71 // occur, however this setting does not appear to be sticky 72 // across a reboot 73 mInputProxy.setConnectionPolicy(mTarget, BluetoothProfile.CONNECTION_POLICY_ALLOWED); 74 } 75 } 76 }; 77 78 private BluetoothHidHost mInputProxy; 79 private boolean mInputMethodMonitorRegistered = false; 80 81 private BluetoothDevice mTarget; 82 private Context mContext; 83 private Handler mHandler; 84 private BluetoothDevicePairer.OpenConnectionCallback mOpenConnectionCallback; 85 registerInputMethodMonitor()86 private void registerInputMethodMonitor() { 87 InputManager inputManager = (InputManager) mContext.getSystemService(Context.INPUT_SERVICE); 88 inputManager.registerInputDeviceListener(mInputListener, mHandler); 89 90 // TO DO: The line below is a workaround for an issue in InputManager. 91 // The manager doesn't actually registers itself with the InputService 92 // unless we query it for input devices. We should remove this once 93 // the problem is fixed in InputManager. 94 // Reference bug in Frameworks: b/10415556 95 int[] inputDevices = inputManager.getInputDeviceIds(); 96 97 mInputMethodMonitorRegistered = true; 98 } 99 100 private InputManager.InputDeviceListener mInputListener = 101 new InputManager.InputDeviceListener() { 102 @Override 103 public void onInputDeviceRemoved(int deviceId) { 104 // ignored 105 } 106 107 @Override 108 public void onInputDeviceChanged(int deviceId) { 109 // ignored 110 } 111 112 @Override 113 public void onInputDeviceAdded(int deviceId) { 114 if (BluetoothDevicePairer.hasValidInputDevice(mContext, new int[] {deviceId})) { 115 onInputAdded(); 116 } 117 } 118 }; 119 onInputAdded()120 private void onInputAdded() { 121 unregisterInputMethodMonitor(); 122 closeInputProfileProxy(); 123 mOpenConnectionCallback.succeeded(); 124 } 125 unregisterInputMethodMonitor()126 private void unregisterInputMethodMonitor() { 127 if (mInputMethodMonitorRegistered) { 128 InputManager inputManager = (InputManager) mContext.getSystemService(Context.INPUT_SERVICE); 129 inputManager.unregisterInputDeviceListener(mInputListener); 130 mInputMethodMonitorRegistered = false; 131 } 132 } 133 closeInputProfileProxy()134 private void closeInputProfileProxy() { 135 if (mInputProxy != null) { 136 try { 137 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 138 adapter.closeProfileProxy(BluetoothProfile.HID_HOST, mInputProxy); 139 mInputProxy = null; 140 } catch (Throwable t) { 141 Log.w(TAG, "Error cleaning up input profile proxy", t); 142 } 143 } 144 } 145 BluetoothInputDeviceConnector()146 private BluetoothInputDeviceConnector() { 147 } 148 BluetoothInputDeviceConnector(Context context, BluetoothDevice target, Handler handler, BluetoothDevicePairer.OpenConnectionCallback callback)149 public BluetoothInputDeviceConnector(Context context, BluetoothDevice target, Handler handler, 150 BluetoothDevicePairer.OpenConnectionCallback callback) { 151 mContext = context; 152 mTarget = target; 153 mHandler = handler; 154 mOpenConnectionCallback = callback; 155 } 156 157 @Override openConnection(BluetoothAdapter adapter)158 public void openConnection(BluetoothAdapter adapter) { 159 if (!adapter.getProfileProxy(mContext, mServiceConnection, BluetoothProfile.HID_HOST)) { 160 mOpenConnectionCallback.failed(); 161 } 162 } 163 164 @Override dispose()165 public void dispose() { 166 unregisterInputMethodMonitor(); 167 closeInputProfileProxy(); 168 } 169 } 170