• 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.RemoteException;
26 import android.os.IBinder;
27 import android.util.Log;
28 
29 /**
30  * The Android Bluetooth API is not finalized, and *will* change. Use at your
31  * own risk.
32  *
33  * Public API for controlling the Bluetooth Headset Service. This includes both
34  * Bluetooth Headset and Handsfree (v1.5) profiles. The Headset service will
35  * attempt a handsfree connection first, and fall back to headset.
36  *
37  * BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
38  * Service via IPC.
39  *
40  * Creating a BluetoothHeadset object will create a binding with the
41  * BluetoothHeadset service. Users of this object should call close() when they
42  * are finished with the BluetoothHeadset, so that this proxy object can unbind
43  * from the service.
44  *
45  * This BluetoothHeadset object is not immediately bound to the
46  * BluetoothHeadset service. Use the ServiceListener interface to obtain a
47  * notification when it is bound, this is especially important if you wish to
48  * immediately call methods on BluetoothHeadset after construction.
49  *
50  * Android only supports one connected Bluetooth Headset at a time.
51  *
52  * @hide
53  */
54 public final class BluetoothHeadset {
55 
56     private static final String TAG = "BluetoothHeadset";
57     private static final boolean DBG = false;
58 
59     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
60     public static final String ACTION_STATE_CHANGED =
61             "android.bluetooth.headset.action.STATE_CHANGED";
62     /**
63      * TODO(API release): Consider incorporating as new state in
64      * HEADSET_STATE_CHANGED
65      */
66     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
67     public static final String ACTION_AUDIO_STATE_CHANGED =
68             "android.bluetooth.headset.action.AUDIO_STATE_CHANGED";
69     public static final String EXTRA_STATE =
70             "android.bluetooth.headset.extra.STATE";
71     public static final String EXTRA_PREVIOUS_STATE =
72             "android.bluetooth.headset.extra.PREVIOUS_STATE";
73     public static final String EXTRA_AUDIO_STATE =
74             "android.bluetooth.headset.extra.AUDIO_STATE";
75 
76     /** Extra to be used with the Headset State change intent.
77      * This will be used only when Headset state changes to
78      * {@link #STATE_DISCONNECTED} from any previous state.
79      * This extra field is optional and will be used when
80      * we have deterministic information regarding whether
81      * the disconnect was initiated by the remote device or
82      * by the local adapter.
83      */
84     public static final String EXTRA_DISCONNECT_INITIATOR =
85             "android.bluetooth.headset.extra.DISCONNECT_INITIATOR";
86 
87     /**
88      * TODO(API release): Consider incorporating as new state in
89      * HEADSET_STATE_CHANGED
90      */
91     private IBluetoothHeadset mService;
92     private final Context mContext;
93     private final ServiceListener mServiceListener;
94 
95     /** There was an error trying to obtain the state */
96     public static final int STATE_ERROR        = -1;
97     /** No headset currently connected */
98     public static final int STATE_DISCONNECTED = 0;
99     /** Connection attempt in progress */
100     public static final int STATE_CONNECTING   = 1;
101     /** A headset is currently connected */
102     public static final int STATE_CONNECTED    = 2;
103 
104     /** A SCO audio channel is not established */
105     public static final int AUDIO_STATE_DISCONNECTED = 0;
106     /** A SCO audio channel is established */
107     public static final int AUDIO_STATE_CONNECTED = 1;
108 
109     public static final int RESULT_FAILURE = 0;
110     public static final int RESULT_SUCCESS = 1;
111     /** Connection canceled before completion. */
112     public static final int RESULT_CANCELED = 2;
113 
114     /** Values for {@link #EXTRA_DISCONNECT_INITIATOR} */
115     public static final int REMOTE_DISCONNECT = 0;
116     public static final int LOCAL_DISCONNECT = 1;
117 
118 
119     /** Default priority for headsets for which we will accept
120      * incoming connections and auto-connect. */
121     public static final int PRIORITY_AUTO_CONNECT = 1000;
122     /** Default priority for headsets for which we will accept
123      * incoming connections but not auto-connect. */
124     public static final int PRIORITY_ON = 100;
125     /** Default priority for headsets that should not be auto-connected
126      * and not allow incoming connections. */
127     public static final int PRIORITY_OFF = 0;
128     /** Default priority when not set or when the device is unpaired */
129     public static final int PRIORITY_UNDEFINED = -1;
130 
131     /**
132      * An interface for notifying BluetoothHeadset IPC clients when they have
133      * been connected to the BluetoothHeadset service.
134      */
135     public interface ServiceListener {
136         /**
137          * Called to notify the client when this proxy object has been
138          * connected to the BluetoothHeadset service. Clients must wait for
139          * this callback before making IPC calls on the BluetoothHeadset
140          * service.
141          */
onServiceConnected()142         public void onServiceConnected();
143 
144         /**
145          * Called to notify the client that this proxy object has been
146          * disconnected from the BluetoothHeadset service. Clients must not
147          * make IPC calls on the BluetoothHeadset service after this callback.
148          * This callback will currently only occur if the application hosting
149          * the BluetoothHeadset service, but may be called more often in future.
150          */
onServiceDisconnected()151         public void onServiceDisconnected();
152     }
153 
154     /**
155      * Create a BluetoothHeadset proxy object.
156      */
BluetoothHeadset(Context context, ServiceListener l)157     public BluetoothHeadset(Context context, ServiceListener l) {
158         mContext = context;
159         mServiceListener = l;
160         if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) {
161             Log.e(TAG, "Could not bind to Bluetooth Headset Service");
162         }
163     }
164 
finalize()165     protected void finalize() throws Throwable {
166         try {
167             close();
168         } finally {
169             super.finalize();
170         }
171     }
172 
173     /**
174      * Close the connection to the backing service.
175      * Other public functions of BluetoothHeadset will return default error
176      * results once close() has been called. Multiple invocations of close()
177      * are ok.
178      */
close()179     public synchronized void close() {
180         if (DBG) log("close()");
181         if (mConnection != null) {
182             mContext.unbindService(mConnection);
183             mConnection = null;
184         }
185     }
186 
187     /**
188      * Get the current state of the Bluetooth Headset service.
189      * @return One of the STATE_ return codes, or STATE_ERROR if this proxy
190      *         object is currently not connected to the Headset service.
191      */
getState(BluetoothDevice device)192     public int getState(BluetoothDevice device) {
193         if (DBG) log("getState()");
194         if (mService != null) {
195             try {
196                 return mService.getState(device);
197             } catch (RemoteException e) {Log.e(TAG, e.toString());}
198         } else {
199             Log.w(TAG, "Proxy not attached to service");
200             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
201         }
202         return BluetoothHeadset.STATE_ERROR;
203     }
204 
205     /**
206      * Get the BluetoothDevice for the current headset.
207      * @return current headset, or null if not in connected or connecting
208      *         state, or if this proxy object is not connected to the Headset
209      *         service.
210      */
getCurrentHeadset()211     public BluetoothDevice getCurrentHeadset() {
212         if (DBG) log("getCurrentHeadset()");
213         if (mService != null) {
214             try {
215                 return mService.getCurrentHeadset();
216             } catch (RemoteException e) {Log.e(TAG, e.toString());}
217         } else {
218             Log.w(TAG, "Proxy not attached to service");
219             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
220         }
221         return null;
222     }
223 
224     /**
225      * Request to initiate a connection to a headset.
226      * This call does not block. Fails if a headset is already connecting
227      * or connected.
228      * Initiates auto-connection if device is null. Tries to connect to all
229      * devices with priority greater than PRIORITY_AUTO in descending order.
230      * @param device device to connect to, or null to auto-connect last connected
231      *               headset
232      * @return       false if there was a problem initiating the connection
233      *               procedure, and no further HEADSET_STATE_CHANGED intents
234      *               will be expected.
235      */
connectHeadset(BluetoothDevice device)236     public boolean connectHeadset(BluetoothDevice device) {
237         if (DBG) log("connectHeadset(" + device + ")");
238         if (mService != null) {
239             try {
240                 if (mService.connectHeadset(device)) {
241                     return true;
242                 }
243             } catch (RemoteException e) {Log.e(TAG, e.toString());}
244         } else {
245             Log.w(TAG, "Proxy not attached to service");
246             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
247         }
248         return false;
249     }
250 
251     /**
252      * Returns true if the specified headset is connected (does not include
253      * connecting). Returns false if not connected, or if this proxy object
254      * if not currently connected to the headset service.
255      */
isConnected(BluetoothDevice device)256     public boolean isConnected(BluetoothDevice device) {
257         if (DBG) log("isConnected(" + device + ")");
258         if (mService != null) {
259             try {
260                 return mService.isConnected(device);
261             } catch (RemoteException e) {Log.e(TAG, e.toString());}
262         } else {
263             Log.w(TAG, "Proxy not attached to service");
264             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
265         }
266         return false;
267     }
268 
269     /**
270      * Disconnects the current headset. Currently this call blocks, it may soon
271      * be made asynchronous. Returns false if this proxy object is
272      * not currently connected to the Headset service.
273      */
disconnectHeadset(BluetoothDevice device)274     public boolean disconnectHeadset(BluetoothDevice device) {
275         if (DBG) log("disconnectHeadset()");
276         if (mService != null) {
277             try {
278                 mService.disconnectHeadset(device);
279                 return true;
280             } catch (RemoteException e) {Log.e(TAG, e.toString());}
281         } else {
282             Log.w(TAG, "Proxy not attached to service");
283             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
284         }
285         return false;
286     }
287 
288     /**
289      * Start BT Voice Recognition mode, and set up Bluetooth audio path.
290      * Returns false if there is no headset connected, or if the
291      * connected headset does not support voice recognition, or on
292      * error.
293      */
startVoiceRecognition()294     public boolean startVoiceRecognition() {
295         if (DBG) log("startVoiceRecognition()");
296         if (mService != null) {
297             try {
298                 return mService.startVoiceRecognition();
299             } catch (RemoteException e) {Log.e(TAG, e.toString());}
300         } else {
301             Log.w(TAG, "Proxy not attached to service");
302             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
303         }
304         return false;
305     }
306 
307     /**
308      * Stop BT Voice Recognition mode, and shut down Bluetooth audio path.
309      * Returns false if there is no headset connected, or the connected
310      * headset is not in voice recognition mode, or on error.
311      */
stopVoiceRecognition()312     public boolean stopVoiceRecognition() {
313         if (DBG) log("stopVoiceRecognition()");
314         if (mService != null) {
315             try {
316                 return mService.stopVoiceRecognition();
317             } catch (RemoteException e) {Log.e(TAG, e.toString());}
318         } else {
319             Log.w(TAG, "Proxy not attached to service");
320             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
321         }
322         return false;
323     }
324 
325     /**
326      * Set priority of headset.
327      * Priority is a non-negative integer. By default paired headsets will have
328      * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0).
329      * Headsets with priority greater than zero will be auto-connected, and
330      * incoming connections will be accepted (if no other headset is
331      * connected).
332      * Auto-connection occurs at the following events: boot, incoming phone
333      * call, outgoing phone call.
334      * Headsets with priority equal to zero, or that are unpaired, are not
335      * auto-connected.
336      * Incoming connections are ignored regardless of priority if there is
337      * already a headset connected.
338      * @param device paired headset
339      * @param priority Integer priority, for example PRIORITY_AUTO or
340      *                 PRIORITY_NONE
341      * @return true if successful, false if there was some error
342      */
setPriority(BluetoothDevice device, int priority)343     public boolean setPriority(BluetoothDevice device, int priority) {
344         if (DBG) log("setPriority(" + device + ", " + priority + ")");
345         if (mService != null) {
346             try {
347                 return mService.setPriority(device, priority);
348             } catch (RemoteException e) {Log.e(TAG, e.toString());}
349         } else {
350             Log.w(TAG, "Proxy not attached to service");
351             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
352         }
353         return false;
354     }
355 
356     /**
357      * Get priority of headset.
358      * @param device headset
359      * @return non-negative priority, or negative error code on error
360      */
getPriority(BluetoothDevice device)361     public int getPriority(BluetoothDevice device) {
362         if (DBG) log("getPriority(" + device + ")");
363         if (mService != null) {
364             try {
365                 return mService.getPriority(device);
366             } catch (RemoteException e) {Log.e(TAG, e.toString());}
367         } else {
368             Log.w(TAG, "Proxy not attached to service");
369             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
370         }
371         return -1;
372     }
373 
374     /**
375      * Get battery usage hint for Bluetooth Headset service.
376      * This is a monotonically increasing integer. Wraps to 0 at
377      * Integer.MAX_INT, and at boot.
378      * Current implementation returns the number of AT commands handled since
379      * boot. This is a good indicator for spammy headset/handsfree units that
380      * can keep the device awake by polling for cellular status updates. As a
381      * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
382      * @return monotonically increasing battery usage hint, or a negative error
383      *         code on error
384      * @hide
385      */
getBatteryUsageHint()386     public int getBatteryUsageHint() {
387         if (DBG) log("getBatteryUsageHint()");
388         if (mService != null) {
389             try {
390                 return mService.getBatteryUsageHint();
391             } catch (RemoteException e) {Log.e(TAG, e.toString());}
392         } else {
393             Log.w(TAG, "Proxy not attached to service");
394             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
395         }
396         return -1;
397     }
398     /**
399      * Indicates if current platform supports voice dialing over bluetooth SCO.
400      * @return true if voice dialing over bluetooth is supported, false otherwise.
401      * @hide
402      */
isBluetoothVoiceDialingEnabled(Context context)403     public static boolean isBluetoothVoiceDialingEnabled(Context context) {
404         return context.getResources().getBoolean(
405                 com.android.internal.R.bool.config_bluetooth_sco_off_call);
406     }
407 
408     /**
409      * Cancel the outgoing connection.
410      * @hide
411      */
cancelConnectThread()412     public boolean cancelConnectThread() {
413         if (DBG) log("cancelConnectThread");
414         if (mService != null) {
415             try {
416                 return mService.cancelConnectThread();
417             } catch (RemoteException e) {Log.e(TAG, e.toString());}
418         } else {
419             Log.w(TAG, "Proxy not attached to service");
420             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
421         }
422         return false;
423     }
424 
425     /**
426      * Accept the incoming connection.
427      * @hide
428      */
acceptIncomingConnect(BluetoothDevice device)429     public boolean acceptIncomingConnect(BluetoothDevice device) {
430         if (DBG) log("acceptIncomingConnect");
431         if (mService != null) {
432             try {
433                 return mService.acceptIncomingConnect(device);
434             } catch (RemoteException e) {Log.e(TAG, e.toString());}
435         } else {
436             Log.w(TAG, "Proxy not attached to service");
437             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
438         }
439         return false;
440     }
441 
442     /**
443      * Create the connect thread the incoming connection.
444      * @hide
445      */
createIncomingConnect(BluetoothDevice device)446     public boolean createIncomingConnect(BluetoothDevice device) {
447         if (DBG) log("createIncomingConnect");
448         if (mService != null) {
449             try {
450                 return mService.createIncomingConnect(device);
451             } catch (RemoteException e) {Log.e(TAG, e.toString());}
452         } else {
453             Log.w(TAG, "Proxy not attached to service");
454             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
455         }
456         return false;
457     }
458 
459     /**
460      * Reject the incoming connection.
461      * @hide
462      */
rejectIncomingConnect(BluetoothDevice device)463     public boolean rejectIncomingConnect(BluetoothDevice device) {
464         if (DBG) log("rejectIncomingConnect");
465         if (mService != null) {
466             try {
467                 return mService.rejectIncomingConnect(device);
468             } catch (RemoteException e) {Log.e(TAG, e.toString());}
469         } else {
470             Log.w(TAG, "Proxy not attached to service");
471             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
472         }
473         return false;
474     }
475 
476     /**
477      * Connect to a Bluetooth Headset.
478      * Note: This is an internal function and shouldn't be exposed
479      * @hide
480      */
connectHeadsetInternal(BluetoothDevice device)481     public boolean connectHeadsetInternal(BluetoothDevice device) {
482         if (DBG) log("connectHeadsetInternal");
483         if (mService != null) {
484             try {
485                 return mService.connectHeadsetInternal(device);
486             } catch (RemoteException e) {Log.e(TAG, e.toString());}
487         } else {
488             Log.w(TAG, "Proxy not attached to service");
489             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
490         }
491         return false;
492     }
493 
494     /**
495      * Disconnect a Bluetooth Headset.
496      * Note: This is an internal function and shouldn't be exposed
497      * @hide
498      */
disconnectHeadsetInternal(BluetoothDevice device)499     public boolean disconnectHeadsetInternal(BluetoothDevice device) {
500         if (DBG) log("disconnectHeadsetInternal");
501         if (mService != null) {
502             try {
503                  return mService.disconnectHeadsetInternal(device);
504             } catch (RemoteException e) {Log.e(TAG, e.toString());}
505         } else {
506             Log.w(TAG, "Proxy not attached to service");
507             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
508         }
509         return false;
510     }
511     private ServiceConnection mConnection = new ServiceConnection() {
512         public void onServiceConnected(ComponentName className, IBinder service) {
513             if (DBG) Log.d(TAG, "Proxy object connected");
514             mService = IBluetoothHeadset.Stub.asInterface(service);
515             if (mServiceListener != null) {
516                 mServiceListener.onServiceConnected();
517             }
518         }
519         public void onServiceDisconnected(ComponentName className) {
520             if (DBG) Log.d(TAG, "Proxy object disconnected");
521             mService = null;
522             if (mServiceListener != null) {
523                 mServiceListener.onServiceDisconnected();
524             }
525         }
526     };
527 
log(String msg)528     private static void log(String msg) {
529         Log.d(TAG, msg);
530     }
531 }
532