• 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.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  * Public API for controlling the Bluetooth Headset Service. This includes both
34  * Bluetooth Headset and Handsfree (v1.5) profiles.
35  *
36  * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
37  * Service via IPC.
38  *
39  * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
40  * the BluetoothHeadset proxy object. Use
41  * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
42  *
43  * <p> Android only supports one connected Bluetooth Headset at a time.
44  * Each method is protected with its appropriate permission.
45  */
46 public final class BluetoothHeadset implements BluetoothProfile {
47     private static final String TAG = "BluetoothHeadset";
48     private static final boolean DBG = false;
49 
50     /**
51      * Intent used to broadcast the change in connection state of the Headset
52      * profile.
53      *
54      * <p>This intent will have 3 extras:
55      * <ul>
56      *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
57      *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
58      *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
59      * </ul>
60      * <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.headset.profile.action.CONNECTION_STATE_CHANGED";
70 
71     /**
72      * Intent used to broadcast the change in the Audio Connection state of the
73      * A2DP profile.
74      *
75      * <p>This intent will have 3 extras:
76      * <ul>
77      *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
78      *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
79      *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
80      * </ul>
81      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
82      * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
83      *
84      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
85      * to receive.
86      */
87     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
88     public static final String ACTION_AUDIO_STATE_CHANGED =
89         "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
90 
91 
92     /**
93      * Intent used to broadcast that the headset has posted a
94      * vendor-specific event.
95      *
96      * <p>This intent will have 4 extras and 1 category.
97      * <ul>
98      *  <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device
99      *       </li>
100      *  <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor
101      *       specific command </li>
102      *  <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT
103      *       command type which can be one of  {@link #AT_CMD_TYPE_READ},
104      *       {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET},
105      *       {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li>
106      *  <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command
107      *       arguments. </li>
108      * </ul>
109      *
110      *<p> The category is the Company ID of the vendor defining the
111      * vendor-specific command. {@link BluetoothAssignedNumbers}
112      *
113      * For example, for Plantronics specific events
114      * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55
115      *
116      * <p> For example, an AT+XEVENT=foo,3 will get translated into
117      * <ul>
118      *   <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li>
119      *   <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li>
120      *   <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li>
121      * </ul>
122      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
123      * to receive.
124      */
125     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
126     public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
127             "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
128 
129     /**
130      * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
131      * intents that contains the name of the vendor-specific command.
132      */
133     public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
134             "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
135 
136     /**
137      * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
138      * intents that contains the AT command type of the vendor-specific command.
139      */
140     public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE =
141             "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE";
142 
143     /**
144      * AT command type READ used with
145      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
146      * For example, AT+VGM?. There are no arguments for this command type.
147      */
148     public static final int AT_CMD_TYPE_READ = 0;
149 
150     /**
151      * AT command type TEST used with
152      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
153      * For example, AT+VGM=?. There are no arguments for this command type.
154      */
155     public static final int AT_CMD_TYPE_TEST = 1;
156 
157     /**
158      * AT command type SET used with
159      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
160      * For example, AT+VGM=<args>.
161      */
162     public static final int AT_CMD_TYPE_SET = 2;
163 
164     /**
165      * AT command type BASIC used with
166      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
167      * For example, ATD. Single character commands and everything following the
168      * character are arguments.
169      */
170     public static final int AT_CMD_TYPE_BASIC = 3;
171 
172     /**
173      * AT command type ACTION used with
174      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
175      * For example, AT+CHUP. There are no arguments for action commands.
176      */
177     public static final int AT_CMD_TYPE_ACTION = 4;
178 
179     /**
180      * A Parcelable String array extra field in
181      * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
182      * the arguments to the vendor-specific command.
183      */
184     public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
185             "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
186 
187     /**
188      * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
189      * for the companyId
190      */
191     public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY  =
192             "android.bluetooth.headset.intent.category.companyid";
193 
194     /**
195      * Headset state when SCO audio is not connected.
196      * This state can be one of
197      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
198      * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
199      */
200     public static final int STATE_AUDIO_DISCONNECTED = 10;
201 
202     /**
203      * Headset state when SCO audio is connecting.
204      * This state can be one of
205      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
206      * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
207      */
208     public static final int STATE_AUDIO_CONNECTING = 11;
209 
210     /**
211      * Headset state when SCO audio is connected.
212      * This state can be one of
213      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
214      * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
215      */
216     public static final int STATE_AUDIO_CONNECTED = 12;
217 
218 
219     private Context mContext;
220     private ServiceListener mServiceListener;
221     private IBluetoothHeadset mService;
222     BluetoothAdapter mAdapter;
223 
224     /**
225      * Create a BluetoothHeadset proxy object.
226      */
BluetoothHeadset(Context context, ServiceListener l)227     /*package*/ BluetoothHeadset(Context context, ServiceListener l) {
228         mContext = context;
229         mServiceListener = l;
230         mAdapter = BluetoothAdapter.getDefaultAdapter();
231         if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) {
232             Log.e(TAG, "Could not bind to Bluetooth Headset Service");
233         }
234     }
235 
236     /**
237      * Close the connection to the backing service.
238      * Other public functions of BluetoothHeadset will return default error
239      * results once close() has been called. Multiple invocations of close()
240      * are ok.
241      */
close()242     /*package*/ synchronized void close() {
243         if (DBG) log("close()");
244         if (mConnection != null) {
245             mContext.unbindService(mConnection);
246             mConnection = null;
247         }
248     }
249 
250     /**
251      * Initiate connection to a profile of the remote bluetooth device.
252      *
253      * <p> Currently, the system supports only 1 connection to the
254      * headset/handsfree profile. The API will automatically disconnect connected
255      * devices before connecting.
256      *
257      * <p> This API returns false in scenarios like the profile on the
258      * device is already connected or Bluetooth is not turned on.
259      * When this API returns true, it is guaranteed that
260      * connection state intent for the profile will be broadcasted with
261      * the state. Users can get the connection state of the profile
262      * from this intent.
263      *
264      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
265      * permission.
266      *
267      * @param device Remote Bluetooth Device
268      * @return false on immediate error,
269      *               true otherwise
270      * @hide
271      */
connect(BluetoothDevice device)272     public boolean connect(BluetoothDevice device) {
273         if (DBG) log("connect(" + device + ")");
274         if (mService != null && isEnabled() &&
275             isValidDevice(device)) {
276             try {
277                 return mService.connect(device);
278             } catch (RemoteException e) {
279                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
280                 return false;
281             }
282         }
283         if (mService == null) Log.w(TAG, "Proxy not attached to service");
284         return false;
285     }
286 
287     /**
288      * Initiate disconnection from a profile
289      *
290      * <p> This API will return false in scenarios like the profile on the
291      * Bluetooth device is not in connected state etc. When this API returns,
292      * true, it is guaranteed that the connection state change
293      * intent will be broadcasted with the state. Users can get the
294      * disconnection state of the profile from this intent.
295      *
296      * <p> If the disconnection is initiated by a remote device, the state
297      * will transition from {@link #STATE_CONNECTED} to
298      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
299      * host (local) device the state will transition from
300      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
301      * state {@link #STATE_DISCONNECTED}. The transition to
302      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
303      * two scenarios.
304      *
305      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
306      * permission.
307      *
308      * @param device Remote Bluetooth Device
309      * @return false on immediate error,
310      *               true otherwise
311      * @hide
312      */
disconnect(BluetoothDevice device)313     public boolean disconnect(BluetoothDevice device) {
314         if (DBG) log("disconnect(" + device + ")");
315         if (mService != null && isEnabled() &&
316             isValidDevice(device)) {
317             try {
318                 return mService.disconnect(device);
319             } catch (RemoteException e) {
320               Log.e(TAG, 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      * {@inheritDoc}
330      */
getConnectedDevices()331     public List<BluetoothDevice> getConnectedDevices() {
332         if (DBG) log("getConnectedDevices()");
333         if (mService != null && isEnabled()) {
334             try {
335                 return mService.getConnectedDevices();
336             } catch (RemoteException e) {
337                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
338                 return new ArrayList<BluetoothDevice>();
339             }
340         }
341         if (mService == null) Log.w(TAG, "Proxy not attached to service");
342         return new ArrayList<BluetoothDevice>();
343     }
344 
345     /**
346      * {@inheritDoc}
347      */
getDevicesMatchingConnectionStates(int[] states)348     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
349         if (DBG) log("getDevicesMatchingStates()");
350         if (mService != null && isEnabled()) {
351             try {
352                 return mService.getDevicesMatchingConnectionStates(states);
353             } catch (RemoteException e) {
354                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
355                 return new ArrayList<BluetoothDevice>();
356             }
357         }
358         if (mService == null) Log.w(TAG, "Proxy not attached to service");
359         return new ArrayList<BluetoothDevice>();
360     }
361 
362     /**
363      * {@inheritDoc}
364      */
getConnectionState(BluetoothDevice device)365     public int getConnectionState(BluetoothDevice device) {
366         if (DBG) log("getConnectionState(" + device + ")");
367         if (mService != null && isEnabled() &&
368             isValidDevice(device)) {
369             try {
370                 return mService.getConnectionState(device);
371             } catch (RemoteException e) {
372                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
373                 return BluetoothProfile.STATE_DISCONNECTED;
374             }
375         }
376         if (mService == null) Log.w(TAG, "Proxy not attached to service");
377         return BluetoothProfile.STATE_DISCONNECTED;
378     }
379 
380     /**
381      * Set priority of the profile
382      *
383      * <p> The device should already be paired.
384      *  Priority can be one of {@link #PRIORITY_ON} or
385      * {@link #PRIORITY_OFF},
386      *
387      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
388      * permission.
389      *
390      * @param device Paired bluetooth device
391      * @param priority
392      * @return true if priority is set, false on error
393      * @hide
394      */
setPriority(BluetoothDevice device, int priority)395     public boolean setPriority(BluetoothDevice device, int priority) {
396         if (DBG) log("setPriority(" + device + ", " + priority + ")");
397         if (mService != null && isEnabled() &&
398             isValidDevice(device)) {
399             if (priority != BluetoothProfile.PRIORITY_OFF &&
400                 priority != BluetoothProfile.PRIORITY_ON) {
401               return false;
402             }
403             try {
404                 return mService.setPriority(device, priority);
405             } catch (RemoteException e) {
406                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
407                 return false;
408             }
409         }
410         if (mService == null) Log.w(TAG, "Proxy not attached to service");
411         return false;
412     }
413 
414     /**
415      * Get the priority of the profile.
416      *
417      * <p> The priority can be any of:
418      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
419      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
420      *
421      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
422      *
423      * @param device Bluetooth device
424      * @return priority of the device
425      * @hide
426      */
getPriority(BluetoothDevice device)427     public int getPriority(BluetoothDevice device) {
428         if (DBG) log("getPriority(" + device + ")");
429         if (mService != null && isEnabled() &&
430             isValidDevice(device)) {
431             try {
432                 return mService.getPriority(device);
433             } catch (RemoteException e) {
434                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
435                 return PRIORITY_OFF;
436             }
437         }
438         if (mService == null) Log.w(TAG, "Proxy not attached to service");
439         return PRIORITY_OFF;
440     }
441 
442     /**
443      * Start Bluetooth voice recognition. This methods sends the voice
444      * recognition AT command to the headset and establishes the
445      * audio connection.
446      *
447      * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
448      * If this function returns true, this intent will be broadcasted with
449      * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
450      *
451      * <p> {@link #EXTRA_STATE} will transition from
452      * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
453      * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
454      * in case of failure to establish the audio connection.
455      *
456      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
457      *
458      * @param device Bluetooth headset
459      * @return false if there is no headset connected of if the
460      *               connected headset doesn't support voice recognition
461      *               or on error, true otherwise
462      */
startVoiceRecognition(BluetoothDevice device)463     public boolean startVoiceRecognition(BluetoothDevice device) {
464         if (DBG) log("startVoiceRecognition()");
465         if (mService != null && isEnabled() &&
466             isValidDevice(device)) {
467             try {
468                 return mService.startVoiceRecognition(device);
469             } catch (RemoteException e) {
470                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
471             }
472         }
473         if (mService == null) Log.w(TAG, "Proxy not attached to service");
474         return false;
475     }
476 
477     /**
478      * Stop Bluetooth Voice Recognition mode, and shut down the
479      * Bluetooth audio path.
480      *
481      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
482      *
483      * @param device Bluetooth headset
484      * @return false if there is no headset connected
485      *               or on error, true otherwise
486      */
stopVoiceRecognition(BluetoothDevice device)487     public boolean stopVoiceRecognition(BluetoothDevice device) {
488         if (DBG) log("stopVoiceRecognition()");
489         if (mService != null && isEnabled() &&
490             isValidDevice(device)) {
491             try {
492                 return mService.stopVoiceRecognition(device);
493             } catch (RemoteException e) {
494                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
495             }
496         }
497         if (mService == null) Log.w(TAG, "Proxy not attached to service");
498         return false;
499     }
500 
501     /**
502      * Check if Bluetooth SCO audio is connected.
503      *
504      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
505      *
506      * @param device Bluetooth headset
507      * @return true if SCO is connected,
508      *         false otherwise or on error
509      */
isAudioConnected(BluetoothDevice device)510     public boolean isAudioConnected(BluetoothDevice device) {
511         if (DBG) log("isAudioConnected()");
512         if (mService != null && isEnabled() &&
513             isValidDevice(device)) {
514             try {
515               return mService.isAudioConnected(device);
516             } catch (RemoteException e) {
517               Log.e(TAG,  Log.getStackTraceString(new Throwable()));
518             }
519         }
520         if (mService == null) Log.w(TAG, "Proxy not attached to service");
521         return false;
522     }
523 
524     /**
525      * Get battery usage hint for Bluetooth Headset service.
526      * This is a monotonically increasing integer. Wraps to 0 at
527      * Integer.MAX_INT, and at boot.
528      * Current implementation returns the number of AT commands handled since
529      * boot. This is a good indicator for spammy headset/handsfree units that
530      * can keep the device awake by polling for cellular status updates. As a
531      * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
532      *
533      * @param device the bluetooth headset.
534      * @return monotonically increasing battery usage hint, or a negative error
535      *         code on error
536      * @hide
537      */
getBatteryUsageHint(BluetoothDevice device)538     public int getBatteryUsageHint(BluetoothDevice device) {
539         if (DBG) log("getBatteryUsageHint()");
540         if (mService != null && isEnabled() &&
541             isValidDevice(device)) {
542             try {
543                 return mService.getBatteryUsageHint(device);
544             } catch (RemoteException e) {
545                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
546             }
547         }
548         if (mService == null) Log.w(TAG, "Proxy not attached to service");
549         return -1;
550     }
551 
552     /**
553      * Indicates if current platform supports voice dialing over bluetooth SCO.
554      *
555      * @return true if voice dialing over bluetooth is supported, false otherwise.
556      * @hide
557      */
isBluetoothVoiceDialingEnabled(Context context)558     public static boolean isBluetoothVoiceDialingEnabled(Context context) {
559         return context.getResources().getBoolean(
560                 com.android.internal.R.bool.config_bluetooth_sco_off_call);
561     }
562 
563     /**
564      * Cancel the outgoing connection.
565      * Note: This is an internal function and shouldn't be exposed
566      *
567      * @hide
568      */
cancelConnectThread()569     public boolean cancelConnectThread() {
570         if (DBG) log("cancelConnectThread");
571         if (mService != null && isEnabled()) {
572             try {
573                 return mService.cancelConnectThread();
574             } catch (RemoteException e) {Log.e(TAG, e.toString());}
575         } else {
576             Log.w(TAG, "Proxy not attached to service");
577             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
578         }
579         return false;
580     }
581 
582     /**
583      * Accept the incoming connection.
584      * Note: This is an internal function and shouldn't be exposed
585      *
586      * @hide
587      */
acceptIncomingConnect(BluetoothDevice device)588     public boolean acceptIncomingConnect(BluetoothDevice device) {
589         if (DBG) log("acceptIncomingConnect");
590         if (mService != null && isEnabled()) {
591             try {
592                 return mService.acceptIncomingConnect(device);
593             } catch (RemoteException e) {Log.e(TAG, e.toString());}
594         } else {
595             Log.w(TAG, "Proxy not attached to service");
596             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
597         }
598         return false;
599     }
600 
601     /**
602      * Create the connect thread for the incoming connection.
603      * Note: This is an internal function and shouldn't be exposed
604      *
605      * @hide
606      */
createIncomingConnect(BluetoothDevice device)607     public boolean createIncomingConnect(BluetoothDevice device) {
608         if (DBG) log("createIncomingConnect");
609         if (mService != null && isEnabled()) {
610             try {
611                 return mService.createIncomingConnect(device);
612             } catch (RemoteException e) {Log.e(TAG, e.toString());}
613         } else {
614             Log.w(TAG, "Proxy not attached to service");
615             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
616         }
617         return false;
618     }
619 
620     /**
621      * Reject the incoming connection.
622      * @hide
623      */
rejectIncomingConnect(BluetoothDevice device)624     public boolean rejectIncomingConnect(BluetoothDevice device) {
625         if (DBG) log("rejectIncomingConnect");
626         if (mService != null) {
627             try {
628                 return mService.rejectIncomingConnect(device);
629             } catch (RemoteException e) {Log.e(TAG, e.toString());}
630         } else {
631             Log.w(TAG, "Proxy not attached to service");
632             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
633         }
634         return false;
635     }
636 
637     /**
638      * Connect to a Bluetooth Headset.
639      * Note: This is an internal function and shouldn't be exposed
640      *
641      * @hide
642      */
connectHeadsetInternal(BluetoothDevice device)643     public boolean connectHeadsetInternal(BluetoothDevice device) {
644         if (DBG) log("connectHeadsetInternal");
645         if (mService != null && isEnabled()) {
646             try {
647                 return mService.connectHeadsetInternal(device);
648             } catch (RemoteException e) {Log.e(TAG, e.toString());}
649         } else {
650             Log.w(TAG, "Proxy not attached to service");
651             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
652         }
653         return false;
654     }
655 
656     /**
657      * Disconnect a Bluetooth Headset.
658      * Note: This is an internal function and shouldn't be exposed
659      *
660      * @hide
661      */
disconnectHeadsetInternal(BluetoothDevice device)662     public boolean disconnectHeadsetInternal(BluetoothDevice device) {
663         if (DBG) log("disconnectHeadsetInternal");
664         if (mService != null && !isDisabled()) {
665             try {
666                  return mService.disconnectHeadsetInternal(device);
667             } catch (RemoteException e) {Log.e(TAG, e.toString());}
668         } else {
669             Log.w(TAG, "Proxy not attached to service");
670             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
671         }
672         return false;
673     }
674 
675     /**
676      * Set the audio state of the Headset.
677      * Note: This is an internal function and shouldn't be exposed
678      *
679      * @hide
680      */
setAudioState(BluetoothDevice device, int state)681     public boolean setAudioState(BluetoothDevice device, int state) {
682         if (DBG) log("setAudioState");
683         if (mService != null && !isDisabled()) {
684             try {
685                 return mService.setAudioState(device, state);
686             } catch (RemoteException e) {Log.e(TAG, e.toString());}
687         } else {
688             Log.w(TAG, "Proxy not attached to service");
689             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
690         }
691         return false;
692     }
693 
694     /**
695      * Get the current audio state of the Headset.
696      * Note: This is an internal function and shouldn't be exposed
697      *
698      * @hide
699      */
getAudioState(BluetoothDevice device)700     public int getAudioState(BluetoothDevice device) {
701         if (DBG) log("getAudioState");
702         if (mService != null && !isDisabled()) {
703             try {
704                 return mService.getAudioState(device);
705             } catch (RemoteException e) {Log.e(TAG, e.toString());}
706         } else {
707             Log.w(TAG, "Proxy not attached to service");
708             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
709         }
710         return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
711     }
712 
713     /**
714      * Initiates a SCO channel connection with the headset (if connected).
715      * Also initiates a virtual voice call for Handsfree devices as many devices
716      * do not accept SCO audio without a call.
717      * This API allows the handsfree device to be used for routing non-cellular
718      * call audio.
719      *
720      * @param device Remote Bluetooth Device
721      * @return true if successful, false if there was some error.
722      * @hide
723      */
startScoUsingVirtualVoiceCall(BluetoothDevice device)724     public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
725         if (DBG) log("startScoUsingVirtualVoiceCall()");
726         if (mService != null && isEnabled() && isValidDevice(device)) {
727             try {
728                 return mService.startScoUsingVirtualVoiceCall(device);
729             } catch (RemoteException e) {
730                 Log.e(TAG, e.toString());
731             }
732         } else {
733             Log.w(TAG, "Proxy not attached to service");
734             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
735         }
736         return false;
737     }
738 
739     /**
740      * Terminates an ongoing SCO connection and the associated virtual
741      * call.
742      *
743      * @param device Remote Bluetooth Device
744      * @return true if successful, false if there was some error.
745      * @hide
746      */
stopScoUsingVirtualVoiceCall(BluetoothDevice device)747     public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
748         if (DBG) log("stopScoUsingVirtualVoiceCall()");
749         if (mService != null && isEnabled() && isValidDevice(device)) {
750             try {
751                 return mService.stopScoUsingVirtualVoiceCall(device);
752             } catch (RemoteException e) {
753                 Log.e(TAG, e.toString());
754             }
755         } else {
756             Log.w(TAG, "Proxy not attached to service");
757             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
758         }
759         return false;
760     }
761 
762     private ServiceConnection mConnection = new ServiceConnection() {
763         public void onServiceConnected(ComponentName className, IBinder service) {
764             if (DBG) Log.d(TAG, "Proxy object connected");
765             mService = IBluetoothHeadset.Stub.asInterface(service);
766 
767             if (mServiceListener != null) {
768                 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this);
769             }
770         }
771         public void onServiceDisconnected(ComponentName className) {
772             if (DBG) Log.d(TAG, "Proxy object disconnected");
773             mService = null;
774             if (mServiceListener != null) {
775                 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
776             }
777         }
778     };
779 
isEnabled()780     private boolean isEnabled() {
781        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
782        return false;
783     }
784 
isDisabled()785     private boolean isDisabled() {
786        if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) return true;
787        return false;
788     }
789 
isValidDevice(BluetoothDevice device)790     private boolean isValidDevice(BluetoothDevice device) {
791        if (device == null) return false;
792 
793        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
794        return false;
795     }
796 
log(String msg)797     private static void log(String msg) {
798         Log.d(TAG, msg);
799     }
800 }
801