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