• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.Binder;
26 import android.os.IBinder;
27 import android.os.RemoteException;
28 import android.util.Log;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 
34 /**
35  * This class provides the public APIs to control the Bluetooth Input
36  * Device Profile.
37  *
38  *<p>BluetoothInputDevice is a proxy object for controlling the Bluetooth
39  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
40  * the BluetoothInputDevice proxy object.
41  *
42  *<p>Each method is protected with its appropriate permission.
43  *@hide
44  */
45 public final class BluetoothInputDevice implements BluetoothProfile {
46     private static final String TAG = "BluetoothInputDevice";
47     private static final boolean DBG = true;
48     private static final boolean VDBG = false;
49 
50     /**
51      * Intent used to broadcast the change in connection state of the Input
52      * Device profile.
53      *
54      * <p>This intent will have 3 extras:
55      * <ul>
56      *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
57      *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
58      *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
59      * </ul>
60      *
61      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
62      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
63      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
64      *
65      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
66      * receive.
67      */
68     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
69     public static final String ACTION_CONNECTION_STATE_CHANGED =
70         "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
71 
72     /**
73      * @hide
74      */
75     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
76     public static final String ACTION_PROTOCOL_MODE_CHANGED =
77         "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED";
78 
79     /**
80      * @hide
81      */
82     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
83     public static final String ACTION_HANDSHAKE =
84         "android.bluetooth.input.profile.action.HANDSHAKE";
85 
86     /**
87      * @hide
88      */
89     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
90     public static final String ACTION_REPORT =
91         "android.bluetooth.input.profile.action.REPORT";
92 
93     /**
94      * @hide
95      */
96     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
97     public static final String ACTION_VIRTUAL_UNPLUG_STATUS =
98         "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS";
99 
100 
101     /**
102      * Return codes for the connect and disconnect Bluez / Dbus calls.
103      * @hide
104      */
105     public static final int INPUT_DISCONNECT_FAILED_NOT_CONNECTED = 5000;
106 
107     /**
108      * @hide
109      */
110     public static final int INPUT_CONNECT_FAILED_ALREADY_CONNECTED = 5001;
111 
112     /**
113      * @hide
114      */
115     public static final int INPUT_CONNECT_FAILED_ATTEMPT_FAILED = 5002;
116 
117     /**
118      * @hide
119      */
120     public static final int INPUT_OPERATION_GENERIC_FAILURE = 5003;
121 
122     /**
123      * @hide
124      */
125     public static final int INPUT_OPERATION_SUCCESS = 5004;
126 
127     /**
128      * @hide
129      */
130     public static final int PROTOCOL_REPORT_MODE = 0;
131 
132     /**
133      * @hide
134      */
135     public static final int PROTOCOL_BOOT_MODE = 1;
136 
137     /**
138      * @hide
139      */
140     public static final int PROTOCOL_UNSUPPORTED_MODE = 255;
141 
142     /*  int reportType, int reportType, int bufferSize */
143     /**
144      * @hide
145      */
146     public static final byte REPORT_TYPE_INPUT = 1;
147 
148     /**
149      * @hide
150      */
151     public static final byte REPORT_TYPE_OUTPUT = 2;
152 
153     /**
154      * @hide
155      */
156     public static final byte REPORT_TYPE_FEATURE = 3;
157 
158     /**
159      * @hide
160      */
161     public static final int VIRTUAL_UNPLUG_STATUS_SUCCESS = 0;
162 
163     /**
164      * @hide
165      */
166     public static final int VIRTUAL_UNPLUG_STATUS_FAIL = 1;
167 
168     /**
169      * @hide
170      */
171     public static final String EXTRA_PROTOCOL_MODE = "android.bluetooth.BluetoothInputDevice.extra.PROTOCOL_MODE";
172 
173     /**
174      * @hide
175      */
176     public static final String EXTRA_REPORT_TYPE = "android.bluetooth.BluetoothInputDevice.extra.REPORT_TYPE";
177 
178     /**
179      * @hide
180      */
181     public static final String EXTRA_REPORT_ID = "android.bluetooth.BluetoothInputDevice.extra.REPORT_ID";
182 
183     /**
184      * @hide
185      */
186     public static final String EXTRA_REPORT_BUFFER_SIZE = "android.bluetooth.BluetoothInputDevice.extra.REPORT_BUFFER_SIZE";
187 
188     /**
189      * @hide
190      */
191     public static final String EXTRA_REPORT = "android.bluetooth.BluetoothInputDevice.extra.REPORT";
192 
193     /**
194      * @hide
195      */
196     public static final String EXTRA_STATUS = "android.bluetooth.BluetoothInputDevice.extra.STATUS";
197 
198     /**
199      * @hide
200      */
201     public static final String EXTRA_VIRTUAL_UNPLUG_STATUS = "android.bluetooth.BluetoothInputDevice.extra.VIRTUAL_UNPLUG_STATUS";
202 
203     private Context mContext;
204     private ServiceListener mServiceListener;
205     private BluetoothAdapter mAdapter;
206     private IBluetoothInputDevice mService;
207 
208     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
209             new IBluetoothStateChangeCallback.Stub() {
210                 public void onBluetoothStateChange(boolean up) {
211                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
212                     if (!up) {
213                         if (VDBG) Log.d(TAG,"Unbinding service...");
214                         synchronized (mConnection) {
215                             try {
216                                 mService = null;
217                                 mContext.unbindService(mConnection);
218                             } catch (Exception re) {
219                                 Log.e(TAG,"",re);
220                             }
221                         }
222                     } else {
223                         synchronized (mConnection) {
224                             try {
225                                 if (mService == null) {
226                                     if (VDBG) Log.d(TAG,"Binding service...");
227                                     doBind();
228                                 }
229                             } catch (Exception re) {
230                                 Log.e(TAG,"",re);
231                             }
232                         }
233                     }
234                 }
235         };
236 
237     /**
238      * Create a BluetoothInputDevice proxy object for interacting with the local
239      * Bluetooth Service which handles the InputDevice profile
240      *
241      */
BluetoothInputDevice(Context context, ServiceListener l)242     /*package*/ BluetoothInputDevice(Context context, ServiceListener l) {
243         mContext = context;
244         mServiceListener = l;
245         mAdapter = BluetoothAdapter.getDefaultAdapter();
246 
247         IBluetoothManager mgr = mAdapter.getBluetoothManager();
248         if (mgr != null) {
249             try {
250                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
251             } catch (RemoteException e) {
252                 Log.e(TAG,"",e);
253             }
254         }
255 
256         doBind();
257     }
258 
doBind()259     boolean doBind() {
260         Intent intent = new Intent(IBluetoothInputDevice.class.getName());
261         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
262         intent.setComponent(comp);
263         if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
264                 android.os.Process.myUserHandle())) {
265             Log.e(TAG, "Could not bind to Bluetooth HID Service with " + intent);
266             return false;
267         }
268         return true;
269     }
270 
close()271     /*package*/ void close() {
272         if (VDBG) log("close()");
273         IBluetoothManager mgr = mAdapter.getBluetoothManager();
274         if (mgr != null) {
275             try {
276                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
277             } catch (Exception e) {
278                 Log.e(TAG,"",e);
279             }
280         }
281 
282         synchronized (mConnection) {
283             if (mService != null) {
284                 try {
285                     mService = null;
286                     mContext.unbindService(mConnection);
287                 } catch (Exception re) {
288                     Log.e(TAG,"",re);
289                 }
290            }
291         }
292         mServiceListener = null;
293     }
294 
295     /**
296      * Initiate connection to a profile of the remote bluetooth device.
297      *
298      * <p> The system supports connection to multiple input devices.
299      *
300      * <p> This API returns false in scenarios like the profile on the
301      * device is already connected or Bluetooth is not turned on.
302      * When this API returns true, it is guaranteed that
303      * connection state intent for the profile will be broadcasted with
304      * the state. Users can get the connection state of the profile
305      * from this intent.
306      *
307      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
308      * permission.
309      *
310      * @param device Remote Bluetooth Device
311      * @return false on immediate error,
312      *               true otherwise
313      * @hide
314      */
connect(BluetoothDevice device)315     public boolean connect(BluetoothDevice device) {
316         if (DBG) log("connect(" + device + ")");
317         if (mService != null && isEnabled() && isValidDevice(device)) {
318             try {
319                 return mService.connect(device);
320             } catch (RemoteException e) {
321                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
322                 return false;
323             }
324         }
325         if (mService == null) Log.w(TAG, "Proxy not attached to service");
326         return false;
327     }
328 
329     /**
330      * Initiate disconnection from a profile
331      *
332      * <p> This API will return false in scenarios like the profile on the
333      * Bluetooth device is not in connected state etc. When this API returns,
334      * true, it is guaranteed that the connection state change
335      * intent will be broadcasted with the state. Users can get the
336      * disconnection state of the profile from this intent.
337      *
338      * <p> If the disconnection is initiated by a remote device, the state
339      * will transition from {@link #STATE_CONNECTED} to
340      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
341      * host (local) device the state will transition from
342      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
343      * state {@link #STATE_DISCONNECTED}. The transition to
344      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
345      * two scenarios.
346      *
347      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
348      * permission.
349      *
350      * @param device Remote Bluetooth Device
351      * @return false on immediate error,
352      *               true otherwise
353      * @hide
354      */
disconnect(BluetoothDevice device)355     public boolean disconnect(BluetoothDevice device) {
356         if (DBG) log("disconnect(" + device + ")");
357         if (mService != null && isEnabled() && isValidDevice(device)) {
358             try {
359                 return mService.disconnect(device);
360             } catch (RemoteException e) {
361                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
362                 return false;
363             }
364         }
365         if (mService == null) Log.w(TAG, "Proxy not attached to service");
366         return false;
367     }
368 
369     /**
370      * {@inheritDoc}
371      */
getConnectedDevices()372     public List<BluetoothDevice> getConnectedDevices() {
373         if (VDBG) log("getConnectedDevices()");
374         if (mService != null && isEnabled()) {
375             try {
376                 return mService.getConnectedDevices();
377             } catch (RemoteException e) {
378                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
379                 return new ArrayList<BluetoothDevice>();
380             }
381         }
382         if (mService == null) Log.w(TAG, "Proxy not attached to service");
383         return new ArrayList<BluetoothDevice>();
384     }
385 
386     /**
387      * {@inheritDoc}
388      */
getDevicesMatchingConnectionStates(int[] states)389     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
390         if (VDBG) log("getDevicesMatchingStates()");
391         if (mService != null && isEnabled()) {
392             try {
393                 return mService.getDevicesMatchingConnectionStates(states);
394             } catch (RemoteException e) {
395                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
396                 return new ArrayList<BluetoothDevice>();
397             }
398         }
399         if (mService == null) Log.w(TAG, "Proxy not attached to service");
400         return new ArrayList<BluetoothDevice>();
401     }
402 
403     /**
404      * {@inheritDoc}
405      */
getConnectionState(BluetoothDevice device)406     public int getConnectionState(BluetoothDevice device) {
407         if (VDBG) log("getState(" + device + ")");
408         if (mService != null && isEnabled() && isValidDevice(device)) {
409             try {
410                 return mService.getConnectionState(device);
411             } catch (RemoteException e) {
412                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
413                 return BluetoothProfile.STATE_DISCONNECTED;
414             }
415         }
416         if (mService == null) Log.w(TAG, "Proxy not attached to service");
417         return BluetoothProfile.STATE_DISCONNECTED;
418     }
419 
420     /**
421      * Set priority of the profile
422      *
423      * <p> The device should already be paired.
424      *  Priority can be one of {@link #PRIORITY_ON} or
425      * {@link #PRIORITY_OFF},
426      *
427      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
428      * permission.
429      *
430      * @param device Paired bluetooth device
431      * @param priority
432      * @return true if priority is set, false on error
433      * @hide
434      */
setPriority(BluetoothDevice device, int priority)435     public boolean setPriority(BluetoothDevice device, int priority) {
436         if (DBG) log("setPriority(" + device + ", " + priority + ")");
437         if (mService != null && isEnabled() && isValidDevice(device)) {
438             if (priority != BluetoothProfile.PRIORITY_OFF &&
439                 priority != BluetoothProfile.PRIORITY_ON) {
440               return false;
441             }
442             try {
443                 return mService.setPriority(device, priority);
444             } catch (RemoteException e) {
445                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
446                 return false;
447             }
448         }
449         if (mService == null) Log.w(TAG, "Proxy not attached to service");
450         return false;
451     }
452 
453     /**
454      * Get the priority of the profile.
455      *
456      * <p> The priority can be any of:
457      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
458      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
459      *
460      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
461      *
462      * @param device Bluetooth device
463      * @return priority of the device
464      * @hide
465      */
getPriority(BluetoothDevice device)466     public int getPriority(BluetoothDevice device) {
467         if (VDBG) log("getPriority(" + device + ")");
468         if (mService != null && isEnabled() && isValidDevice(device)) {
469             try {
470                 return mService.getPriority(device);
471             } catch (RemoteException e) {
472                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
473                 return BluetoothProfile.PRIORITY_OFF;
474             }
475         }
476         if (mService == null) Log.w(TAG, "Proxy not attached to service");
477         return BluetoothProfile.PRIORITY_OFF;
478     }
479 
480     private final ServiceConnection mConnection = new ServiceConnection() {
481         public void onServiceConnected(ComponentName className, IBinder service) {
482             if (DBG) Log.d(TAG, "Proxy object connected");
483             mService = IBluetoothInputDevice.Stub.asInterface(Binder.allowBlocking(service));
484 
485             if (mServiceListener != null) {
486                 mServiceListener.onServiceConnected(BluetoothProfile.INPUT_DEVICE, BluetoothInputDevice.this);
487             }
488         }
489         public void onServiceDisconnected(ComponentName className) {
490             if (DBG) Log.d(TAG, "Proxy object disconnected");
491             mService = null;
492             if (mServiceListener != null) {
493                 mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_DEVICE);
494             }
495         }
496     };
497 
isEnabled()498     private boolean isEnabled() {
499        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
500        return false;
501     }
502 
isValidDevice(BluetoothDevice device)503     private boolean isValidDevice(BluetoothDevice device) {
504        if (device == null) return false;
505 
506        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
507        return false;
508     }
509 
510 
511     /**
512      * Initiate virtual unplug for a HID input device.
513      *
514      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
515      *
516      * @param device Remote Bluetooth Device
517      * @return false on immediate error,
518      *               true otherwise
519      * @hide
520      */
virtualUnplug(BluetoothDevice device)521     public boolean virtualUnplug(BluetoothDevice device) {
522         if (DBG) log("virtualUnplug(" + device + ")");
523         if (mService != null && isEnabled() && isValidDevice(device)) {
524             try {
525                 return mService.virtualUnplug(device);
526             } catch (RemoteException e) {
527                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
528                 return false;
529             }
530         }
531 
532         if (mService == null) Log.w(TAG, "Proxy not attached to service");
533         return false;
534 
535     }
536 
537     /**
538     * Send Get_Protocol_Mode command to the connected HID input device.
539     *
540     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
541     *
542     * @param device Remote Bluetooth Device
543     * @return false on immediate error,
544     *true otherwise
545     * @hide
546     */
getProtocolMode(BluetoothDevice device)547     public boolean getProtocolMode(BluetoothDevice device) {
548         if (VDBG) log("getProtocolMode(" + device + ")");
549         if (mService != null && isEnabled() && isValidDevice(device)) {
550             try {
551                 return mService.getProtocolMode(device);
552             } catch (RemoteException e) {
553                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
554                 return false;
555             }
556         }
557         if (mService == null) Log.w(TAG, "Proxy not attached to service");
558             return false;
559     }
560 
561     /**
562      * Send Set_Protocol_Mode command to the connected HID input device.
563      *
564      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
565      *
566      * @param device Remote Bluetooth Device
567      * @return false on immediate error,
568      *               true otherwise
569      * @hide
570      */
setProtocolMode(BluetoothDevice device, int protocolMode)571     public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
572         if (DBG) log("setProtocolMode(" + device + ")");
573         if (mService != null && isEnabled() && isValidDevice(device)) {
574             try {
575                 return mService.setProtocolMode(device, protocolMode);
576             } catch (RemoteException e) {
577                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
578                 return false;
579             }
580         }
581         if (mService == null) Log.w(TAG, "Proxy not attached to service");
582         return false;
583     }
584 
585     /**
586      * Send Get_Report command to the connected HID input device.
587      *
588      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
589      *
590      * @param device Remote Bluetooth Device
591      * @param reportType Report type
592      * @param reportId Report ID
593      * @param bufferSize Report receiving buffer size
594      * @return false on immediate error,
595      *               true otherwise
596      * @hide
597      */
getReport(BluetoothDevice device, byte reportType, byte reportId, int bufferSize)598     public boolean getReport(BluetoothDevice device, byte reportType, byte reportId, int bufferSize) {
599         if (VDBG) log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId + "bufferSize=" + bufferSize);
600         if (mService != null && isEnabled() && isValidDevice(device)) {
601             try {
602                 return mService.getReport(device, reportType, reportId, bufferSize);
603             } catch (RemoteException e) {
604                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
605                 return false;
606             }
607         }
608         if (mService == null) Log.w(TAG, "Proxy not attached to service");
609         return false;
610     }
611 
612     /**
613      * Send Set_Report command to the connected HID input device.
614      *
615      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
616      *
617      * @param device Remote Bluetooth Device
618      * @param reportType Report type
619      * @param report Report receiving buffer size
620      * @return false on immediate error,
621      *               true otherwise
622      * @hide
623      */
setReport(BluetoothDevice device, byte reportType, String report)624     public boolean setReport(BluetoothDevice device, byte reportType, String report) {
625         if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
626         if (mService != null && isEnabled() && isValidDevice(device)) {
627             try {
628                 return mService.setReport(device, reportType, report);
629             } catch (RemoteException e) {
630                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
631                 return false;
632             }
633         }
634         if (mService == null) Log.w(TAG, "Proxy not attached to service");
635         return false;
636     }
637 
638     /**
639      * Send Send_Data command to the connected HID input device.
640      *
641      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
642      *
643      * @param device Remote Bluetooth Device
644      * @param report Report to send
645      * @return false on immediate error,
646      *               true otherwise
647      * @hide
648      */
sendData(BluetoothDevice device, String report)649     public boolean sendData(BluetoothDevice device, String report) {
650         if (DBG) log("sendData(" + device + "), report=" + report);
651         if (mService != null && isEnabled() && isValidDevice(device)) {
652             try {
653                 return mService.sendData(device, report);
654             } catch (RemoteException e) {
655                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
656                 return false;
657             }
658         }
659         if (mService == null) Log.w(TAG, "Proxy not attached to service");
660         return false;
661     }
log(String msg)662     private static void log(String msg) {
663       Log.d(TAG, msg);
664     }
665 }
666