BluetoothHeadset.java revision 03cd78cf5e51c3adb78d2e3d314838dcf3e36b26
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.IBinder;
26import android.os.RemoteException;
27import android.util.Log;
28
29import java.util.ArrayList;
30import 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 */
46public 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     * {@link #EXTRA_STATE} - The current state of the profile.
56     * {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile
57     * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
58     *
59     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
60     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
61     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
62     *
63     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
64     */
65    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
66    public static final String ACTION_CONNECTION_STATE_CHANGED =
67        "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
68
69    /**
70     * Intent used to broadcast the change in the Audio Connection state of the
71     * A2DP profile.
72     *
73     * <p>This intent will have 3 extras:
74     * {@link #EXTRA_STATE} - The current state of the profile.
75     * {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile
76     * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
77     *
78     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
79     * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
80     *
81     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
82     */
83    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
84    public static final String ACTION_AUDIO_STATE_CHANGED =
85        "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
86
87
88    /**
89     * Broadcast Action: Indicates a headset has posted a vendor-specific event.
90     * <p>Always contains the extra fields {@link #EXTRA_DEVICE},
91     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD}, and
92     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS}.
93     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
94     * @hide
95     */
96    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
97    public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
98            "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
99
100    /**
101     * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
102     * intents that contains the name of the vendor-specific command.
103     * @hide
104     */
105    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
106            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
107
108    /**
109     * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
110     * intents that contains the Company ID of the vendor defining the vendor-specific
111     * command.
112     * @see <a href="https://www.bluetooth.org/Technical/AssignedNumbers/identifiers.htm">
113     * Bluetooth SIG Assigned Numbers - Company Identifiers</a>
114     * @hide
115     */
116    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID =
117            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID";
118
119    /**
120     * A Parcelable String array extra field in
121     * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
122     * the arguments to the vendor-specific command.
123     * @hide
124     */
125    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
126            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
127
128    /*
129     * Headset state when SCO audio is connected
130     * This state can be one of
131     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
132     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
133     */
134    public static final int STATE_AUDIO_CONNECTED = 10;
135
136    /**
137     * Headset state when SCO audio is NOT connected
138     * This state can be one of
139     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
140     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
141     */
142    public static final int STATE_AUDIO_DISCONNECTED = 11;
143
144
145    private Context mContext;
146    private ServiceListener mServiceListener;
147    private IBluetoothHeadset mService;
148    BluetoothAdapter mAdapter;
149
150    /**
151     * Create a BluetoothHeadset proxy object.
152     */
153    /*package*/ BluetoothHeadset(Context context, ServiceListener l) {
154        mContext = context;
155        mServiceListener = l;
156        mAdapter = BluetoothAdapter.getDefaultAdapter();
157        if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) {
158            Log.e(TAG, "Could not bind to Bluetooth Headset Service");
159        }
160    }
161
162    /**
163     * Close the connection to the backing service.
164     * Other public functions of BluetoothHeadset will return default error
165     * results once close() has been called. Multiple invocations of close()
166     * are ok.
167     */
168    /*package*/ synchronized void close() {
169        if (DBG) log("close()");
170        if (mConnection != null) {
171            mContext.unbindService(mConnection);
172            mConnection = null;
173        }
174    }
175
176    /**
177     * {@inheritDoc}
178     * @hide
179     */
180    public boolean connect(BluetoothDevice device) {
181        if (DBG) log("connect(" + device + ")");
182        if (mService != null && isEnabled() &&
183            isValidDevice(device)) {
184            try {
185                return mService.connect(device);
186            } catch (RemoteException e) {
187                Log.e(TAG, Log.getStackTraceString(new Throwable()));
188                return false;
189            }
190        }
191        if (mService == null) Log.w(TAG, "Proxy not attached to service");
192        return false;
193    }
194
195    /**
196     * {@inheritDoc}
197     * @hide
198     */
199    public boolean disconnect(BluetoothDevice device) {
200        if (DBG) log("disconnect(" + device + ")");
201        if (mService != null && isEnabled() &&
202            isValidDevice(device)) {
203            try {
204                return mService.disconnect(device);
205            } catch (RemoteException e) {
206              Log.e(TAG, Log.getStackTraceString(new Throwable()));
207              return false;
208            }
209        }
210        if (mService == null) Log.w(TAG, "Proxy not attached to service");
211        return false;
212    }
213
214    /**
215     * {@inheritDoc}
216     */
217    public List<BluetoothDevice> getConnectedDevices() {
218        if (DBG) log("getConnectedDevices()");
219        if (mService != null && isEnabled()) {
220            try {
221                return mService.getConnectedDevices();
222            } catch (RemoteException e) {
223                Log.e(TAG, Log.getStackTraceString(new Throwable()));
224                return new ArrayList<BluetoothDevice>();
225            }
226        }
227        if (mService == null) Log.w(TAG, "Proxy not attached to service");
228        return new ArrayList<BluetoothDevice>();
229    }
230
231    /**
232     * {@inheritDoc}
233     */
234    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
235        if (DBG) log("getDevicesMatchingStates()");
236        if (mService != null && isEnabled()) {
237            try {
238                return mService.getDevicesMatchingConnectionStates(states);
239            } catch (RemoteException e) {
240                Log.e(TAG, Log.getStackTraceString(new Throwable()));
241                return new ArrayList<BluetoothDevice>();
242            }
243        }
244        if (mService == null) Log.w(TAG, "Proxy not attached to service");
245        return new ArrayList<BluetoothDevice>();
246    }
247
248    /**
249     * {@inheritDoc}
250     */
251    public int getConnectionState(BluetoothDevice device) {
252        if (DBG) log("getConnectionState(" + device + ")");
253        if (mService != null && isEnabled() &&
254            isValidDevice(device)) {
255            try {
256                return mService.getConnectionState(device);
257            } catch (RemoteException e) {
258                Log.e(TAG, Log.getStackTraceString(new Throwable()));
259                return BluetoothProfile.STATE_DISCONNECTED;
260            }
261        }
262        if (mService == null) Log.w(TAG, "Proxy not attached to service");
263        return BluetoothProfile.STATE_DISCONNECTED;
264    }
265
266    /**
267     * {@inheritDoc}
268     * @hide
269     */
270    public boolean setPriority(BluetoothDevice device, int priority) {
271        if (DBG) log("setPriority(" + device + ", " + priority + ")");
272        if (mService != null && isEnabled() &&
273            isValidDevice(device)) {
274            if (priority != BluetoothProfile.PRIORITY_OFF &&
275                priority != BluetoothProfile.PRIORITY_ON) {
276              return false;
277            }
278            try {
279                return mService.setPriority(device, priority);
280            } catch (RemoteException e) {
281                Log.e(TAG, Log.getStackTraceString(new Throwable()));
282                return false;
283            }
284        }
285        if (mService == null) Log.w(TAG, "Proxy not attached to service");
286        return false;
287    }
288
289    /**
290     * {@inheritDoc}
291     * @hide
292     */
293    public int getPriority(BluetoothDevice device) {
294        if (DBG) log("getPriority(" + device + ")");
295        if (mService != null && isEnabled() &&
296            isValidDevice(device)) {
297            try {
298                return mService.getPriority(device);
299            } catch (RemoteException e) {
300                Log.e(TAG, Log.getStackTraceString(new Throwable()));
301                return PRIORITY_OFF;
302            }
303        }
304        if (mService == null) Log.w(TAG, "Proxy not attached to service");
305        return PRIORITY_OFF;
306    }
307
308    /**
309     * Start Bluetooth voice recognition. This methods sends the voice
310     * recognition AT command to the headset and establishes the
311     * audio connection.
312     *
313     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
314     * {@link #EXTRA_STATE} will be set to {@link #STATE_AUDIO_CONNECTED}
315     * when the audio connection is established.
316     *
317     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
318     *
319     * @param device Bluetooth headset
320     * @return false if there is no headset connected of if the
321     *               connected headset doesn't support voice recognition
322     *               or on error, true otherwise
323     */
324    public boolean startVoiceRecognition(BluetoothDevice device) {
325        if (DBG) log("startVoiceRecognition()");
326        if (mService != null && isEnabled() &&
327            isValidDevice(device)) {
328            try {
329                return mService.startVoiceRecognition(device);
330            } catch (RemoteException e) {
331                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
332            }
333        }
334        if (mService == null) Log.w(TAG, "Proxy not attached to service");
335        return false;
336    }
337
338    /**
339     * Stop Bluetooth Voice Recognition mode, and shut down the
340     * Bluetooth audio path.
341     *
342     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
343     *
344     * @param device Bluetooth headset
345     * @return false if there is no headset connected
346     *               or on error, true otherwise
347     */
348    public boolean stopVoiceRecognition(BluetoothDevice device) {
349        if (DBG) log("stopVoiceRecognition()");
350        if (mService != null && isEnabled() &&
351            isValidDevice(device)) {
352            try {
353                return mService.stopVoiceRecognition(device);
354            } catch (RemoteException e) {
355                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
356            }
357        }
358        if (mService == null) Log.w(TAG, "Proxy not attached to service");
359        return false;
360    }
361
362    /**
363     * Check if Bluetooth SCO audio is connected.
364     *
365     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
366     *
367     * @param device Bluetooth headset
368     * @return true if SCO is connected,
369     *         false otherwise or on error
370     */
371    public boolean isAudioConnected(BluetoothDevice device) {
372        if (DBG) log("isAudioConnected()");
373        if (mService != null && isEnabled() &&
374            isValidDevice(device)) {
375            try {
376              return mService.isAudioConnected(device);
377            } catch (RemoteException e) {
378              Log.e(TAG,  Log.getStackTraceString(new Throwable()));
379            }
380        }
381        if (mService == null) Log.w(TAG, "Proxy not attached to service");
382        return false;
383    }
384
385    /**
386     * Get battery usage hint for Bluetooth Headset service.
387     * This is a monotonically increasing integer. Wraps to 0 at
388     * Integer.MAX_INT, and at boot.
389     * Current implementation returns the number of AT commands handled since
390     * boot. This is a good indicator for spammy headset/handsfree units that
391     * can keep the device awake by polling for cellular status updates. As a
392     * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
393     *
394     * @param device the bluetooth headset.
395     * @return monotonically increasing battery usage hint, or a negative error
396     *         code on error
397     * @hide
398     */
399    public int getBatteryUsageHint(BluetoothDevice device) {
400        if (DBG) log("getBatteryUsageHint()");
401        if (mService != null && isEnabled() &&
402            isValidDevice(device)) {
403            try {
404                return mService.getBatteryUsageHint(device);
405            } catch (RemoteException e) {
406                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
407            }
408        }
409        if (mService == null) Log.w(TAG, "Proxy not attached to service");
410        return -1;
411    }
412
413    /**
414     * Indicates if current platform supports voice dialing over bluetooth SCO.
415     *
416     * @return true if voice dialing over bluetooth is supported, false otherwise.
417     * @hide
418     */
419    public static boolean isBluetoothVoiceDialingEnabled(Context context) {
420        return context.getResources().getBoolean(
421                com.android.internal.R.bool.config_bluetooth_sco_off_call);
422    }
423
424    /**
425     * Cancel the outgoing connection.
426     * Note: This is an internal function and shouldn't be exposed
427     *
428     * @hide
429     */
430    public boolean cancelConnectThread() {
431        if (DBG) log("cancelConnectThread");
432        if (mService != null && isEnabled()) {
433            try {
434                return mService.cancelConnectThread();
435            } catch (RemoteException e) {Log.e(TAG, e.toString());}
436        } else {
437            Log.w(TAG, "Proxy not attached to service");
438            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
439        }
440        return false;
441    }
442
443    /**
444     * Accept the incoming connection.
445     * Note: This is an internal function and shouldn't be exposed
446     *
447     * @hide
448     */
449    public boolean acceptIncomingConnect(BluetoothDevice device) {
450        if (DBG) log("acceptIncomingConnect");
451        if (mService != null && isEnabled()) {
452            try {
453                return mService.acceptIncomingConnect(device);
454            } catch (RemoteException e) {Log.e(TAG, e.toString());}
455        } else {
456            Log.w(TAG, "Proxy not attached to service");
457            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
458        }
459        return false;
460    }
461
462    /**
463     * Create the connect thread for the incoming connection.
464     * Note: This is an internal function and shouldn't be exposed
465     *
466     * @hide
467     */
468    public boolean createIncomingConnect(BluetoothDevice device) {
469        if (DBG) log("createIncomingConnect");
470        if (mService != null && isEnabled()) {
471            try {
472                return mService.createIncomingConnect(device);
473            } catch (RemoteException e) {Log.e(TAG, e.toString());}
474        } else {
475            Log.w(TAG, "Proxy not attached to service");
476            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
477        }
478        return false;
479    }
480
481    /**
482     * Connect to a Bluetooth Headset.
483     * Note: This is an internal function and shouldn't be exposed
484     *
485     * @hide
486     */
487    public boolean connectHeadsetInternal(BluetoothDevice device) {
488        if (DBG) log("connectHeadsetInternal");
489        if (mService != null && isEnabled()) {
490            try {
491                return mService.connectHeadsetInternal(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     * Disconnect a Bluetooth Headset.
502     * Note: This is an internal function and shouldn't be exposed
503     *
504     * @hide
505     */
506    public boolean disconnectHeadsetInternal(BluetoothDevice device) {
507        if (DBG) log("disconnectHeadsetInternal");
508        if (mService != null && isEnabled()) {
509            try {
510                 return mService.disconnectHeadsetInternal(device);
511            } catch (RemoteException e) {Log.e(TAG, e.toString());}
512        } else {
513            Log.w(TAG, "Proxy not attached to service");
514            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
515        }
516        return false;
517    }
518
519    /**
520     * Set the audio state of the Headset.
521     * Note: This is an internal function and shouldn't be exposed
522     *
523     * @hide
524     */
525    public boolean setAudioState(BluetoothDevice device, int state) {
526        if (DBG) log("setAudioState");
527        if (mService != null && isEnabled()) {
528            try {
529                return mService.setAudioState(device, state);
530            } catch (RemoteException e) {Log.e(TAG, e.toString());}
531        } else {
532            Log.w(TAG, "Proxy not attached to service");
533            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
534        }
535        return false;
536    }
537
538    private ServiceConnection mConnection = new ServiceConnection() {
539        public void onServiceConnected(ComponentName className, IBinder service) {
540            if (DBG) Log.d(TAG, "Proxy object connected");
541            mService = IBluetoothHeadset.Stub.asInterface(service);
542
543            if (mServiceListener != null) {
544                mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this);
545            }
546        }
547        public void onServiceDisconnected(ComponentName className) {
548            if (DBG) Log.d(TAG, "Proxy object disconnected");
549            mService = null;
550            if (mServiceListener != null) {
551                mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
552            }
553        }
554    };
555
556    private boolean isEnabled() {
557       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
558       return false;
559    }
560
561    private boolean isValidDevice(BluetoothDevice device) {
562       if (device == null) return false;
563
564       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
565       return false;
566    }
567
568    private static void log(String msg) {
569        Log.d(TAG, msg);
570    }
571}
572