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