• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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.os.Handler;
24 import android.os.IBinder;
25 import android.os.Looper;
26 import android.os.Message;
27 import android.os.RemoteException;
28 import android.util.Log;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 /**
34  * Public API for controlling the Bluetooth Headset Service. This includes both
35  * Bluetooth Headset and Handsfree (v1.5) profiles.
36  *
37  * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
38  * Service via IPC.
39  *
40  * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
41  * the BluetoothHeadset proxy object. Use
42  * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
43  *
44  * <p> Android only supports one connected Bluetooth Headset at a time.
45  * Each method is protected with its appropriate permission.
46  */
47 public final class BluetoothHeadset implements BluetoothProfile {
48     private static final String TAG = "BluetoothHeadset";
49     private static final boolean DBG = true;
50     private static final boolean VDBG = false;
51 
52     /**
53      * Intent used to broadcast the change in connection state of the Headset
54      * profile.
55      *
56      * <p>This intent will have 3 extras:
57      * <ul>
58      *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
59      *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
60      *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
61      * </ul>
62      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
63      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
64      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
65      *
66      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
67      * receive.
68      */
69     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
70     public static final String ACTION_CONNECTION_STATE_CHANGED =
71         "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
72 
73     /**
74      * Intent used to broadcast the change in the Audio Connection state of the
75      * A2DP profile.
76      *
77      * <p>This intent will have 3 extras:
78      * <ul>
79      *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
80      *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
81      *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
82      * </ul>
83      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
84      * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
85      *
86      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
87      * to receive.
88      */
89     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
90     public static final String ACTION_AUDIO_STATE_CHANGED =
91         "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
92 
93 
94     /**
95      * Intent used to broadcast that the headset has posted a
96      * vendor-specific event.
97      *
98      * <p>This intent will have 4 extras and 1 category.
99      * <ul>
100      *  <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device
101      *       </li>
102      *  <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor
103      *       specific command </li>
104      *  <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT
105      *       command type which can be one of  {@link #AT_CMD_TYPE_READ},
106      *       {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET},
107      *       {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li>
108      *  <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command
109      *       arguments. </li>
110      * </ul>
111      *
112      *<p> The category is the Company ID of the vendor defining the
113      * vendor-specific command. {@link BluetoothAssignedNumbers}
114      *
115      * For example, for Plantronics specific events
116      * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55
117      *
118      * <p> For example, an AT+XEVENT=foo,3 will get translated into
119      * <ul>
120      *   <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li>
121      *   <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li>
122      *   <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li>
123      * </ul>
124      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
125      * to receive.
126      */
127     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
128     public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
129             "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
130 
131     /**
132      * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
133      * intents that contains the name of the vendor-specific command.
134      */
135     public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
136             "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
137 
138     /**
139      * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
140      * intents that contains the AT command type of the vendor-specific command.
141      */
142     public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE =
143             "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE";
144 
145     /**
146      * AT command type READ used with
147      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
148      * For example, AT+VGM?. There are no arguments for this command type.
149      */
150     public static final int AT_CMD_TYPE_READ = 0;
151 
152     /**
153      * AT command type TEST used with
154      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
155      * For example, AT+VGM=?. There are no arguments for this command type.
156      */
157     public static final int AT_CMD_TYPE_TEST = 1;
158 
159     /**
160      * AT command type SET used with
161      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
162      * For example, AT+VGM=<args>.
163      */
164     public static final int AT_CMD_TYPE_SET = 2;
165 
166     /**
167      * AT command type BASIC used with
168      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
169      * For example, ATD. Single character commands and everything following the
170      * character are arguments.
171      */
172     public static final int AT_CMD_TYPE_BASIC = 3;
173 
174     /**
175      * AT command type ACTION used with
176      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
177      * For example, AT+CHUP. There are no arguments for action commands.
178      */
179     public static final int AT_CMD_TYPE_ACTION = 4;
180 
181     /**
182      * A Parcelable String array extra field in
183      * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
184      * the arguments to the vendor-specific command.
185      */
186     public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
187             "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
188 
189     /**
190      * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
191      * for the companyId
192      */
193     public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY  =
194             "android.bluetooth.headset.intent.category.companyid";
195 
196     /**
197      * A vendor-specific command for unsolicited result code.
198      */
199     public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID";
200 
201     /**
202      * Headset state when SCO audio is not connected.
203      * This state can be one of
204      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
205      * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
206      */
207     public static final int STATE_AUDIO_DISCONNECTED = 10;
208 
209     /**
210      * Headset state when SCO audio is connecting.
211      * This state can be one of
212      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
213      * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
214      */
215     public static final int STATE_AUDIO_CONNECTING = 11;
216 
217     /**
218      * Headset state when SCO audio is connected.
219      * This state can be one of
220      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
221      * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
222      */
223     public static final int STATE_AUDIO_CONNECTED = 12;
224 
225     private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100;
226     private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101;
227 
228     private Context mContext;
229     private ServiceListener mServiceListener;
230     private IBluetoothHeadset mService;
231     private BluetoothAdapter mAdapter;
232 
233     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
234             new IBluetoothStateChangeCallback.Stub() {
235                 public void onBluetoothStateChange(boolean up) {
236                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
237                     if (!up) {
238                         if (VDBG) Log.d(TAG,"Unbinding service...");
239                         doUnbind();
240                     } else {
241                         synchronized (mConnection) {
242                             try {
243                                 if (mService == null) {
244                                     if (VDBG) Log.d(TAG,"Binding service...");
245                                     doBind();
246                                 }
247                             } catch (Exception re) {
248                                 Log.e(TAG,"",re);
249                             }
250                         }
251                     }
252                 }
253         };
254 
255     /**
256      * Create a BluetoothHeadset proxy object.
257      */
BluetoothHeadset(Context context, ServiceListener l)258     /*package*/ BluetoothHeadset(Context context, ServiceListener l) {
259         mContext = context;
260         mServiceListener = l;
261         mAdapter = BluetoothAdapter.getDefaultAdapter();
262 
263         IBluetoothManager mgr = mAdapter.getBluetoothManager();
264         if (mgr != null) {
265             try {
266                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
267             } catch (RemoteException e) {
268                 Log.e(TAG,"",e);
269             }
270         }
271 
272         doBind();
273     }
274 
doBind()275     boolean doBind() {
276         try {
277             return mAdapter.getBluetoothManager().bindBluetoothProfileService(
278                     BluetoothProfile.HEADSET, mConnection);
279         } catch (RemoteException e) {
280             Log.e(TAG, "Unable to bind HeadsetService", e);
281         }
282         return false;
283     }
284 
doUnbind()285     void doUnbind() {
286         synchronized (mConnection) {
287             if (mService != null) {
288                 try {
289                     mAdapter.getBluetoothManager().unbindBluetoothProfileService(
290                             BluetoothProfile.HEADSET, mConnection);
291                 } catch (RemoteException e) {
292                     Log.e(TAG,"Unable to unbind HeadsetService", e);
293                 }
294             }
295         }
296     }
297 
298     /**
299      * Close the connection to the backing service.
300      * Other public functions of BluetoothHeadset will return default error
301      * results once close() has been called. Multiple invocations of close()
302      * are ok.
303      */
close()304     /*package*/ void close() {
305         if (VDBG) log("close()");
306 
307         IBluetoothManager mgr = mAdapter.getBluetoothManager();
308         if (mgr != null) {
309             try {
310                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
311             } catch (Exception e) {
312                 Log.e(TAG,"",e);
313             }
314         }
315         mServiceListener = null;
316         doUnbind();
317     }
318 
319     /**
320      * Initiate connection to a profile of the remote bluetooth device.
321      *
322      * <p> Currently, the system supports only 1 connection to the
323      * headset/handsfree profile. The API will automatically disconnect connected
324      * devices before connecting.
325      *
326      * <p> This API returns false in scenarios like the profile on the
327      * device is already connected or Bluetooth is not turned on.
328      * When this API returns true, it is guaranteed that
329      * connection state intent for the profile will be broadcasted with
330      * the state. Users can get the connection state of the profile
331      * from this intent.
332      *
333      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
334      * permission.
335      *
336      * @param device Remote Bluetooth Device
337      * @return false on immediate error,
338      *               true otherwise
339      * @hide
340      */
connect(BluetoothDevice device)341     public boolean connect(BluetoothDevice device) {
342         if (DBG) log("connect(" + device + ")");
343         if (mService != null && isEnabled() &&
344             isValidDevice(device)) {
345             try {
346                 return mService.connect(device);
347             } catch (RemoteException e) {
348                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
349                 return false;
350             }
351         }
352         if (mService == null) Log.w(TAG, "Proxy not attached to service");
353         return false;
354     }
355 
356     /**
357      * Initiate disconnection from a profile
358      *
359      * <p> This API will return false in scenarios like the profile on the
360      * Bluetooth device is not in connected state etc. When this API returns,
361      * true, it is guaranteed that the connection state change
362      * intent will be broadcasted with the state. Users can get the
363      * disconnection state of the profile from this intent.
364      *
365      * <p> If the disconnection is initiated by a remote device, the state
366      * will transition from {@link #STATE_CONNECTED} to
367      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
368      * host (local) device the state will transition from
369      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
370      * state {@link #STATE_DISCONNECTED}. The transition to
371      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
372      * two scenarios.
373      *
374      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
375      * permission.
376      *
377      * @param device Remote Bluetooth Device
378      * @return false on immediate error,
379      *               true otherwise
380      * @hide
381      */
disconnect(BluetoothDevice device)382     public boolean disconnect(BluetoothDevice device) {
383         if (DBG) log("disconnect(" + device + ")");
384         if (mService != null && isEnabled() &&
385             isValidDevice(device)) {
386             try {
387                 return mService.disconnect(device);
388             } catch (RemoteException e) {
389               Log.e(TAG, Log.getStackTraceString(new Throwable()));
390               return false;
391             }
392         }
393         if (mService == null) Log.w(TAG, "Proxy not attached to service");
394         return false;
395     }
396 
397     /**
398      * {@inheritDoc}
399      */
getConnectedDevices()400     public List<BluetoothDevice> getConnectedDevices() {
401         if (VDBG) log("getConnectedDevices()");
402         if (mService != null && isEnabled()) {
403             try {
404                 return mService.getConnectedDevices();
405             } catch (RemoteException e) {
406                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
407                 return new ArrayList<BluetoothDevice>();
408             }
409         }
410         if (mService == null) Log.w(TAG, "Proxy not attached to service");
411         return new ArrayList<BluetoothDevice>();
412     }
413 
414     /**
415      * {@inheritDoc}
416      */
getDevicesMatchingConnectionStates(int[] states)417     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
418         if (VDBG) log("getDevicesMatchingStates()");
419         if (mService != null && isEnabled()) {
420             try {
421                 return mService.getDevicesMatchingConnectionStates(states);
422             } catch (RemoteException e) {
423                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
424                 return new ArrayList<BluetoothDevice>();
425             }
426         }
427         if (mService == null) Log.w(TAG, "Proxy not attached to service");
428         return new ArrayList<BluetoothDevice>();
429     }
430 
431     /**
432      * {@inheritDoc}
433      */
getConnectionState(BluetoothDevice device)434     public int getConnectionState(BluetoothDevice device) {
435         if (VDBG) log("getConnectionState(" + device + ")");
436         if (mService != null && isEnabled() &&
437             isValidDevice(device)) {
438             try {
439                 return mService.getConnectionState(device);
440             } catch (RemoteException e) {
441                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
442                 return BluetoothProfile.STATE_DISCONNECTED;
443             }
444         }
445         if (mService == null) Log.w(TAG, "Proxy not attached to service");
446         return BluetoothProfile.STATE_DISCONNECTED;
447     }
448 
449     /**
450      * Set priority of the profile
451      *
452      * <p> The device should already be paired.
453      *  Priority can be one of {@link #PRIORITY_ON} or
454      * {@link #PRIORITY_OFF},
455      *
456      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
457      * permission.
458      *
459      * @param device Paired bluetooth device
460      * @param priority
461      * @return true if priority is set, false on error
462      * @hide
463      */
setPriority(BluetoothDevice device, int priority)464     public boolean setPriority(BluetoothDevice device, int priority) {
465         if (DBG) log("setPriority(" + device + ", " + priority + ")");
466         if (mService != null && isEnabled() &&
467             isValidDevice(device)) {
468             if (priority != BluetoothProfile.PRIORITY_OFF &&
469                 priority != BluetoothProfile.PRIORITY_ON) {
470               return false;
471             }
472             try {
473                 return mService.setPriority(device, priority);
474             } catch (RemoteException e) {
475                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
476                 return false;
477             }
478         }
479         if (mService == null) Log.w(TAG, "Proxy not attached to service");
480         return false;
481     }
482 
483     /**
484      * Get the priority of the profile.
485      *
486      * <p> The priority can be any of:
487      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
488      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
489      *
490      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
491      *
492      * @param device Bluetooth device
493      * @return priority of the device
494      * @hide
495      */
getPriority(BluetoothDevice device)496     public int getPriority(BluetoothDevice device) {
497         if (VDBG) log("getPriority(" + device + ")");
498         if (mService != null && isEnabled() &&
499             isValidDevice(device)) {
500             try {
501                 return mService.getPriority(device);
502             } catch (RemoteException e) {
503                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
504                 return PRIORITY_OFF;
505             }
506         }
507         if (mService == null) Log.w(TAG, "Proxy not attached to service");
508         return PRIORITY_OFF;
509     }
510 
511     /**
512      * Start Bluetooth voice recognition. This methods sends the voice
513      * recognition AT command to the headset and establishes the
514      * audio connection.
515      *
516      * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
517      * If this function returns true, this intent will be broadcasted with
518      * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
519      *
520      * <p> {@link #EXTRA_STATE} will transition from
521      * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
522      * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
523      * in case of failure to establish the audio connection.
524      *
525      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
526      *
527      * @param device Bluetooth headset
528      * @return false if there is no headset connected of if the
529      *               connected headset doesn't support voice recognition
530      *               or on error, true otherwise
531      */
startVoiceRecognition(BluetoothDevice device)532     public boolean startVoiceRecognition(BluetoothDevice device) {
533         if (DBG) log("startVoiceRecognition()");
534         if (mService != null && isEnabled() &&
535             isValidDevice(device)) {
536             try {
537                 return mService.startVoiceRecognition(device);
538             } catch (RemoteException e) {
539                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
540             }
541         }
542         if (mService == null) Log.w(TAG, "Proxy not attached to service");
543         return false;
544     }
545 
546     /**
547      * Stop Bluetooth Voice Recognition mode, and shut down the
548      * Bluetooth audio path.
549      *
550      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
551      *
552      * @param device Bluetooth headset
553      * @return false if there is no headset connected
554      *               or on error, true otherwise
555      */
stopVoiceRecognition(BluetoothDevice device)556     public boolean stopVoiceRecognition(BluetoothDevice device) {
557         if (DBG) log("stopVoiceRecognition()");
558         if (mService != null && isEnabled() &&
559             isValidDevice(device)) {
560             try {
561                 return mService.stopVoiceRecognition(device);
562             } catch (RemoteException e) {
563                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
564             }
565         }
566         if (mService == null) Log.w(TAG, "Proxy not attached to service");
567         return false;
568     }
569 
570     /**
571      * Check if Bluetooth SCO audio is connected.
572      *
573      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
574      *
575      * @param device Bluetooth headset
576      * @return true if SCO is connected,
577      *         false otherwise or on error
578      */
isAudioConnected(BluetoothDevice device)579     public boolean isAudioConnected(BluetoothDevice device) {
580         if (VDBG) log("isAudioConnected()");
581         if (mService != null && isEnabled() &&
582             isValidDevice(device)) {
583             try {
584               return mService.isAudioConnected(device);
585             } catch (RemoteException e) {
586               Log.e(TAG,  Log.getStackTraceString(new Throwable()));
587             }
588         }
589         if (mService == null) Log.w(TAG, "Proxy not attached to service");
590         return false;
591     }
592 
593     /**
594      * Get battery usage hint for Bluetooth Headset service.
595      * This is a monotonically increasing integer. Wraps to 0 at
596      * Integer.MAX_INT, and at boot.
597      * Current implementation returns the number of AT commands handled since
598      * boot. This is a good indicator for spammy headset/handsfree units that
599      * can keep the device awake by polling for cellular status updates. As a
600      * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
601      *
602      * @param device the bluetooth headset.
603      * @return monotonically increasing battery usage hint, or a negative error
604      *         code on error
605      * @hide
606      */
getBatteryUsageHint(BluetoothDevice device)607     public int getBatteryUsageHint(BluetoothDevice device) {
608         if (VDBG) log("getBatteryUsageHint()");
609         if (mService != null && isEnabled() &&
610             isValidDevice(device)) {
611             try {
612                 return mService.getBatteryUsageHint(device);
613             } catch (RemoteException e) {
614                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
615             }
616         }
617         if (mService == null) Log.w(TAG, "Proxy not attached to service");
618         return -1;
619     }
620 
621     /**
622      * Indicates if current platform supports voice dialing over bluetooth SCO.
623      *
624      * @return true if voice dialing over bluetooth is supported, false otherwise.
625      * @hide
626      */
isBluetoothVoiceDialingEnabled(Context context)627     public static boolean isBluetoothVoiceDialingEnabled(Context context) {
628         return context.getResources().getBoolean(
629                 com.android.internal.R.bool.config_bluetooth_sco_off_call);
630     }
631 
632     /**
633      * Accept the incoming connection.
634      * Note: This is an internal function and shouldn't be exposed
635      *
636      * @hide
637      */
acceptIncomingConnect(BluetoothDevice device)638     public boolean acceptIncomingConnect(BluetoothDevice device) {
639         if (DBG) log("acceptIncomingConnect");
640         if (mService != null && isEnabled()) {
641             try {
642                 return mService.acceptIncomingConnect(device);
643             } catch (RemoteException e) {Log.e(TAG, e.toString());}
644         } else {
645             Log.w(TAG, "Proxy not attached to service");
646             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
647         }
648         return false;
649     }
650 
651     /**
652      * Reject the incoming connection.
653      * @hide
654      */
rejectIncomingConnect(BluetoothDevice device)655     public boolean rejectIncomingConnect(BluetoothDevice device) {
656         if (DBG) log("rejectIncomingConnect");
657         if (mService != null) {
658             try {
659                 return mService.rejectIncomingConnect(device);
660             } catch (RemoteException e) {Log.e(TAG, e.toString());}
661         } else {
662             Log.w(TAG, "Proxy not attached to service");
663             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
664         }
665         return false;
666     }
667 
668     /**
669      * Get the current audio state of the Headset.
670      * Note: This is an internal function and shouldn't be exposed
671      *
672      * @hide
673      */
getAudioState(BluetoothDevice device)674     public int getAudioState(BluetoothDevice device) {
675         if (VDBG) log("getAudioState");
676         if (mService != null && !isDisabled()) {
677             try {
678                 return mService.getAudioState(device);
679             } catch (RemoteException e) {Log.e(TAG, e.toString());}
680         } else {
681             Log.w(TAG, "Proxy not attached to service");
682             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
683         }
684         return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
685     }
686 
687     /**
688      * Check if Bluetooth SCO audio is connected.
689      *
690      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
691      *
692      * @return true if SCO is connected,
693      *         false otherwise or on error
694      * @hide
695      */
isAudioOn()696     public boolean isAudioOn() {
697         if (VDBG) log("isAudioOn()");
698         if (mService != null && isEnabled()) {
699             try {
700               return mService.isAudioOn();
701             } catch (RemoteException e) {
702               Log.e(TAG,  Log.getStackTraceString(new Throwable()));
703             }
704         }
705         if (mService == null) Log.w(TAG, "Proxy not attached to service");
706         return false;
707 
708     }
709 
710     /**
711      * Initiates a connection of headset audio.
712      * It setup SCO channel with remote connected headset device.
713      *
714      * @return true if successful
715      *         false if there was some error such as
716      *               there is no connected headset
717      * @hide
718      */
connectAudio()719     public boolean connectAudio() {
720         if (mService != null && isEnabled()) {
721             try {
722                 return mService.connectAudio();
723             } catch (RemoteException e) {
724                 Log.e(TAG, e.toString());
725             }
726         } else {
727             Log.w(TAG, "Proxy not attached to service");
728             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
729         }
730         return false;
731     }
732 
733     /**
734      * Initiates a disconnection of headset audio.
735      * It tears down the SCO channel from remote headset device.
736      *
737      * @return true if successful
738      *         false if there was some error such as
739      *               there is no connected SCO channel
740      * @hide
741      */
disconnectAudio()742     public boolean disconnectAudio() {
743         if (mService != null && isEnabled()) {
744             try {
745                 return mService.disconnectAudio();
746             } catch (RemoteException e) {
747                 Log.e(TAG, e.toString());
748             }
749         } else {
750             Log.w(TAG, "Proxy not attached to service");
751             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
752         }
753         return false;
754     }
755 
756     /**
757      * Initiates a SCO channel connection with the headset (if connected).
758      * Also initiates a virtual voice call for Handsfree devices as many devices
759      * do not accept SCO audio without a call.
760      * This API allows the handsfree device to be used for routing non-cellular
761      * call audio.
762      *
763      * @param device Remote Bluetooth Device
764      * @return true if successful, false if there was some error.
765      * @hide
766      */
startScoUsingVirtualVoiceCall(BluetoothDevice device)767     public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
768         if (DBG) log("startScoUsingVirtualVoiceCall()");
769         if (mService != null && isEnabled() && isValidDevice(device)) {
770             try {
771                 return mService.startScoUsingVirtualVoiceCall(device);
772             } catch (RemoteException e) {
773                 Log.e(TAG, e.toString());
774             }
775         } else {
776             Log.w(TAG, "Proxy not attached to service");
777             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
778         }
779         return false;
780     }
781 
782     /**
783      * Terminates an ongoing SCO connection and the associated virtual
784      * call.
785      *
786      * @param device Remote Bluetooth Device
787      * @return true if successful, false if there was some error.
788      * @hide
789      */
stopScoUsingVirtualVoiceCall(BluetoothDevice device)790     public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
791         if (DBG) log("stopScoUsingVirtualVoiceCall()");
792         if (mService != null && isEnabled() && isValidDevice(device)) {
793             try {
794                 return mService.stopScoUsingVirtualVoiceCall(device);
795             } catch (RemoteException e) {
796                 Log.e(TAG, e.toString());
797             }
798         } else {
799             Log.w(TAG, "Proxy not attached to service");
800             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
801         }
802         return false;
803     }
804 
805     /**
806      * Notify Headset of phone state change.
807      * This is a backdoor for phone app to call BluetoothHeadset since
808      * there is currently not a good way to get precise call state change outside
809      * of phone app.
810      *
811      * @hide
812      */
phoneStateChanged(int numActive, int numHeld, int callState, String number, int type)813     public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
814                                   int type) {
815         if (mService != null && isEnabled()) {
816             try {
817                 mService.phoneStateChanged(numActive, numHeld, callState, number, type);
818             } catch (RemoteException e) {
819                 Log.e(TAG, e.toString());
820             }
821         } else {
822             Log.w(TAG, "Proxy not attached to service");
823             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
824         }
825     }
826 
827     /**
828      * Send Headset of CLCC response
829      *
830      * @hide
831      */
clccResponse(int index, int direction, int status, int mode, boolean mpty, String number, int type)832     public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
833                              String number, int type) {
834         if (mService != null && isEnabled()) {
835             try {
836                 mService.clccResponse(index, direction, status, mode, mpty, number, type);
837             } catch (RemoteException e) {
838                 Log.e(TAG, e.toString());
839             }
840         } else {
841             Log.w(TAG, "Proxy not attached to service");
842             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
843         }
844     }
845 
846     /**
847      * Sends a vendor-specific unsolicited result code to the headset.
848      *
849      * <p>The actual string to be sent is <code>command + ": " + arg</code>.
850      * For example, if {@code command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg}
851      * is {@code "0"}, the string <code>"+ANDROID: 0"</code> will be sent.
852      *
853      * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
854      *
855      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
856      *
857      * @param device Bluetooth headset.
858      * @param command A vendor-specific command.
859      * @param arg The argument that will be attached to the command.
860      * @return {@code false} if there is no headset connected, or if the command is not an allowed
861      *         vendor-specific unsolicited result code, or on error. {@code true} otherwise.
862      * @throws IllegalArgumentException if {@code command} is {@code null}.
863      */
sendVendorSpecificResultCode(BluetoothDevice device, String command, String arg)864     public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
865             String arg) {
866         if (DBG) {
867             log("sendVendorSpecificResultCode()");
868         }
869         if (command == null) {
870             throw new IllegalArgumentException("command is null");
871         }
872         if (mService != null && isEnabled() &&
873                 isValidDevice(device)) {
874             try {
875                 return mService.sendVendorSpecificResultCode(device, command, arg);
876             } catch (RemoteException e) {
877                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
878             }
879         }
880         if (mService == null) {
881             Log.w(TAG, "Proxy not attached to service");
882         }
883         return false;
884     }
885 
886     /**
887      * enable WBS codec setting.
888      *
889      * @return true if successful
890      *         false if there was some error such as
891      *               there is no connected headset
892      * @hide
893      */
enableWBS()894     public boolean enableWBS() {
895         if (mService != null && isEnabled()) {
896             try {
897                 return mService.enableWBS();
898             } catch (RemoteException e) {
899                 Log.e(TAG, e.toString());
900             }
901         } else {
902             Log.w(TAG, "Proxy not attached to service");
903             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
904         }
905         return false;
906     }
907 
908     /**
909      * disable WBS codec settting. It set NBS codec.
910      *
911      * @return true if successful
912      *         false if there was some error such as
913      *               there is no connected headset
914      * @hide
915      */
disableWBS()916     public boolean disableWBS() {
917         if (mService != null && isEnabled()) {
918             try {
919                 return mService.disableWBS();
920             } catch (RemoteException e) {
921                 Log.e(TAG, e.toString());
922             }
923         } else {
924             Log.w(TAG, "Proxy not attached to service");
925             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
926         }
927         return false;
928     }
929 
930     private final IBluetoothProfileServiceConnection mConnection
931             = new IBluetoothProfileServiceConnection.Stub()  {
932         @Override
933         public void onServiceConnected(ComponentName className, IBinder service) {
934             if (DBG) Log.d(TAG, "Proxy object connected");
935             mService = IBluetoothHeadset.Stub.asInterface(service);
936             mHandler.sendMessage(mHandler.obtainMessage(
937                     MESSAGE_HEADSET_SERVICE_CONNECTED));
938         }
939         @Override
940         public void onServiceDisconnected(ComponentName className) {
941             if (DBG) Log.d(TAG, "Proxy object disconnected");
942             mService = null;
943             mHandler.sendMessage(mHandler.obtainMessage(
944                     MESSAGE_HEADSET_SERVICE_DISCONNECTED));
945         }
946     };
947 
isEnabled()948     private boolean isEnabled() {
949        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
950        return false;
951     }
952 
isDisabled()953     private boolean isDisabled() {
954        if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) return true;
955        return false;
956     }
957 
isValidDevice(BluetoothDevice device)958     private boolean isValidDevice(BluetoothDevice device) {
959        if (device == null) return false;
960 
961        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
962        return false;
963     }
964 
log(String msg)965     private static void log(String msg) {
966         Log.d(TAG, msg);
967     }
968 
969     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
970         @Override
971         public void handleMessage(Message msg) {
972             switch (msg.what) {
973                 case MESSAGE_HEADSET_SERVICE_CONNECTED: {
974                     if (mServiceListener != null) {
975                         mServiceListener.onServiceConnected(BluetoothProfile.HEADSET,
976                                 BluetoothHeadset.this);
977                     }
978                     break;
979                 }
980                 case MESSAGE_HEADSET_SERVICE_DISCONNECTED: {
981                     if (mServiceListener != null) {
982                         mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
983                     }
984                     break;
985                 }
986             }
987         }
988     };
989 }
990