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