• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 android.bluetooth;
18 
19 import android.annotation.SdkConstant;
20 import android.annotation.SdkConstant.SdkConstantType;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.ServiceConnection;
25 import android.os.IBinder;
26 import android.os.RemoteException;
27 import android.util.Log;
28 
29 import java.util.Arrays;
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 /**
34  * @hide
35  */
36 public final class BluetoothInputHost implements BluetoothProfile {
37 
38     private static final String TAG = BluetoothInputHost.class.getSimpleName();
39 
40     /**
41      * Intent used to broadcast the change in connection state of the Input
42      * Host profile.
43      *
44      * <p>This intent will have 3 extras:
45      * <ul>
46      *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
47      *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
48      *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
49      * </ul>
50      *
51      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
52      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
53      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
54      *
55      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
56      * receive.
57      */
58     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
59     public static final String ACTION_CONNECTION_STATE_CHANGED =
60         "android.bluetooth.inputhost.profile.action.CONNECTION_STATE_CHANGED";
61 
62     /**
63      * Constants representing device subclass.
64      *
65      * @see #registerApp(String, String, String, byte, byte[],
66      *      BluetoothHidDeviceCallback)
67      */
68     public static final byte SUBCLASS1_NONE = (byte) 0x00;
69     public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40;
70     public static final byte SUBCLASS1_MOUSE = (byte) 0x80;
71     public static final byte SUBCLASS1_COMBO = (byte) 0xC0;
72 
73     public static final byte SUBCLASS2_UNCATEGORIZED = (byte) 0x00;
74     public static final byte SUBCLASS2_JOYSTICK = (byte) 0x01;
75     public static final byte SUBCLASS2_GAMEPAD = (byte) 0x02;
76     public static final byte SUBCLASS2_REMOTE_CONTROL = (byte) 0x03;
77     public static final byte SUBCLASS2_SENSING_DEVICE = (byte) 0x04;
78     public static final byte SUBCLASS2_DIGITIZER_TABLED = (byte) 0x05;
79     public static final byte SUBCLASS2_CARD_READER = (byte) 0x06;
80 
81     /**
82      * Constants representing report types.
83      *
84      * @see BluetoothHidDeviceCallback#onGetReport(byte, byte, int)
85      * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[])
86      * @see BluetoothHidDeviceCallback#onIntrData(byte, byte[])
87      */
88     public static final byte REPORT_TYPE_INPUT = (byte) 1;
89     public static final byte REPORT_TYPE_OUTPUT = (byte) 2;
90     public static final byte REPORT_TYPE_FEATURE = (byte) 3;
91 
92     /**
93      * Constants representing error response for Set Report.
94      *
95      * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[])
96      */
97     public static final byte ERROR_RSP_SUCCESS = (byte) 0;
98     public static final byte ERROR_RSP_NOT_READY = (byte) 1;
99     public static final byte ERROR_RSP_INVALID_RPT_ID = (byte) 2;
100     public static final byte ERROR_RSP_UNSUPPORTED_REQ = (byte) 3;
101     public static final byte ERROR_RSP_INVALID_PARAM = (byte) 4;
102     public static final byte ERROR_RSP_UNKNOWN = (byte) 14;
103 
104     /**
105      * Constants representing protocol mode used set by host. Default is always
106      * {@link #PROTOCOL_REPORT_MODE} unless notified otherwise.
107      *
108      * @see BluetoothHidDeviceCallback#onSetProtocol(byte)
109      */
110     public static final byte PROTOCOL_BOOT_MODE = (byte) 0;
111     public static final byte PROTOCOL_REPORT_MODE = (byte) 1;
112 
113     private Context mContext;
114 
115     private ServiceListener mServiceListener;
116 
117     private IBluetoothInputHost mService;
118 
119     private BluetoothAdapter mAdapter;
120 
121     private static class BluetoothHidDeviceCallbackWrapper extends IBluetoothHidDeviceCallback.Stub {
122 
123         private BluetoothHidDeviceCallback mCallback;
124 
BluetoothHidDeviceCallbackWrapper(BluetoothHidDeviceCallback callback)125         public BluetoothHidDeviceCallbackWrapper(BluetoothHidDeviceCallback callback) {
126             mCallback = callback;
127         }
128 
129         @Override
onAppStatusChanged(BluetoothDevice pluggedDevice, BluetoothHidDeviceAppConfiguration config, boolean registered)130         public void onAppStatusChanged(BluetoothDevice pluggedDevice,
131                 BluetoothHidDeviceAppConfiguration config, boolean registered) {
132             mCallback.onAppStatusChanged(pluggedDevice, config, registered);
133         }
134 
135         @Override
onConnectionStateChanged(BluetoothDevice device, int state)136         public void onConnectionStateChanged(BluetoothDevice device, int state) {
137             mCallback.onConnectionStateChanged(device, state);
138         }
139 
140         @Override
onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize)141         public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
142             mCallback.onGetReport(device, type, id, bufferSize);
143         }
144 
145         @Override
onSetReport(BluetoothDevice device, byte type, byte id, byte[] data)146         public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) {
147             mCallback.onSetReport(device, type, id, data);
148         }
149 
150         @Override
onSetProtocol(BluetoothDevice device, byte protocol)151         public void onSetProtocol(BluetoothDevice device, byte protocol) {
152             mCallback.onSetProtocol(device, protocol);
153         }
154 
155         @Override
onIntrData(BluetoothDevice device, byte reportId, byte[] data)156         public void onIntrData(BluetoothDevice device, byte reportId, byte[] data) {
157             mCallback.onIntrData(device, reportId, data);
158         }
159 
160         @Override
onVirtualCableUnplug(BluetoothDevice device)161         public void onVirtualCableUnplug(BluetoothDevice device) {
162             mCallback.onVirtualCableUnplug(device);
163         }
164     }
165 
166     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
167         new IBluetoothStateChangeCallback.Stub() {
168 
169         public void onBluetoothStateChange(boolean up) {
170             Log.d(TAG, "onBluetoothStateChange: up=" + up);
171             synchronized (mConnection) {
172                 if (!up) {
173                     Log.d(TAG,"Unbinding service...");
174                     if (mService != null) {
175                         mService = null;
176                         try {
177                             mContext.unbindService(mConnection);
178                         } catch (IllegalArgumentException e) {
179                             Log.e(TAG,"onBluetoothStateChange: could not unbind service:", e);
180                         }
181                     }
182                 } else {
183                     try {
184                         if (mService == null) {
185                             Log.d(TAG,"Binding HID Device service...");
186                             doBind();
187                         }
188                     } catch (IllegalStateException e) {
189                         Log.e(TAG,"onBluetoothStateChange: could not bind to HID Dev service: ", e);
190                     } catch (SecurityException e) {
191                         Log.e(TAG,"onBluetoothStateChange: could not bind to HID Dev service: ", e);
192                     }
193                 }
194             }
195         }
196     };
197 
198     private ServiceConnection mConnection = new ServiceConnection() {
199 
200         public void onServiceConnected(ComponentName className, IBinder service) {
201             Log.d(TAG, "onServiceConnected()");
202 
203             mService = IBluetoothInputHost.Stub.asInterface(service);
204 
205             if (mServiceListener != null) {
206                 mServiceListener.onServiceConnected(BluetoothProfile.INPUT_HOST,
207                     BluetoothInputHost.this);
208             }
209         }
210 
211         public void onServiceDisconnected(ComponentName className) {
212             Log.d(TAG, "onServiceDisconnected()");
213 
214             mService = null;
215 
216             if (mServiceListener != null) {
217                 mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_HOST);
218             }
219         }
220     };
221 
BluetoothInputHost(Context context, ServiceListener listener)222     BluetoothInputHost(Context context, ServiceListener listener) {
223         Log.v(TAG, "BluetoothInputHost");
224 
225         mContext = context;
226         mServiceListener = listener;
227         mAdapter = BluetoothAdapter.getDefaultAdapter();
228 
229         IBluetoothManager mgr = mAdapter.getBluetoothManager();
230         if (mgr != null) {
231             try {
232                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
233             } catch (RemoteException e) {
234                 e.printStackTrace();
235             }
236         }
237 
238         doBind();
239     }
240 
doBind()241     boolean doBind() {
242         Intent intent = new Intent(IBluetoothInputHost.class.getName());
243         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
244         intent.setComponent(comp);
245         if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
246                 android.os.Process.myUserHandle())) {
247             Log.e(TAG, "Could not bind to Bluetooth HID Device Service with " + intent);
248             return false;
249         }
250         Log.d(TAG, "Bound to HID Device Service");
251         return true;
252     }
253 
close()254     void close() {
255         Log.v(TAG, "close()");
256 
257         IBluetoothManager mgr = mAdapter.getBluetoothManager();
258         if (mgr != null) {
259             try {
260                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
261             } catch (RemoteException e) {
262                 e.printStackTrace();
263             }
264         }
265 
266         synchronized (mConnection) {
267             if (mService != null) {
268                 mService = null;
269                 try {
270                     mContext.unbindService(mConnection);
271                 } catch (IllegalArgumentException e) {
272                     Log.e(TAG,"close: could not unbind HID Dev service: ", e);
273                 }
274            }
275         }
276 
277         mServiceListener = null;
278     }
279 
280     /**
281      * {@inheritDoc}
282      */
getConnectedDevices()283     public List<BluetoothDevice> getConnectedDevices() {
284         Log.v(TAG, "getConnectedDevices()");
285 
286         if (mService != null) {
287             try {
288                 return mService.getConnectedDevices();
289             } catch (RemoteException e) {
290                 Log.e(TAG, e.toString());
291             }
292         } else {
293             Log.w(TAG, "Proxy not attached to service");
294         }
295 
296         return new ArrayList<BluetoothDevice>();
297     }
298 
299     /**
300      * {@inheritDoc}
301      */
getDevicesMatchingConnectionStates(int[] states)302     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
303         Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
304 
305         if (mService != null) {
306             try {
307                 return mService.getDevicesMatchingConnectionStates(states);
308             } catch (RemoteException e) {
309                 Log.e(TAG, e.toString());
310             }
311         } else {
312             Log.w(TAG, "Proxy not attached to service");
313         }
314 
315         return new ArrayList<BluetoothDevice>();
316     }
317 
318     /**
319      * {@inheritDoc}
320      */
getConnectionState(BluetoothDevice device)321     public int getConnectionState(BluetoothDevice device) {
322         Log.v(TAG, "getConnectionState(): device=" + device);
323 
324         if (mService != null) {
325             try {
326                 return mService.getConnectionState(device);
327             } catch (RemoteException e) {
328                 Log.e(TAG, e.toString());
329             }
330         } else {
331             Log.w(TAG, "Proxy not attached to service");
332         }
333 
334         return STATE_DISCONNECTED;
335     }
336 
337     /**
338      * Registers application to be used for HID device. Connections to HID
339      * Device are only possible when application is registered. Only one
340      * application can be registered at time. When no longer used, application
341      * should be unregistered using
342      * {@link #unregisterApp(BluetoothHidDeviceAppConfiguration)}.
343      *
344      * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of
345      *             HID Device SDP record.
346      * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of
347      *             Incoming QoS Settings.
348      * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of
349      *             Outgoing QoS Settings.
350      * @param callback {@link BluetoothHidDeviceCallback} object to which
351      *            callback messages will be sent.
352      * @return
353      */
registerApp(BluetoothHidDeviceAppSdpSettings sdp, BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos, BluetoothHidDeviceCallback callback)354     public boolean registerApp(BluetoothHidDeviceAppSdpSettings sdp,
355             BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos,
356             BluetoothHidDeviceCallback callback) {
357         Log.v(TAG, "registerApp(): sdp=" + sdp + " inQos=" + inQos + " outQos=" + outQos
358                 + " callback=" + callback);
359 
360         boolean result = false;
361 
362         if (sdp == null || callback == null) {
363             return false;
364         }
365 
366         if (mService != null) {
367             try {
368                 BluetoothHidDeviceAppConfiguration config =
369                     new BluetoothHidDeviceAppConfiguration();
370                 BluetoothHidDeviceCallbackWrapper cbw =
371                     new BluetoothHidDeviceCallbackWrapper(callback);
372                 result = mService.registerApp(config, sdp, inQos, outQos, cbw);
373             } catch (RemoteException e) {
374                 Log.e(TAG, e.toString());
375             }
376         } else {
377             Log.w(TAG, "Proxy not attached to service");
378         }
379 
380         return result;
381     }
382 
383     /**
384      * Unregisters application. Active connection will be disconnected and no
385      * new connections will be allowed until registered again using
386      * {@link #registerApp(String, String, String, byte, byte[], BluetoothHidDeviceCallback)}
387      *
388      * @param config {@link BluetoothHidDeviceAppConfiguration} object as
389      *            obtained from
390      *            {@link BluetoothHidDeviceCallback#onAppStatusChanged(BluetoothDevice,
391      *            BluetoothHidDeviceAppConfiguration, boolean)}
392      *
393      * @return
394      */
unregisterApp(BluetoothHidDeviceAppConfiguration config)395     public boolean unregisterApp(BluetoothHidDeviceAppConfiguration config) {
396         Log.v(TAG, "unregisterApp()");
397 
398         boolean result = false;
399 
400         if (mService != null) {
401             try {
402                 result = mService.unregisterApp(config);
403             } catch (RemoteException e) {
404                 Log.e(TAG, e.toString());
405             }
406         } else {
407             Log.w(TAG, "Proxy not attached to service");
408         }
409 
410         return result;
411     }
412 
413     /**
414      * Sends report to remote host using interrupt channel.
415      *
416      * @param id Report Id, as defined in descriptor. Can be 0 in case Report Id
417      *            are not defined in descriptor.
418      * @param data Report data, not including Report Id.
419      * @return
420      */
sendReport(BluetoothDevice device, int id, byte[] data)421     public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
422         boolean result = false;
423 
424         if (mService != null) {
425             try {
426                 result = mService.sendReport(device, id, data);
427             } catch (RemoteException e) {
428                 Log.e(TAG, e.toString());
429             }
430         } else {
431             Log.w(TAG, "Proxy not attached to service");
432         }
433 
434         return result;
435     }
436 
437     /**
438      * Sends report to remote host as reply for GET_REPORT request from
439      * {@link BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int)}.
440      *
441      * @param type Report Type, as in request.
442      * @param id Report Id, as in request.
443      * @param data Report data, not including Report Id.
444      * @return
445      */
replyReport(BluetoothDevice device, byte type, byte id, byte[] data)446     public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
447         Log.v(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id);
448 
449         boolean result = false;
450 
451         if (mService != null) {
452             try {
453                 result = mService.replyReport(device, type, id, data);
454             } catch (RemoteException e) {
455                 Log.e(TAG, e.toString());
456             }
457         } else {
458             Log.w(TAG, "Proxy not attached to service");
459         }
460 
461         return result;
462     }
463 
464     /**
465      * Sends error handshake message as reply for invalid SET_REPORT request
466      * from {@link BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])}.
467      *
468      * @param error Error to be sent for SET_REPORT via HANDSHAKE.
469      * @return
470      */
reportError(BluetoothDevice device, byte error)471     public boolean reportError(BluetoothDevice device, byte error) {
472         Log.v(TAG, "reportError(): device=" + device + " error=" + error);
473 
474         boolean result = false;
475 
476         if (mService != null) {
477             try {
478                 result = mService.reportError(device, error);
479             } catch (RemoteException e) {
480                 Log.e(TAG, e.toString());
481             }
482         } else {
483             Log.w(TAG, "Proxy not attached to service");
484         }
485 
486         return result;
487     }
488 
489     /**
490      * Sends Virtual Cable Unplug to currently connected host.
491      *
492      * @return
493      */
unplug(BluetoothDevice device)494     public boolean unplug(BluetoothDevice device) {
495         Log.v(TAG, "unplug(): device=" + device);
496 
497         boolean result = false;
498 
499         if (mService != null) {
500             try {
501                 result = mService.unplug(device);
502             } catch (RemoteException e) {
503                 Log.e(TAG, e.toString());
504             }
505         } else {
506             Log.w(TAG, "Proxy not attached to service");
507         }
508 
509         return result;
510     }
511 
512     /**
513      * Initiates connection to host which currently has Virtual Cable
514      * established with device.
515      *
516      * @return
517      */
connect(BluetoothDevice device)518     public boolean connect(BluetoothDevice device) {
519         Log.v(TAG, "connect(): device=" + device);
520 
521         boolean result = false;
522 
523         if (mService != null) {
524             try {
525                 result = mService.connect(device);
526             } catch (RemoteException e) {
527                 Log.e(TAG, e.toString());
528             }
529         } else {
530             Log.w(TAG, "Proxy not attached to service");
531         }
532 
533         return result;
534     }
535 
536     /**
537      * Disconnects from currently connected host.
538      *
539      * @return
540      */
disconnect(BluetoothDevice device)541     public boolean disconnect(BluetoothDevice device) {
542         Log.v(TAG, "disconnect(): device=" + device);
543 
544         boolean result = false;
545 
546         if (mService != null) {
547             try {
548                 result = mService.disconnect(device);
549             } catch (RemoteException e) {
550                 Log.e(TAG, e.toString());
551             }
552         } else {
553             Log.w(TAG, "Proxy not attached to service");
554         }
555 
556         return result;
557     }
558 }
559