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