BluetoothHeadset.java revision f5b4b98fada53d91c4c2ebeb5a1d33ccc95c94d2
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.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
23import android.os.RemoteException;
24import android.os.IBinder;
25import android.util.Log;
26
27/**
28 * The Android Bluetooth API is not finalized, and *will* change. Use at your
29 * own risk.
30 *
31 * Public API for controlling the Bluetooth Headset Service. This includes both
32 * Bluetooth Headset and Handsfree (v1.5) profiles. The Headset service will
33 * attempt a handsfree connection first, and fall back to headset.
34 *
35 * BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
36 * Service via IPC.
37 *
38 * Creating a BluetoothHeadset object will create a binding with the
39 * BluetoothHeadset service. Users of this object should call close() when they
40 * are finished with the BluetoothHeadset, so that this proxy object can unbind
41 * from the service.
42 *
43 * This BluetoothHeadset object is not immediately bound to the
44 * BluetoothHeadset service. Use the ServiceListener interface to obtain a
45 * notification when it is bound, this is especially important if you wish to
46 * immediately call methods on BluetootHeadset after construction.
47 *
48 * Android only supports one connected Bluetooth Headset at a time.
49 *
50 * @hide
51 */
52public class BluetoothHeadset {
53
54    private static final String TAG = "BluetoothHeadset";
55    private static final boolean DBG = false;
56
57    private IBluetoothHeadset mService;
58    private final Context mContext;
59    private final ServiceListener mServiceListener;
60
61    /** There was an error trying to obtain the state */
62    public static final int STATE_ERROR        = -1;
63    /** No headset currently connected */
64    public static final int STATE_DISCONNECTED = 0;
65    /** Connection attempt in progress */
66    public static final int STATE_CONNECTING   = 1;
67    /** A headset is currently connected */
68    public static final int STATE_CONNECTED    = 2;
69
70    public static final int RESULT_FAILURE = 0;
71    public static final int RESULT_SUCCESS = 1;
72    /** Connection canceled before completetion. */
73    public static final int RESULT_CANCELED = 2;
74
75    /** Default priority for headsets that should be auto-connected */
76    public static final int PRIORITY_AUTO = 100;
77    /** Default priority for headsets that should not be auto-connected */
78    public static final int PRIORITY_OFF = 0;
79
80    /**
81     * An interface for notifying BluetoothHeadset IPC clients when they have
82     * been connected to the BluetoothHeadset service.
83     */
84    public interface ServiceListener {
85        /**
86         * Called to notify the client when this proxy object has been
87         * connected to the BluetoothHeadset service. Clients must wait for
88         * this callback before making IPC calls on the BluetoothHeadset
89         * service.
90         */
91        public void onServiceConnected();
92
93        /**
94         * Called to notify the client that this proxy object has been
95         * disconnected from the BluetoothHeadset service. Clients must not
96         * make IPC calls on the BluetoothHeadset service after this callback.
97         * This callback will currently only occur if the application hosting
98         * the BluetoothHeadset service, but may be called more often in future.
99         */
100        public void onServiceDisconnected();
101    }
102
103    /**
104     * Create a BluetoothHeadset proxy object.
105     */
106    public BluetoothHeadset(Context context, ServiceListener l) {
107        mContext = context;
108        mServiceListener = l;
109        if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) {
110            Log.e(TAG, "Could not bind to Bluetooth Headset Service");
111        }
112    }
113
114    protected void finalize() throws Throwable {
115        try {
116            close();
117        } finally {
118            super.finalize();
119        }
120    }
121
122    /**
123     * Close the connection to the backing service.
124     * Other public functions of BluetoothHeadset will return default error
125     * results once close() has been called. Multiple invocations of close()
126     * are ok.
127     */
128    public synchronized void close() {
129        if (DBG) log("close()");
130        if (mConnection != null) {
131            mContext.unbindService(mConnection);
132            mConnection = null;
133        }
134    }
135
136    /**
137     * Get the current state of the Bluetooth Headset service.
138     * @return One of the STATE_ return codes, or STATE_ERROR if this proxy
139     *         object is currently not connected to the Headset service.
140     */
141    public int getState() {
142        if (DBG) log("getState()");
143        if (mService != null) {
144            try {
145                return mService.getState();
146            } catch (RemoteException e) {Log.e(TAG, e.toString());}
147        } else {
148            Log.w(TAG, "Proxy not attached to service");
149            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
150        }
151        return BluetoothHeadset.STATE_ERROR;
152    }
153
154    /**
155     * Get the Bluetooth address of the current headset.
156     * @return The Bluetooth address, or null if not in connected or connecting
157     *         state, or if this proxy object is not connected to the Headset
158     *         service.
159     */
160    public String getHeadsetAddress() {
161        if (DBG) log("getHeadsetAddress()");
162        if (mService != null) {
163            try {
164                return mService.getHeadsetAddress();
165            } catch (RemoteException e) {Log.e(TAG, e.toString());}
166        } else {
167            Log.w(TAG, "Proxy not attached to service");
168            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
169        }
170        return null;
171    }
172
173    /**
174     * Request to initiate a connection to a headset.
175     * This call does not block. Fails if a headset is already connecting
176     * or connected.
177     * Initiates auto-connection if address is null. Tries to connect to all
178     * devices with priority greater than PRIORITY_AUTO in descending order.
179     * @param address The Bluetooth Address to connect to, or null to
180     *                auto-connect to the last connected headset.
181     * @return        False if there was a problem initiating the connection
182     *                procedure, and no further HEADSET_STATE_CHANGED intents
183     *                will be expected.
184     */
185    public boolean connectHeadset(String address) {
186        if (DBG) log("connectHeadset(" + address + ")");
187        if (mService != null) {
188            try {
189                if (mService.connectHeadset(address)) {
190                    return true;
191                }
192            } catch (RemoteException e) {Log.e(TAG, e.toString());}
193        } else {
194            Log.w(TAG, "Proxy not attached to service");
195            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
196        }
197        return false;
198    }
199
200    /**
201     * Returns true if the specified headset is connected (does not include
202     * connecting). Returns false if not connected, or if this proxy object
203     * if not currently connected to the headset service.
204     */
205    public boolean isConnected(String address) {
206        if (DBG) log("isConnected(" + address + ")");
207        if (mService != null) {
208            try {
209                return mService.isConnected(address);
210            } catch (RemoteException e) {Log.e(TAG, e.toString());}
211        } else {
212            Log.w(TAG, "Proxy not attached to service");
213            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
214        }
215        return false;
216    }
217
218    /**
219     * Disconnects the current headset. Currently this call blocks, it may soon
220     * be made asynchornous. Returns false if this proxy object is
221     * not currently connected to the Headset service.
222     */
223    public boolean disconnectHeadset() {
224        if (DBG) log("disconnectHeadset()");
225        if (mService != null) {
226            try {
227                mService.disconnectHeadset();
228                return true;
229            } catch (RemoteException e) {Log.e(TAG, e.toString());}
230        } else {
231            Log.w(TAG, "Proxy not attached to service");
232            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
233        }
234        return false;
235    }
236
237    /**
238     * Start BT Voice Recognition mode, and set up Bluetooth audio path.
239     * Returns false if there is no headset connected, or if the
240     * connected headset does not support voice recognition, or on
241     * error.
242     */
243    public boolean startVoiceRecognition() {
244        if (DBG) log("startVoiceRecognition()");
245        if (mService != null) {
246            try {
247                return mService.startVoiceRecognition();
248            } catch (RemoteException e) {Log.e(TAG, e.toString());}
249        } else {
250            Log.w(TAG, "Proxy not attached to service");
251            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
252        }
253        return false;
254    }
255
256    /**
257     * Stop BT Voice Recognition mode, and shut down Bluetooth audio path.
258     * Returns false if there is no headset connected, or the connected
259     * headset is not in voice recognition mode, or on error.
260     */
261    public boolean stopVoiceRecognition() {
262        if (DBG) log("stopVoiceRecognition()");
263        if (mService != null) {
264            try {
265                return mService.stopVoiceRecognition();
266            } catch (RemoteException e) {Log.e(TAG, e.toString());}
267        } else {
268            Log.w(TAG, "Proxy not attached to service");
269            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
270        }
271        return false;
272    }
273
274    /**
275     * Set priority of headset.
276     * Priority is a non-negative integer. By default paired headsets will have
277     * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0).
278     * Headsets with priority greater than zero will be auto-connected, and
279     * incoming connections will be accepted (if no other headset is
280     * connected).
281     * Auto-connection occurs at the following events: boot, incoming phone
282     * call, outgoing phone call.
283     * Headsets with priority equal to zero, or that are unpaired, are not
284     * auto-connected.
285     * Incoming connections are ignored regardless of priority if there is
286     * already a headset connected.
287     * @param address Paired headset
288     * @param priority Integer priority, for example PRIORITY_AUTO or
289     *                 PRIORITY_NONE
290     * @return True if successful, false if there was some error.
291     */
292    public boolean setPriority(String address, int priority) {
293        if (DBG) log("setPriority(" + address + ", " + priority + ")");
294        if (mService != null) {
295            try {
296                return mService.setPriority(address, priority);
297            } catch (RemoteException e) {Log.e(TAG, e.toString());}
298        } else {
299            Log.w(TAG, "Proxy not attached to service");
300            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
301        }
302        return false;
303    }
304
305    /**
306     * Get priority of headset.
307     * @param address Headset
308     * @return non-negative priority, or negative error code on error.
309     */
310    public int getPriority(String address) {
311        if (DBG) log("getPriority(" + address + ")");
312        if (mService != null) {
313            try {
314                return mService.getPriority(address);
315            } catch (RemoteException e) {Log.e(TAG, e.toString());}
316        } else {
317            Log.w(TAG, "Proxy not attached to service");
318            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
319        }
320        return -1;
321    }
322
323    /**
324     * Check class bits for possible HSP or HFP support.
325     * This is a simple heuristic that tries to guess if a device with the
326     * given class bits might support HSP or HFP. It is not accurate for all
327     * devices. It tries to err on the side of false positives.
328     * @return True if this device might support HSP or HFP.
329     */
330    public static boolean doesClassMatch(int btClass) {
331        // The render service class is required by the spec for HFP, so is a
332        // pretty good signal
333        if (BluetoothClass.Service.hasService(btClass, BluetoothClass.Service.RENDER)) {
334            return true;
335        }
336        // Just in case they forgot the render service class
337        switch (BluetoothClass.Device.getDevice(btClass)) {
338        case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
339        case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
340        case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
341            return true;
342        default:
343            return false;
344        }
345    }
346
347    private ServiceConnection mConnection = new ServiceConnection() {
348        public void onServiceConnected(ComponentName className, IBinder service) {
349            if (DBG) Log.d(TAG, "Proxy object connected");
350            mService = IBluetoothHeadset.Stub.asInterface(service);
351            if (mServiceListener != null) {
352                mServiceListener.onServiceConnected();
353            }
354        }
355        public void onServiceDisconnected(ComponentName className) {
356            if (DBG) Log.d(TAG, "Proxy object disconnected");
357            mService = null;
358            if (mServiceListener != null) {
359                mServiceListener.onServiceDisconnected();
360            }
361        }
362    };
363
364    private static void log(String msg) {
365        Log.d(TAG, msg);
366    }
367}
368