• 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         mServiceListener = null;
249     }
250 
251     /**
252      * Initiate connection to a profile of the remote bluetooth device.
253      *
254      * <p> Currently, the system supports only 1 connection to the
255      * headset/handsfree profile. The API will automatically disconnect connected
256      * devices before connecting.
257      *
258      * <p> This API returns false in scenarios like the profile on the
259      * device is already connected or Bluetooth is not turned on.
260      * When this API returns true, it is guaranteed that
261      * connection state intent for the profile will be broadcasted with
262      * the state. Users can get the connection state of the profile
263      * from this intent.
264      *
265      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
266      * permission.
267      *
268      * @param device Remote Bluetooth Device
269      * @return false on immediate error,
270      *               true otherwise
271      * @hide
272      */
connect(BluetoothDevice device)273     public boolean connect(BluetoothDevice device) {
274         if (DBG) log("connect(" + device + ")");
275         if (mService != null && isEnabled() &&
276             isValidDevice(device)) {
277             try {
278                 return mService.connect(device);
279             } catch (RemoteException e) {
280                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
281                 return false;
282             }
283         }
284         if (mService == null) Log.w(TAG, "Proxy not attached to service");
285         return false;
286     }
287 
288     /**
289      * Initiate disconnection from a profile
290      *
291      * <p> This API will return false in scenarios like the profile on the
292      * Bluetooth device is not in connected state etc. When this API returns,
293      * true, it is guaranteed that the connection state change
294      * intent will be broadcasted with the state. Users can get the
295      * disconnection state of the profile from this intent.
296      *
297      * <p> If the disconnection is initiated by a remote device, the state
298      * will transition from {@link #STATE_CONNECTED} to
299      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
300      * host (local) device the state will transition from
301      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
302      * state {@link #STATE_DISCONNECTED}. The transition to
303      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
304      * two scenarios.
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      */
disconnect(BluetoothDevice device)314     public boolean disconnect(BluetoothDevice device) {
315         if (DBG) log("disconnect(" + device + ")");
316         if (mService != null && isEnabled() &&
317             isValidDevice(device)) {
318             try {
319                 return mService.disconnect(device);
320             } catch (RemoteException e) {
321               Log.e(TAG, Log.getStackTraceString(new Throwable()));
322               return false;
323             }
324         }
325         if (mService == null) Log.w(TAG, "Proxy not attached to service");
326         return false;
327     }
328 
329     /**
330      * {@inheritDoc}
331      */
getConnectedDevices()332     public List<BluetoothDevice> getConnectedDevices() {
333         if (DBG) log("getConnectedDevices()");
334         if (mService != null && isEnabled()) {
335             try {
336                 return mService.getConnectedDevices();
337             } catch (RemoteException e) {
338                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
339                 return new ArrayList<BluetoothDevice>();
340             }
341         }
342         if (mService == null) Log.w(TAG, "Proxy not attached to service");
343         return new ArrayList<BluetoothDevice>();
344     }
345 
346     /**
347      * {@inheritDoc}
348      */
getDevicesMatchingConnectionStates(int[] states)349     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
350         if (DBG) log("getDevicesMatchingStates()");
351         if (mService != null && isEnabled()) {
352             try {
353                 return mService.getDevicesMatchingConnectionStates(states);
354             } catch (RemoteException e) {
355                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
356                 return new ArrayList<BluetoothDevice>();
357             }
358         }
359         if (mService == null) Log.w(TAG, "Proxy not attached to service");
360         return new ArrayList<BluetoothDevice>();
361     }
362 
363     /**
364      * {@inheritDoc}
365      */
getConnectionState(BluetoothDevice device)366     public int getConnectionState(BluetoothDevice device) {
367         if (DBG) log("getConnectionState(" + device + ")");
368         if (mService != null && isEnabled() &&
369             isValidDevice(device)) {
370             try {
371                 return mService.getConnectionState(device);
372             } catch (RemoteException e) {
373                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
374                 return BluetoothProfile.STATE_DISCONNECTED;
375             }
376         }
377         if (mService == null) Log.w(TAG, "Proxy not attached to service");
378         return BluetoothProfile.STATE_DISCONNECTED;
379     }
380 
381     /**
382      * Set priority of the profile
383      *
384      * <p> The device should already be paired.
385      *  Priority can be one of {@link #PRIORITY_ON} or
386      * {@link #PRIORITY_OFF},
387      *
388      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
389      * permission.
390      *
391      * @param device Paired bluetooth device
392      * @param priority
393      * @return true if priority is set, false on error
394      * @hide
395      */
setPriority(BluetoothDevice device, int priority)396     public boolean setPriority(BluetoothDevice device, int priority) {
397         if (DBG) log("setPriority(" + device + ", " + priority + ")");
398         if (mService != null && isEnabled() &&
399             isValidDevice(device)) {
400             if (priority != BluetoothProfile.PRIORITY_OFF &&
401                 priority != BluetoothProfile.PRIORITY_ON) {
402               return false;
403             }
404             try {
405                 return mService.setPriority(device, priority);
406             } catch (RemoteException e) {
407                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
408                 return false;
409             }
410         }
411         if (mService == null) Log.w(TAG, "Proxy not attached to service");
412         return false;
413     }
414 
415     /**
416      * Get the priority of the profile.
417      *
418      * <p> The priority can be any of:
419      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
420      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
421      *
422      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
423      *
424      * @param device Bluetooth device
425      * @return priority of the device
426      * @hide
427      */
getPriority(BluetoothDevice device)428     public int getPriority(BluetoothDevice device) {
429         if (DBG) log("getPriority(" + device + ")");
430         if (mService != null && isEnabled() &&
431             isValidDevice(device)) {
432             try {
433                 return mService.getPriority(device);
434             } catch (RemoteException e) {
435                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
436                 return PRIORITY_OFF;
437             }
438         }
439         if (mService == null) Log.w(TAG, "Proxy not attached to service");
440         return PRIORITY_OFF;
441     }
442 
443     /**
444      * Start Bluetooth voice recognition. This methods sends the voice
445      * recognition AT command to the headset and establishes the
446      * audio connection.
447      *
448      * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
449      * If this function returns true, this intent will be broadcasted with
450      * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
451      *
452      * <p> {@link #EXTRA_STATE} will transition from
453      * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
454      * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
455      * in case of failure to establish the audio connection.
456      *
457      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
458      *
459      * @param device Bluetooth headset
460      * @return false if there is no headset connected of if the
461      *               connected headset doesn't support voice recognition
462      *               or on error, true otherwise
463      */
startVoiceRecognition(BluetoothDevice device)464     public boolean startVoiceRecognition(BluetoothDevice device) {
465         if (DBG) log("startVoiceRecognition()");
466         if (mService != null && isEnabled() &&
467             isValidDevice(device)) {
468             try {
469                 return mService.startVoiceRecognition(device);
470             } catch (RemoteException e) {
471                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
472             }
473         }
474         if (mService == null) Log.w(TAG, "Proxy not attached to service");
475         return false;
476     }
477 
478     /**
479      * Stop Bluetooth Voice Recognition mode, and shut down the
480      * Bluetooth audio path.
481      *
482      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
483      *
484      * @param device Bluetooth headset
485      * @return false if there is no headset connected
486      *               or on error, true otherwise
487      */
stopVoiceRecognition(BluetoothDevice device)488     public boolean stopVoiceRecognition(BluetoothDevice device) {
489         if (DBG) log("stopVoiceRecognition()");
490         if (mService != null && isEnabled() &&
491             isValidDevice(device)) {
492             try {
493                 return mService.stopVoiceRecognition(device);
494             } catch (RemoteException e) {
495                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
496             }
497         }
498         if (mService == null) Log.w(TAG, "Proxy not attached to service");
499         return false;
500     }
501 
502     /**
503      * Check if Bluetooth SCO audio is connected.
504      *
505      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
506      *
507      * @param device Bluetooth headset
508      * @return true if SCO is connected,
509      *         false otherwise or on error
510      */
isAudioConnected(BluetoothDevice device)511     public boolean isAudioConnected(BluetoothDevice device) {
512         if (DBG) log("isAudioConnected()");
513         if (mService != null && isEnabled() &&
514             isValidDevice(device)) {
515             try {
516               return mService.isAudioConnected(device);
517             } catch (RemoteException e) {
518               Log.e(TAG,  Log.getStackTraceString(new Throwable()));
519             }
520         }
521         if (mService == null) Log.w(TAG, "Proxy not attached to service");
522         return false;
523     }
524 
525     /**
526      * Get battery usage hint for Bluetooth Headset service.
527      * This is a monotonically increasing integer. Wraps to 0 at
528      * Integer.MAX_INT, and at boot.
529      * Current implementation returns the number of AT commands handled since
530      * boot. This is a good indicator for spammy headset/handsfree units that
531      * can keep the device awake by polling for cellular status updates. As a
532      * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
533      *
534      * @param device the bluetooth headset.
535      * @return monotonically increasing battery usage hint, or a negative error
536      *         code on error
537      * @hide
538      */
getBatteryUsageHint(BluetoothDevice device)539     public int getBatteryUsageHint(BluetoothDevice device) {
540         if (DBG) log("getBatteryUsageHint()");
541         if (mService != null && isEnabled() &&
542             isValidDevice(device)) {
543             try {
544                 return mService.getBatteryUsageHint(device);
545             } catch (RemoteException e) {
546                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
547             }
548         }
549         if (mService == null) Log.w(TAG, "Proxy not attached to service");
550         return -1;
551     }
552 
553     /**
554      * Indicates if current platform supports voice dialing over bluetooth SCO.
555      *
556      * @return true if voice dialing over bluetooth is supported, false otherwise.
557      * @hide
558      */
isBluetoothVoiceDialingEnabled(Context context)559     public static boolean isBluetoothVoiceDialingEnabled(Context context) {
560         return context.getResources().getBoolean(
561                 com.android.internal.R.bool.config_bluetooth_sco_off_call);
562     }
563 
564     /**
565      * Cancel the outgoing connection.
566      * Note: This is an internal function and shouldn't be exposed
567      *
568      * @hide
569      */
cancelConnectThread()570     public boolean cancelConnectThread() {
571         if (DBG) log("cancelConnectThread");
572         if (mService != null && isEnabled()) {
573             try {
574                 return mService.cancelConnectThread();
575             } catch (RemoteException e) {Log.e(TAG, e.toString());}
576         } else {
577             Log.w(TAG, "Proxy not attached to service");
578             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
579         }
580         return false;
581     }
582 
583     /**
584      * Accept the incoming connection.
585      * Note: This is an internal function and shouldn't be exposed
586      *
587      * @hide
588      */
acceptIncomingConnect(BluetoothDevice device)589     public boolean acceptIncomingConnect(BluetoothDevice device) {
590         if (DBG) log("acceptIncomingConnect");
591         if (mService != null && isEnabled()) {
592             try {
593                 return mService.acceptIncomingConnect(device);
594             } catch (RemoteException e) {Log.e(TAG, e.toString());}
595         } else {
596             Log.w(TAG, "Proxy not attached to service");
597             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
598         }
599         return false;
600     }
601 
602     /**
603      * Create the connect thread for the incoming connection.
604      * Note: This is an internal function and shouldn't be exposed
605      *
606      * @hide
607      */
createIncomingConnect(BluetoothDevice device)608     public boolean createIncomingConnect(BluetoothDevice device) {
609         if (DBG) log("createIncomingConnect");
610         if (mService != null && isEnabled()) {
611             try {
612                 return mService.createIncomingConnect(device);
613             } catch (RemoteException e) {Log.e(TAG, e.toString());}
614         } else {
615             Log.w(TAG, "Proxy not attached to service");
616             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
617         }
618         return false;
619     }
620 
621     /**
622      * Reject the incoming connection.
623      * @hide
624      */
rejectIncomingConnect(BluetoothDevice device)625     public boolean rejectIncomingConnect(BluetoothDevice device) {
626         if (DBG) log("rejectIncomingConnect");
627         if (mService != null) {
628             try {
629                 return mService.rejectIncomingConnect(device);
630             } catch (RemoteException e) {Log.e(TAG, e.toString());}
631         } else {
632             Log.w(TAG, "Proxy not attached to service");
633             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
634         }
635         return false;
636     }
637 
638     /**
639      * Connect to a Bluetooth Headset.
640      * Note: This is an internal function and shouldn't be exposed
641      *
642      * @hide
643      */
connectHeadsetInternal(BluetoothDevice device)644     public boolean connectHeadsetInternal(BluetoothDevice device) {
645         if (DBG) log("connectHeadsetInternal");
646         if (mService != null && isEnabled()) {
647             try {
648                 return mService.connectHeadsetInternal(device);
649             } catch (RemoteException e) {Log.e(TAG, e.toString());}
650         } else {
651             Log.w(TAG, "Proxy not attached to service");
652             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
653         }
654         return false;
655     }
656 
657     /**
658      * Disconnect a Bluetooth Headset.
659      * Note: This is an internal function and shouldn't be exposed
660      *
661      * @hide
662      */
disconnectHeadsetInternal(BluetoothDevice device)663     public boolean disconnectHeadsetInternal(BluetoothDevice device) {
664         if (DBG) log("disconnectHeadsetInternal");
665         if (mService != null && !isDisabled()) {
666             try {
667                  return mService.disconnectHeadsetInternal(device);
668             } catch (RemoteException e) {Log.e(TAG, e.toString());}
669         } else {
670             Log.w(TAG, "Proxy not attached to service");
671             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
672         }
673         return false;
674     }
675 
676     /**
677      * Set the audio state of the Headset.
678      * Note: This is an internal function and shouldn't be exposed
679      *
680      * @hide
681      */
setAudioState(BluetoothDevice device, int state)682     public boolean setAudioState(BluetoothDevice device, int state) {
683         if (DBG) log("setAudioState");
684         if (mService != null && !isDisabled()) {
685             try {
686                 return mService.setAudioState(device, state);
687             } catch (RemoteException e) {Log.e(TAG, e.toString());}
688         } else {
689             Log.w(TAG, "Proxy not attached to service");
690             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
691         }
692         return false;
693     }
694 
695     /**
696      * Get the current audio state of the Headset.
697      * Note: This is an internal function and shouldn't be exposed
698      *
699      * @hide
700      */
getAudioState(BluetoothDevice device)701     public int getAudioState(BluetoothDevice device) {
702         if (DBG) log("getAudioState");
703         if (mService != null && !isDisabled()) {
704             try {
705                 return mService.getAudioState(device);
706             } catch (RemoteException e) {Log.e(TAG, e.toString());}
707         } else {
708             Log.w(TAG, "Proxy not attached to service");
709             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
710         }
711         return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
712     }
713 
714     /**
715      * Initiates a SCO channel connection with the headset (if connected).
716      * Also initiates a virtual voice call for Handsfree devices as many devices
717      * do not accept SCO audio without a call.
718      * This API allows the handsfree device to be used for routing non-cellular
719      * call audio.
720      *
721      * @param device Remote Bluetooth Device
722      * @return true if successful, false if there was some error.
723      * @hide
724      */
startScoUsingVirtualVoiceCall(BluetoothDevice device)725     public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
726         if (DBG) log("startScoUsingVirtualVoiceCall()");
727         if (mService != null && isEnabled() && isValidDevice(device)) {
728             try {
729                 return mService.startScoUsingVirtualVoiceCall(device);
730             } catch (RemoteException e) {
731                 Log.e(TAG, e.toString());
732             }
733         } else {
734             Log.w(TAG, "Proxy not attached to service");
735             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
736         }
737         return false;
738     }
739 
740     /**
741      * Terminates an ongoing SCO connection and the associated virtual
742      * call.
743      *
744      * @param device Remote Bluetooth Device
745      * @return true if successful, false if there was some error.
746      * @hide
747      */
stopScoUsingVirtualVoiceCall(BluetoothDevice device)748     public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
749         if (DBG) log("stopScoUsingVirtualVoiceCall()");
750         if (mService != null && isEnabled() && isValidDevice(device)) {
751             try {
752                 return mService.stopScoUsingVirtualVoiceCall(device);
753             } catch (RemoteException e) {
754                 Log.e(TAG, e.toString());
755             }
756         } else {
757             Log.w(TAG, "Proxy not attached to service");
758             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
759         }
760         return false;
761     }
762 
763     private ServiceConnection mConnection = new ServiceConnection() {
764         public void onServiceConnected(ComponentName className, IBinder service) {
765             if (DBG) Log.d(TAG, "Proxy object connected");
766             mService = IBluetoothHeadset.Stub.asInterface(service);
767 
768             if (mServiceListener != null) {
769                 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this);
770             }
771         }
772         public void onServiceDisconnected(ComponentName className) {
773             if (DBG) Log.d(TAG, "Proxy object disconnected");
774             mService = null;
775             if (mServiceListener != null) {
776                 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
777             }
778         }
779     };
780 
isEnabled()781     private boolean isEnabled() {
782        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
783        return false;
784     }
785 
isDisabled()786     private boolean isDisabled() {
787        if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) return true;
788        return false;
789     }
790 
isValidDevice(BluetoothDevice device)791     private boolean isValidDevice(BluetoothDevice device) {
792        if (device == null) return false;
793 
794        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
795        return false;
796     }
797 
log(String msg)798     private static void log(String msg) {
799         Log.d(TAG, msg);
800     }
801 }
802