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