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.BluetoothInputDevice;
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 import android.view.InputDevice;
28 
29 /**
30  * Manages process of pairing and connecting of input devices.
31  */
32 public class BluetoothInputDeviceConnector implements BluetoothDevicePairer.BluetoothConnector {
33 
34     public static final String TAG = "BtInputDeviceConnector";
35 
36     private static final boolean DEBUG = false;
37 
38     private static final String[] INVALID_INPUT_KEYBOARD_DEVICE_NAMES = {
39         "gpio-keypad", "cec_keyboard", "Virtual", "athome_remote"
40     };
41 
42     private BluetoothProfile.ServiceListener mServiceConnection =
43             new BluetoothProfile.ServiceListener() {
44 
45         @Override
46         public void onServiceDisconnected(int profile) {
47             Log.w(TAG, "Service disconnected, perhaps unexpectedly");
48             unregisterInputMethodMonitor();
49             closeInputProfileProxy();
50             mOpenConnectionCallback.failed();
51         }
52 
53         @Override
54         public void onServiceConnected(int profile, BluetoothProfile proxy) {
55             if (DEBUG) {
56                 Log.d(TAG, "Connection made to bluetooth proxy.");
57             }
58             mInputProxy = (BluetoothInputDevice) proxy;
59             if (mTarget != null) {
60                 registerInputMethodMonitor();
61                 if (DEBUG) {
62                     Log.d(TAG, "Connecting to target: " + mTarget.getAddress());
63                 }
64                 // TODO need to start a timer, otherwise if the connection fails we might be
65                 // stuck here forever
66                 mInputProxy.connect(mTarget);
67 
68                 // must set PRIORITY_AUTO_CONNECT or auto-connection will not
69                 // occur, however this setting does not appear to be sticky
70                 // across a reboot
71                 mInputProxy.setPriority(mTarget, BluetoothProfile.PRIORITY_AUTO_CONNECT);
72             }
73         }
74     };
75 
76     private BluetoothInputDevice mInputProxy;
77     private boolean mInputMethodMonitorRegistered = false;
78 
79     private BluetoothDevice mTarget;
80     private Context mContext;
81     private Handler mHandler;
82     private BluetoothDevicePairer.OpenConnectionCallback mOpenConnectionCallback;
83 
registerInputMethodMonitor()84     private void registerInputMethodMonitor() {
85         InputManager inputManager = (InputManager) mContext.getSystemService(Context.INPUT_SERVICE);
86         inputManager.registerInputDeviceListener(mInputListener, mHandler);
87 
88         // TO DO: The line below is a workaround for an issue in InputManager.
89         // The manager doesn't actually registers itself with the InputService
90         // unless we query it for input devices. We should remove this once
91         // the problem is fixed in InputManager.
92         // Reference bug in Frameworks: b/10415556
93         int[] inputDevices = inputManager.getInputDeviceIds();
94 
95         mInputMethodMonitorRegistered = true;
96     }
97 
98     private InputManager.InputDeviceListener mInputListener =
99             new InputManager.InputDeviceListener() {
100         @Override
101         public void onInputDeviceRemoved(int deviceId) {
102             // ignored
103         }
104 
105         @Override
106         public void onInputDeviceChanged(int deviceId) {
107             // ignored
108         }
109 
110         @Override
111         public void onInputDeviceAdded(int deviceId) {
112            if (BluetoothDevicePairer.hasValidInputDevice(mContext, new int[] {deviceId})) {
113                onInputAdded();
114            }
115         }
116     };
117 
onInputAdded()118     private void onInputAdded() {
119         unregisterInputMethodMonitor();
120         closeInputProfileProxy();
121         mOpenConnectionCallback.succeeded();
122     }
123 
unregisterInputMethodMonitor()124     private void unregisterInputMethodMonitor() {
125         if (mInputMethodMonitorRegistered) {
126             InputManager inputManager = (InputManager) mContext.getSystemService(Context.INPUT_SERVICE);
127             inputManager.unregisterInputDeviceListener(mInputListener);
128             mInputMethodMonitorRegistered = false;
129         }
130     }
131 
closeInputProfileProxy()132     private void closeInputProfileProxy() {
133         if (mInputProxy != null) {
134             try {
135                 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
136                 adapter.closeProfileProxy(BluetoothProfile.INPUT_DEVICE, mInputProxy);
137                 mInputProxy = null;
138             } catch (Throwable t) {
139                 Log.w(TAG, "Error cleaning up input profile proxy", t);
140             }
141         }
142     }
143 
BluetoothInputDeviceConnector()144     private BluetoothInputDeviceConnector() {
145     }
146 
BluetoothInputDeviceConnector(Context context, BluetoothDevice target, Handler handler, BluetoothDevicePairer.OpenConnectionCallback callback)147     public BluetoothInputDeviceConnector(Context context, BluetoothDevice target, Handler handler,
148                                          BluetoothDevicePairer.OpenConnectionCallback callback) {
149         mContext = context;
150         mTarget = target;
151         mHandler = handler;
152         mOpenConnectionCallback = callback;
153     }
154 
155     @Override
openConnection(BluetoothAdapter adapter)156     public void openConnection(BluetoothAdapter adapter) {
157         if (!adapter.getProfileProxy(mContext, mServiceConnection, BluetoothProfile.INPUT_DEVICE)) {
158             mOpenConnectionCallback.failed();
159         }
160     }
161 }
162