• 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 BluetootHeadset 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     /**
77      * TODO(API release): Consider incorporating as new state in
78      * HEADSET_STATE_CHANGED
79      */
80     private IBluetoothHeadset mService;
81     private final Context mContext;
82     private final ServiceListener mServiceListener;
83 
84     /** There was an error trying to obtain the state */
85     public static final int STATE_ERROR        = -1;
86     /** No headset currently connected */
87     public static final int STATE_DISCONNECTED = 0;
88     /** Connection attempt in progress */
89     public static final int STATE_CONNECTING   = 1;
90     /** A headset is currently connected */
91     public static final int STATE_CONNECTED    = 2;
92 
93     /** A SCO audio channel is not established */
94     public static final int AUDIO_STATE_DISCONNECTED = 0;
95     /** A SCO audio channel is established */
96     public static final int AUDIO_STATE_CONNECTED = 1;
97 
98     public static final int RESULT_FAILURE = 0;
99     public static final int RESULT_SUCCESS = 1;
100     /** Connection canceled before completetion. */
101     public static final int RESULT_CANCELED = 2;
102 
103     /** Default priority for headsets that should be auto-connected */
104     public static final int PRIORITY_AUTO = 100;
105     /** Default priority for headsets that should not be auto-connected */
106     public static final int PRIORITY_OFF = 0;
107 
108     /** The voice dialer 'works' but the user experience is poor. The voice
109      *  recognizer has trouble dealing with the 8kHz SCO signal, and it still
110      *  requires visual confirmation. Disable for cupcake.
111      */
112     public static final boolean DISABLE_BT_VOICE_DIALING = true;
113 
114     /**
115      * An interface for notifying BluetoothHeadset IPC clients when they have
116      * been connected to the BluetoothHeadset service.
117      */
118     public interface ServiceListener {
119         /**
120          * Called to notify the client when this proxy object has been
121          * connected to the BluetoothHeadset service. Clients must wait for
122          * this callback before making IPC calls on the BluetoothHeadset
123          * service.
124          */
onServiceConnected()125         public void onServiceConnected();
126 
127         /**
128          * Called to notify the client that this proxy object has been
129          * disconnected from the BluetoothHeadset service. Clients must not
130          * make IPC calls on the BluetoothHeadset service after this callback.
131          * This callback will currently only occur if the application hosting
132          * the BluetoothHeadset service, but may be called more often in future.
133          */
onServiceDisconnected()134         public void onServiceDisconnected();
135     }
136 
137     /**
138      * Create a BluetoothHeadset proxy object.
139      */
BluetoothHeadset(Context context, ServiceListener l)140     public BluetoothHeadset(Context context, ServiceListener l) {
141         mContext = context;
142         mServiceListener = l;
143         if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) {
144             Log.e(TAG, "Could not bind to Bluetooth Headset Service");
145         }
146     }
147 
finalize()148     protected void finalize() throws Throwable {
149         try {
150             close();
151         } finally {
152             super.finalize();
153         }
154     }
155 
156     /**
157      * Close the connection to the backing service.
158      * Other public functions of BluetoothHeadset will return default error
159      * results once close() has been called. Multiple invocations of close()
160      * are ok.
161      */
close()162     public synchronized void close() {
163         if (DBG) log("close()");
164         if (mConnection != null) {
165             mContext.unbindService(mConnection);
166             mConnection = null;
167         }
168     }
169 
170     /**
171      * Get the current state of the Bluetooth Headset service.
172      * @return One of the STATE_ return codes, or STATE_ERROR if this proxy
173      *         object is currently not connected to the Headset service.
174      */
getState()175     public int getState() {
176         if (DBG) log("getState()");
177         if (mService != null) {
178             try {
179                 return mService.getState();
180             } catch (RemoteException e) {Log.e(TAG, e.toString());}
181         } else {
182             Log.w(TAG, "Proxy not attached to service");
183             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
184         }
185         return BluetoothHeadset.STATE_ERROR;
186     }
187 
188     /**
189      * Get the BluetoothDevice for the current headset.
190      * @return current headset, or null if not in connected or connecting
191      *         state, or if this proxy object is not connected to the Headset
192      *         service.
193      */
getCurrentHeadset()194     public BluetoothDevice getCurrentHeadset() {
195         if (DBG) log("getCurrentHeadset()");
196         if (mService != null) {
197             try {
198                 return mService.getCurrentHeadset();
199             } catch (RemoteException e) {Log.e(TAG, e.toString());}
200         } else {
201             Log.w(TAG, "Proxy not attached to service");
202             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
203         }
204         return null;
205     }
206 
207     /**
208      * Request to initiate a connection to a headset.
209      * This call does not block. Fails if a headset is already connecting
210      * or connected.
211      * Initiates auto-connection if device is null. Tries to connect to all
212      * devices with priority greater than PRIORITY_AUTO in descending order.
213      * @param device device to connect to, or null to auto-connect last connected
214      *               headset
215      * @return       false if there was a problem initiating the connection
216      *               procedure, and no further HEADSET_STATE_CHANGED intents
217      *               will be expected.
218      */
connectHeadset(BluetoothDevice device)219     public boolean connectHeadset(BluetoothDevice device) {
220         if (DBG) log("connectHeadset(" + device + ")");
221         if (mService != null) {
222             try {
223                 if (mService.connectHeadset(device)) {
224                     return true;
225                 }
226             } catch (RemoteException e) {Log.e(TAG, e.toString());}
227         } else {
228             Log.w(TAG, "Proxy not attached to service");
229             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
230         }
231         return false;
232     }
233 
234     /**
235      * Returns true if the specified headset is connected (does not include
236      * connecting). Returns false if not connected, or if this proxy object
237      * if not currently connected to the headset service.
238      */
isConnected(BluetoothDevice device)239     public boolean isConnected(BluetoothDevice device) {
240         if (DBG) log("isConnected(" + device + ")");
241         if (mService != null) {
242             try {
243                 return mService.isConnected(device);
244             } catch (RemoteException e) {Log.e(TAG, e.toString());}
245         } else {
246             Log.w(TAG, "Proxy not attached to service");
247             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
248         }
249         return false;
250     }
251 
252     /**
253      * Disconnects the current headset. Currently this call blocks, it may soon
254      * be made asynchornous. Returns false if this proxy object is
255      * not currently connected to the Headset service.
256      */
disconnectHeadset()257     public boolean disconnectHeadset() {
258         if (DBG) log("disconnectHeadset()");
259         if (mService != null) {
260             try {
261                 mService.disconnectHeadset();
262                 return true;
263             } catch (RemoteException e) {Log.e(TAG, e.toString());}
264         } else {
265             Log.w(TAG, "Proxy not attached to service");
266             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
267         }
268         return false;
269     }
270 
271     /**
272      * Start BT Voice Recognition mode, and set up Bluetooth audio path.
273      * Returns false if there is no headset connected, or if the
274      * connected headset does not support voice recognition, or on
275      * error.
276      */
startVoiceRecognition()277     public boolean startVoiceRecognition() {
278         if (DBG) log("startVoiceRecognition()");
279         if (mService != null) {
280             try {
281                 return mService.startVoiceRecognition();
282             } catch (RemoteException e) {Log.e(TAG, e.toString());}
283         } else {
284             Log.w(TAG, "Proxy not attached to service");
285             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
286         }
287         return false;
288     }
289 
290     /**
291      * Stop BT Voice Recognition mode, and shut down Bluetooth audio path.
292      * Returns false if there is no headset connected, or the connected
293      * headset is not in voice recognition mode, or on error.
294      */
stopVoiceRecognition()295     public boolean stopVoiceRecognition() {
296         if (DBG) log("stopVoiceRecognition()");
297         if (mService != null) {
298             try {
299                 return mService.stopVoiceRecognition();
300             } catch (RemoteException e) {Log.e(TAG, e.toString());}
301         } else {
302             Log.w(TAG, "Proxy not attached to service");
303             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
304         }
305         return false;
306     }
307 
308     /**
309      * Set priority of headset.
310      * Priority is a non-negative integer. By default paired headsets will have
311      * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0).
312      * Headsets with priority greater than zero will be auto-connected, and
313      * incoming connections will be accepted (if no other headset is
314      * connected).
315      * Auto-connection occurs at the following events: boot, incoming phone
316      * call, outgoing phone call.
317      * Headsets with priority equal to zero, or that are unpaired, are not
318      * auto-connected.
319      * Incoming connections are ignored regardless of priority if there is
320      * already a headset connected.
321      * @param device paired headset
322      * @param priority Integer priority, for example PRIORITY_AUTO or
323      *                 PRIORITY_NONE
324      * @return true if successful, false if there was some error
325      */
setPriority(BluetoothDevice device, int priority)326     public boolean setPriority(BluetoothDevice device, int priority) {
327         if (DBG) log("setPriority(" + device + ", " + priority + ")");
328         if (mService != null) {
329             try {
330                 return mService.setPriority(device, priority);
331             } catch (RemoteException e) {Log.e(TAG, e.toString());}
332         } else {
333             Log.w(TAG, "Proxy not attached to service");
334             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
335         }
336         return false;
337     }
338 
339     /**
340      * Get priority of headset.
341      * @param device headset
342      * @return non-negative priority, or negative error code on error
343      */
getPriority(BluetoothDevice device)344     public int getPriority(BluetoothDevice device) {
345         if (DBG) log("getPriority(" + device + ")");
346         if (mService != null) {
347             try {
348                 return mService.getPriority(device);
349             } catch (RemoteException e) {Log.e(TAG, e.toString());}
350         } else {
351             Log.w(TAG, "Proxy not attached to service");
352             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
353         }
354         return -1;
355     }
356 
357     /**
358      * Get battery usage hint for Bluetooth Headset service.
359      * This is a monotonically increasing integer. Wraps to 0 at
360      * Integer.MAX_INT, and at boot.
361      * Current implementation returns the number of AT commands handled since
362      * boot. This is a good indicator for spammy headset/handsfree units that
363      * can keep the device awake by polling for cellular status updates. As a
364      * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
365      * @return monotonically increasing battery usage hint, or a negative error
366      *         code on error
367      * @hide
368      */
getBatteryUsageHint()369     public int getBatteryUsageHint() {
370         if (DBG) log("getBatteryUsageHint()");
371         if (mService != null) {
372             try {
373                 return mService.getBatteryUsageHint();
374             } catch (RemoteException e) {Log.e(TAG, e.toString());}
375         } else {
376             Log.w(TAG, "Proxy not attached to service");
377             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
378         }
379         return -1;
380     }
381 
382     private ServiceConnection mConnection = new ServiceConnection() {
383         public void onServiceConnected(ComponentName className, IBinder service) {
384             if (DBG) Log.d(TAG, "Proxy object connected");
385             mService = IBluetoothHeadset.Stub.asInterface(service);
386             if (mServiceListener != null) {
387                 mServiceListener.onServiceConnected();
388             }
389         }
390         public void onServiceDisconnected(ComponentName className) {
391             if (DBG) Log.d(TAG, "Proxy object disconnected");
392             mService = null;
393             if (mServiceListener != null) {
394                 mServiceListener.onServiceDisconnected();
395             }
396         }
397     };
398 
log(String msg)399     private static void log(String msg) {
400         Log.d(TAG, msg);
401     }
402 }
403