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