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
17package android.bluetooth;
18
19import android.annotation.SdkConstant;
20import android.annotation.SdkConstant.SdkConstantType;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ServiceConnection;
25import android.os.IBinder;
26import android.os.RemoteException;
27import android.util.Log;
28
29import java.util.ArrayList;
30import 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 */
44public 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     */
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
497    private boolean isEnabled() {
498       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
499       return false;
500    }
501
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     */
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    */
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     */
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     */
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     */
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     */
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    }
661    private static void log(String msg) {
662      Log.d(TAG, msg);
663    }
664}
665