BluetoothHeadset.java revision 4e25533945ebefd888b48f4256e8735591b4f94b
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
17package android.bluetooth;
18
19import android.annotation.SdkConstant;
20import android.annotation.SdkConstant.SdkConstantType;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ServiceConnection;
25import android.os.RemoteException;
26import android.os.IBinder;
27import 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 */
54public 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     * Broadcast Action: Indicates a headset has posted a vendor-specific event.
89     * <p>Always contains the extra fields {@link #EXTRA_DEVICE},
90     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD}, and
91     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS}.
92     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
93     * @hide
94     */
95    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
96    public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
97            "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
98
99    /**
100     * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
101     * intents that contains the name of the vendor-specific command.
102     * @hide
103     */
104    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
105            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
106
107    /**
108     * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
109     * intents that contains the Company ID of the vendor defining the vendor-specific
110     * command.
111     * @see <a href="https://www.bluetooth.org/Technical/AssignedNumbers/identifiers.htm">
112     * Bluetooth SIG Assigned Numbers - Company Identifiers</a>
113     * @hide
114     */
115    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID =
116            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID";
117
118    /**
119     * A Parcelable String array extra field in
120     * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
121     * the arguments to the vendor-specific command.
122     * @hide
123     */
124    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
125            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
126
127
128    /**
129     * TODO(API release): Consider incorporating as new state in
130     * HEADSET_STATE_CHANGED
131     */
132    private IBluetoothHeadset mService;
133    private final Context mContext;
134    private final ServiceListener mServiceListener;
135
136    /** There was an error trying to obtain the state */
137    public static final int STATE_ERROR        = -1;
138    /** No headset currently connected */
139    public static final int STATE_DISCONNECTED = 0;
140    /** Connection attempt in progress */
141    public static final int STATE_CONNECTING   = 1;
142    /** A headset is currently connected */
143    public static final int STATE_CONNECTED    = 2;
144
145    /** A SCO audio channel is not established */
146    public static final int AUDIO_STATE_DISCONNECTED = 0;
147    /** A SCO audio channel is established */
148    public static final int AUDIO_STATE_CONNECTED = 1;
149
150    public static final int RESULT_FAILURE = 0;
151    public static final int RESULT_SUCCESS = 1;
152    /** Connection canceled before completion. */
153    public static final int RESULT_CANCELED = 2;
154
155    /** Values for {@link #EXTRA_DISCONNECT_INITIATOR} */
156    public static final int REMOTE_DISCONNECT = 0;
157    public static final int LOCAL_DISCONNECT = 1;
158
159
160    /** Default priority for headsets that  for which we will accept
161     * inconing connections and auto-connect */
162    public static final int PRIORITY_AUTO_CONNECT = 1000;
163    /** Default priority for headsets that  for which we will accept
164     * inconing connections but not auto-connect */
165    public static final int PRIORITY_ON = 100;
166    /** Default priority for headsets that should not be auto-connected
167     * and not allow incoming connections. */
168    public static final int PRIORITY_OFF = 0;
169    /** Default priority when not set or when the device is unpaired */
170    public static final int PRIORITY_UNDEFINED = -1;
171
172    /**
173     * An interface for notifying BluetoothHeadset IPC clients when they have
174     * been connected to the BluetoothHeadset service.
175     */
176    public interface ServiceListener {
177        /**
178         * Called to notify the client when this proxy object has been
179         * connected to the BluetoothHeadset service. Clients must wait for
180         * this callback before making IPC calls on the BluetoothHeadset
181         * service.
182         */
183        public void onServiceConnected();
184
185        /**
186         * Called to notify the client that this proxy object has been
187         * disconnected from the BluetoothHeadset service. Clients must not
188         * make IPC calls on the BluetoothHeadset service after this callback.
189         * This callback will currently only occur if the application hosting
190         * the BluetoothHeadset service, but may be called more often in future.
191         */
192        public void onServiceDisconnected();
193    }
194
195    /**
196     * Create a BluetoothHeadset proxy object.
197     */
198    public BluetoothHeadset(Context context, ServiceListener l) {
199        mContext = context;
200        mServiceListener = l;
201        if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) {
202            Log.e(TAG, "Could not bind to Bluetooth Headset Service");
203        }
204    }
205
206    protected void finalize() throws Throwable {
207        try {
208            close();
209        } finally {
210            super.finalize();
211        }
212    }
213
214    /**
215     * Close the connection to the backing service.
216     * Other public functions of BluetoothHeadset will return default error
217     * results once close() has been called. Multiple invocations of close()
218     * are ok.
219     */
220    public synchronized void close() {
221        if (DBG) log("close()");
222        if (mConnection != null) {
223            mContext.unbindService(mConnection);
224            mConnection = null;
225        }
226    }
227
228    /**
229     * Get the current state of the Bluetooth Headset service.
230     * @return One of the STATE_ return codes, or STATE_ERROR if this proxy
231     *         object is currently not connected to the Headset service.
232     */
233    public int getState(BluetoothDevice device) {
234        if (DBG) log("getState()");
235        if (mService != null) {
236            try {
237                return mService.getState(device);
238            } catch (RemoteException e) {Log.e(TAG, e.toString());}
239        } else {
240            Log.w(TAG, "Proxy not attached to service");
241            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
242        }
243        return BluetoothHeadset.STATE_ERROR;
244    }
245
246    /**
247     * Get the BluetoothDevice for the current headset.
248     * @return current headset, or null if not in connected or connecting
249     *         state, or if this proxy object is not connected to the Headset
250     *         service.
251     */
252    public BluetoothDevice getCurrentHeadset() {
253        if (DBG) log("getCurrentHeadset()");
254        if (mService != null) {
255            try {
256                return mService.getCurrentHeadset();
257            } catch (RemoteException e) {Log.e(TAG, e.toString());}
258        } else {
259            Log.w(TAG, "Proxy not attached to service");
260            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
261        }
262        return null;
263    }
264
265    /**
266     * Request to initiate a connection to a headset.
267     * This call does not block. Fails if a headset is already connecting
268     * or connected.
269     * Initiates auto-connection if device is null. Tries to connect to all
270     * devices with priority greater than PRIORITY_AUTO in descending order.
271     * @param device device to connect to, or null to auto-connect last connected
272     *               headset
273     * @return       false if there was a problem initiating the connection
274     *               procedure, and no further HEADSET_STATE_CHANGED intents
275     *               will be expected.
276     */
277    public boolean connectHeadset(BluetoothDevice device) {
278        if (DBG) log("connectHeadset(" + device + ")");
279        if (mService != null) {
280            try {
281                if (mService.connectHeadset(device)) {
282                    return true;
283                }
284            } catch (RemoteException e) {Log.e(TAG, e.toString());}
285        } else {
286            Log.w(TAG, "Proxy not attached to service");
287            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
288        }
289        return false;
290    }
291
292    /**
293     * Returns true if the specified headset is connected (does not include
294     * connecting). Returns false if not connected, or if this proxy object
295     * if not currently connected to the headset service.
296     */
297    public boolean isConnected(BluetoothDevice device) {
298        if (DBG) log("isConnected(" + device + ")");
299        if (mService != null) {
300            try {
301                return mService.isConnected(device);
302            } catch (RemoteException e) {Log.e(TAG, e.toString());}
303        } else {
304            Log.w(TAG, "Proxy not attached to service");
305            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
306        }
307        return false;
308    }
309
310    /**
311     * Disconnects the current headset. Currently this call blocks, it may soon
312     * be made asynchornous. Returns false if this proxy object is
313     * not currently connected to the Headset service.
314     */
315    public boolean disconnectHeadset(BluetoothDevice device) {
316        if (DBG) log("disconnectHeadset()");
317        if (mService != null) {
318            try {
319                mService.disconnectHeadset(device);
320                return true;
321            } catch (RemoteException e) {Log.e(TAG, e.toString());}
322        } else {
323            Log.w(TAG, "Proxy not attached to service");
324            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
325        }
326        return false;
327    }
328
329    /**
330     * Start BT Voice Recognition mode, and set up Bluetooth audio path.
331     * Returns false if there is no headset connected, or if the
332     * connected headset does not support voice recognition, or on
333     * error.
334     */
335    public boolean startVoiceRecognition() {
336        if (DBG) log("startVoiceRecognition()");
337        if (mService != null) {
338            try {
339                return mService.startVoiceRecognition();
340            } catch (RemoteException e) {Log.e(TAG, e.toString());}
341        } else {
342            Log.w(TAG, "Proxy not attached to service");
343            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
344        }
345        return false;
346    }
347
348    /**
349     * Stop BT Voice Recognition mode, and shut down Bluetooth audio path.
350     * Returns false if there is no headset connected, or the connected
351     * headset is not in voice recognition mode, or on error.
352     */
353    public boolean stopVoiceRecognition() {
354        if (DBG) log("stopVoiceRecognition()");
355        if (mService != null) {
356            try {
357                return mService.stopVoiceRecognition();
358            } catch (RemoteException e) {Log.e(TAG, e.toString());}
359        } else {
360            Log.w(TAG, "Proxy not attached to service");
361            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
362        }
363        return false;
364    }
365
366    /**
367     * Set priority of headset.
368     * Priority is a non-negative integer. By default paired headsets will have
369     * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0).
370     * Headsets with priority greater than zero will be auto-connected, and
371     * incoming connections will be accepted (if no other headset is
372     * connected).
373     * Auto-connection occurs at the following events: boot, incoming phone
374     * call, outgoing phone call.
375     * Headsets with priority equal to zero, or that are unpaired, are not
376     * auto-connected.
377     * Incoming connections are ignored regardless of priority if there is
378     * already a headset connected.
379     * @param device paired headset
380     * @param priority Integer priority, for example PRIORITY_AUTO or
381     *                 PRIORITY_NONE
382     * @return true if successful, false if there was some error
383     */
384    public boolean setPriority(BluetoothDevice device, int priority) {
385        if (DBG) log("setPriority(" + device + ", " + priority + ")");
386        if (mService != null) {
387            try {
388                return mService.setPriority(device, priority);
389            } catch (RemoteException e) {Log.e(TAG, e.toString());}
390        } else {
391            Log.w(TAG, "Proxy not attached to service");
392            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
393        }
394        return false;
395    }
396
397    /**
398     * Get priority of headset.
399     * @param device headset
400     * @return non-negative priority, or negative error code on error
401     */
402    public int getPriority(BluetoothDevice device) {
403        if (DBG) log("getPriority(" + device + ")");
404        if (mService != null) {
405            try {
406                return mService.getPriority(device);
407            } catch (RemoteException e) {Log.e(TAG, e.toString());}
408        } else {
409            Log.w(TAG, "Proxy not attached to service");
410            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
411        }
412        return -1;
413    }
414
415    /**
416     * Get battery usage hint for Bluetooth Headset service.
417     * This is a monotonically increasing integer. Wraps to 0 at
418     * Integer.MAX_INT, and at boot.
419     * Current implementation returns the number of AT commands handled since
420     * boot. This is a good indicator for spammy headset/handsfree units that
421     * can keep the device awake by polling for cellular status updates. As a
422     * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
423     * @return monotonically increasing battery usage hint, or a negative error
424     *         code on error
425     * @hide
426     */
427    public int getBatteryUsageHint() {
428        if (DBG) log("getBatteryUsageHint()");
429        if (mService != null) {
430            try {
431                return mService.getBatteryUsageHint();
432            } catch (RemoteException e) {Log.e(TAG, e.toString());}
433        } else {
434            Log.w(TAG, "Proxy not attached to service");
435            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
436        }
437        return -1;
438    }
439    /**
440     * Indicates if current platform supports voice dialing over bluetooth SCO.
441     * @return true if voice dialing over bluetooth is supported, false otherwise.
442     * @hide
443     */
444    public static boolean isBluetoothVoiceDialingEnabled(Context context) {
445        return context.getResources().getBoolean(
446                com.android.internal.R.bool.config_bluetooth_sco_off_call);
447    }
448
449    /**
450     * Cancel the outgoing connection.
451     * @hide
452     */
453    public boolean cancelConnectThread() {
454        if (DBG) log("cancelConnectThread");
455        if (mService != null) {
456            try {
457                return mService.cancelConnectThread();
458            } catch (RemoteException e) {Log.e(TAG, e.toString());}
459        } else {
460            Log.w(TAG, "Proxy not attached to service");
461            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
462        }
463        return false;
464    }
465
466    /**
467     * Accept the incoming connection.
468     * @hide
469     */
470    public boolean acceptIncomingConnect(BluetoothDevice device) {
471        if (DBG) log("acceptIncomingConnect");
472        if (mService != null) {
473            try {
474                return mService.acceptIncomingConnect(device);
475            } catch (RemoteException e) {Log.e(TAG, e.toString());}
476        } else {
477            Log.w(TAG, "Proxy not attached to service");
478            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
479        }
480        return false;
481    }
482
483    /**
484     * Create the connect thread the incoming connection.
485     * @hide
486     */
487    public boolean createIncomingConnect(BluetoothDevice device) {
488        if (DBG) log("createIncomingConnect");
489        if (mService != null) {
490            try {
491                return mService.createIncomingConnect(device);
492            } catch (RemoteException e) {Log.e(TAG, e.toString());}
493        } else {
494            Log.w(TAG, "Proxy not attached to service");
495            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
496        }
497        return false;
498    }
499
500    /**
501     * Connect to a Bluetooth Headset.
502     * Note: This is an internal function and shouldn't be exposed
503     * @hide
504     */
505    public boolean connectHeadsetInternal(BluetoothDevice device) {
506        if (DBG) log("connectHeadsetInternal");
507        if (mService != null) {
508            try {
509                return mService.connectHeadsetInternal(device);
510            } catch (RemoteException e) {Log.e(TAG, e.toString());}
511        } else {
512            Log.w(TAG, "Proxy not attached to service");
513            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
514        }
515        return false;
516    }
517
518    /**
519     * Disconnect a Bluetooth Headset.
520     * Note: This is an internal function and shouldn't be exposed
521     * @hide
522     */
523    public boolean disconnectHeadsetInternal(BluetoothDevice device) {
524        if (DBG) log("disconnectHeadsetInternal");
525        if (mService != null) {
526            try {
527                 return mService.disconnectHeadsetInternal(device);
528            } catch (RemoteException e) {Log.e(TAG, e.toString());}
529        } else {
530            Log.w(TAG, "Proxy not attached to service");
531            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
532        }
533        return false;
534    }
535    private ServiceConnection mConnection = new ServiceConnection() {
536        public void onServiceConnected(ComponentName className, IBinder service) {
537            if (DBG) Log.d(TAG, "Proxy object connected");
538            mService = IBluetoothHeadset.Stub.asInterface(service);
539            if (mServiceListener != null) {
540                mServiceListener.onServiceConnected();
541            }
542        }
543        public void onServiceDisconnected(ComponentName className) {
544            if (DBG) Log.d(TAG, "Proxy object disconnected");
545            mService = null;
546            if (mServiceListener != null) {
547                mServiceListener.onServiceDisconnected();
548            }
549        }
550    };
551
552    private static void log(String msg) {
553        Log.d(TAG, msg);
554    }
555}
556