• 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.ArrayList;
30 import java.util.Arrays;
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 volatile 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 final ServiceConnection mConnection = new ServiceConnection() {
199         public void onServiceConnected(ComponentName className, IBinder service) {
200             Log.d(TAG, "onServiceConnected()");
201             mService = IBluetoothInputHost.Stub.asInterface(service);
202             if (mServiceListener != null) {
203                 mServiceListener.onServiceConnected(BluetoothProfile.INPUT_HOST,
204                     BluetoothInputHost.this);
205             }
206         }
207         public void onServiceDisconnected(ComponentName className) {
208             Log.d(TAG, "onServiceDisconnected()");
209             mService = null;
210             if (mServiceListener != null) {
211                 mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_HOST);
212             }
213         }
214     };
215 
BluetoothInputHost(Context context, ServiceListener listener)216     BluetoothInputHost(Context context, ServiceListener listener) {
217         Log.v(TAG, "BluetoothInputHost");
218 
219         mContext = context;
220         mServiceListener = listener;
221         mAdapter = BluetoothAdapter.getDefaultAdapter();
222 
223         IBluetoothManager mgr = mAdapter.getBluetoothManager();
224         if (mgr != null) {
225             try {
226                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
227             } catch (RemoteException e) {
228                 e.printStackTrace();
229             }
230         }
231 
232         doBind();
233     }
234 
doBind()235     boolean doBind() {
236         Intent intent = new Intent(IBluetoothInputHost.class.getName());
237         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
238         intent.setComponent(comp);
239         if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
240                 android.os.Process.myUserHandle())) {
241             Log.e(TAG, "Could not bind to Bluetooth HID Device Service with " + intent);
242             return false;
243         }
244         Log.d(TAG, "Bound to HID Device Service");
245         return true;
246     }
247 
close()248     void close() {
249         Log.v(TAG, "close()");
250 
251         IBluetoothManager mgr = mAdapter.getBluetoothManager();
252         if (mgr != null) {
253             try {
254                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
255             } catch (RemoteException e) {
256                 e.printStackTrace();
257             }
258         }
259 
260         synchronized (mConnection) {
261             if (mService != null) {
262                 mService = null;
263                 try {
264                     mContext.unbindService(mConnection);
265                 } catch (IllegalArgumentException e) {
266                     Log.e(TAG,"close: could not unbind HID Dev service: ", e);
267                 }
268            }
269         }
270 
271         mServiceListener = null;
272     }
273 
274     /**
275      * {@inheritDoc}
276      */
getConnectedDevices()277     public List<BluetoothDevice> getConnectedDevices() {
278         Log.v(TAG, "getConnectedDevices()");
279 
280         final IBluetoothInputHost service = mService;
281         if (service != null) {
282             try {
283                 return service.getConnectedDevices();
284             } catch (RemoteException e) {
285                 Log.e(TAG, e.toString());
286             }
287         } else {
288             Log.w(TAG, "Proxy not attached to service");
289         }
290 
291         return new ArrayList<BluetoothDevice>();
292     }
293 
294     /**
295      * {@inheritDoc}
296      */
getDevicesMatchingConnectionStates(int[] states)297     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
298         Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
299 
300         final IBluetoothInputHost service = mService;
301         if (service != null) {
302             try {
303                 return service.getDevicesMatchingConnectionStates(states);
304             } catch (RemoteException e) {
305                 Log.e(TAG, e.toString());
306             }
307         } else {
308             Log.w(TAG, "Proxy not attached to service");
309         }
310 
311         return new ArrayList<BluetoothDevice>();
312     }
313 
314     /**
315      * {@inheritDoc}
316      */
getConnectionState(BluetoothDevice device)317     public int getConnectionState(BluetoothDevice device) {
318         Log.v(TAG, "getConnectionState(): device=" + device);
319 
320         final IBluetoothInputHost service = mService;
321         if (service != null) {
322             try {
323                 return service.getConnectionState(device);
324             } catch (RemoteException e) {
325                 Log.e(TAG, e.toString());
326             }
327         } else {
328             Log.w(TAG, "Proxy not attached to service");
329         }
330 
331         return STATE_DISCONNECTED;
332     }
333 
334     /**
335      * Registers application to be used for HID device. Connections to HID
336      * Device are only possible when application is registered. Only one
337      * application can be registered at time. When no longer used, application
338      * should be unregistered using
339      * {@link #unregisterApp(BluetoothHidDeviceAppConfiguration)}.
340      *
341      * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of
342      *             HID Device SDP record.
343      * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of
344      *             Incoming QoS Settings.
345      * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of
346      *             Outgoing QoS Settings.
347      * @param callback {@link BluetoothHidDeviceCallback} object to which
348      *            callback messages will be sent.
349      * @return
350      */
registerApp(BluetoothHidDeviceAppSdpSettings sdp, BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos, BluetoothHidDeviceCallback callback)351     public boolean registerApp(BluetoothHidDeviceAppSdpSettings sdp,
352             BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos,
353             BluetoothHidDeviceCallback callback) {
354         Log.v(TAG, "registerApp(): sdp=" + sdp + " inQos=" + inQos + " outQos=" + outQos
355                 + " callback=" + callback);
356 
357         boolean result = false;
358 
359         if (sdp == null || callback == null) {
360             return false;
361         }
362 
363         final IBluetoothInputHost service = mService;
364         if (service != null) {
365             try {
366                 BluetoothHidDeviceAppConfiguration config =
367                         new BluetoothHidDeviceAppConfiguration();
368                 BluetoothHidDeviceCallbackWrapper cbw =
369                         new BluetoothHidDeviceCallbackWrapper(callback);
370                 result = service.registerApp(config, sdp, inQos, outQos, cbw);
371             } catch (RemoteException e) {
372                 Log.e(TAG, e.toString());
373             }
374         } else {
375             Log.w(TAG, "Proxy not attached to service");
376         }
377 
378         return result;
379     }
380 
381     /**
382      * Unregisters application. Active connection will be disconnected and no
383      * new connections will be allowed until registered again using
384      * {@link #registerApp(String, String, String, byte, byte[], BluetoothHidDeviceCallback)}
385      *
386      * @param config {@link BluetoothHidDeviceAppConfiguration} object as
387      *            obtained from
388      *            {@link BluetoothHidDeviceCallback#onAppStatusChanged(BluetoothDevice,
389      *            BluetoothHidDeviceAppConfiguration, boolean)}
390      *
391      * @return
392      */
unregisterApp(BluetoothHidDeviceAppConfiguration config)393     public boolean unregisterApp(BluetoothHidDeviceAppConfiguration config) {
394         Log.v(TAG, "unregisterApp()");
395 
396         boolean result = false;
397 
398         final IBluetoothInputHost service = mService;
399         if (service != null) {
400             try {
401                 result = service.unregisterApp(config);
402             } catch (RemoteException e) {
403                 Log.e(TAG, e.toString());
404             }
405         } else {
406             Log.w(TAG, "Proxy not attached to service");
407         }
408 
409         return result;
410     }
411 
412     /**
413      * Sends report to remote host using interrupt channel.
414      *
415      * @param id Report Id, as defined in descriptor. Can be 0 in case Report Id
416      *            are not defined in descriptor.
417      * @param data Report data, not including Report Id.
418      * @return
419      */
sendReport(BluetoothDevice device, int id, byte[] data)420     public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
421         boolean result = false;
422 
423         final IBluetoothInputHost service = mService;
424         if (service != null) {
425             try {
426                 result = service.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         final IBluetoothInputHost service = mService;
452         if (service != null) {
453             try {
454                 result = service.replyReport(device, type, id, data);
455             } catch (RemoteException e) {
456                 Log.e(TAG, e.toString());
457             }
458         } else {
459             Log.w(TAG, "Proxy not attached to service");
460         }
461 
462         return result;
463     }
464 
465     /**
466      * Sends error handshake message as reply for invalid SET_REPORT request
467      * from {@link BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])}.
468      *
469      * @param error Error to be sent for SET_REPORT via HANDSHAKE.
470      * @return
471      */
reportError(BluetoothDevice device, byte error)472     public boolean reportError(BluetoothDevice device, byte error) {
473         Log.v(TAG, "reportError(): device=" + device + " error=" + error);
474 
475         boolean result = false;
476 
477         final IBluetoothInputHost service = mService;
478         if (service != null) {
479             try {
480                 result = service.reportError(device, error);
481             } catch (RemoteException e) {
482                 Log.e(TAG, e.toString());
483             }
484         } else {
485             Log.w(TAG, "Proxy not attached to service");
486         }
487 
488         return result;
489     }
490 
491     /**
492      * Sends Virtual Cable Unplug to currently connected host.
493      *
494      * @return
495      */
unplug(BluetoothDevice device)496     public boolean unplug(BluetoothDevice device) {
497         Log.v(TAG, "unplug(): device=" + device);
498 
499         boolean result = false;
500 
501         final IBluetoothInputHost service = mService;
502         if (service != null) {
503             try {
504                 result = service.unplug(device);
505             } catch (RemoteException e) {
506                 Log.e(TAG, e.toString());
507             }
508         } else {
509             Log.w(TAG, "Proxy not attached to service");
510         }
511 
512         return result;
513     }
514 
515     /**
516      * Initiates connection to host which currently has Virtual Cable
517      * established with device.
518      *
519      * @return
520      */
connect(BluetoothDevice device)521     public boolean connect(BluetoothDevice device) {
522         Log.v(TAG, "connect(): device=" + device);
523 
524         boolean result = false;
525 
526         final IBluetoothInputHost service = mService;
527         if (service != null) {
528             try {
529                 result = service.connect(device);
530             } catch (RemoteException e) {
531                 Log.e(TAG, e.toString());
532             }
533         } else {
534             Log.w(TAG, "Proxy not attached to service");
535         }
536 
537         return result;
538     }
539 
540     /**
541      * Disconnects from currently connected host.
542      *
543      * @return
544      */
disconnect(BluetoothDevice device)545     public boolean disconnect(BluetoothDevice device) {
546         Log.v(TAG, "disconnect(): device=" + device);
547 
548         boolean result = false;
549 
550         final IBluetoothInputHost service = mService;
551         if (service != null) {
552             try {
553                 result = service.disconnect(device);
554             } catch (RemoteException e) {
555                 Log.e(TAG, e.toString());
556             }
557         } else {
558             Log.w(TAG, "Proxy not attached to service");
559         }
560 
561         return result;
562     }
563 }
564