HeadsetService.java revision cdc8ec8d84c7f16b851238da0d80b51335c4ea07
1/* 2 * Copyright (C) 2012 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 com.android.bluetooth.hfp; 18 19import android.bluetooth.BluetoothDevice; 20import android.bluetooth.BluetoothHeadset; 21import android.bluetooth.BluetoothProfile; 22import android.bluetooth.IBluetoothHeadset; 23import android.content.BroadcastReceiver; 24import android.content.Context; 25import android.content.Intent; 26import android.content.IntentFilter; 27import android.content.pm.PackageManager; 28import android.media.AudioManager; 29import android.os.Handler; 30import android.os.Message; 31import android.provider.Settings; 32import android.util.Log; 33import com.android.bluetooth.btservice.ProfileService; 34import com.android.bluetooth.Utils; 35import java.util.ArrayList; 36import java.util.List; 37import java.util.Iterator; 38import java.util.Map; 39 40/** 41 * Provides Bluetooth Headset and Handsfree profile, as a service in 42 * the Bluetooth application. 43 * @hide 44 */ 45public class HeadsetService extends ProfileService { 46 private static final boolean DBG = false; 47 private static final String TAG = "HeadsetService"; 48 private static final String MODIFY_PHONE_STATE = android.Manifest.permission.MODIFY_PHONE_STATE; 49 50 private HeadsetStateMachine mStateMachine; 51 private static HeadsetService sHeadsetService; 52 53 protected String getName() { 54 return TAG; 55 } 56 57 public IProfileServiceBinder initBinder() { 58 return new BluetoothHeadsetBinder(this); 59 } 60 61 protected boolean start() { 62 mStateMachine = HeadsetStateMachine.make(this); 63 IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); 64 filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); 65 filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); 66 try { 67 registerReceiver(mHeadsetReceiver, filter); 68 } catch (Exception e) { 69 Log.w(TAG,"Unable to register headset receiver",e); 70 } 71 setHeadsetService(this); 72 return true; 73 } 74 75 protected boolean stop() { 76 try { 77 unregisterReceiver(mHeadsetReceiver); 78 } catch (Exception e) { 79 Log.w(TAG,"Unable to unregister headset receiver",e); 80 } 81 mStateMachine.doQuit(); 82 return true; 83 } 84 85 protected boolean cleanup() { 86 if (mStateMachine != null) { 87 mStateMachine.cleanup(); 88 } 89 clearHeadsetService(); 90 return true; 91 } 92 93 private final BroadcastReceiver mHeadsetReceiver = new BroadcastReceiver() { 94 @Override 95 public void onReceive(Context context, Intent intent) { 96 String action = intent.getAction(); 97 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { 98 mStateMachine.sendMessage(HeadsetStateMachine.INTENT_BATTERY_CHANGED, intent); 99 } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) { 100 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 101 if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) { 102 mStateMachine.sendMessage(HeadsetStateMachine.INTENT_SCO_VOLUME_CHANGED, 103 intent); 104 } 105 } 106 else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { 107 Log.v(TAG, "HeadsetService - Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY"); 108 mStateMachine.handleAccessPermissionResult(intent); 109 } 110 } 111 }; 112 113 /** 114 * Handlers for incoming service calls 115 */ 116 private static class BluetoothHeadsetBinder extends IBluetoothHeadset.Stub implements IProfileServiceBinder { 117 private HeadsetService mService; 118 119 public BluetoothHeadsetBinder(HeadsetService svc) { 120 mService = svc; 121 } 122 public boolean cleanup() { 123 mService = null; 124 return true; 125 } 126 127 private HeadsetService getService() { 128 if (!Utils.checkCaller()) { 129 Log.w(TAG,"Headset call not allowed for non-active user"); 130 return null; 131 } 132 133 if (mService != null && mService.isAvailable()) { 134 return mService; 135 } 136 return null; 137 } 138 139 public boolean connect(BluetoothDevice device) { 140 HeadsetService service = getService(); 141 if (service == null) return false; 142 return service.connect(device); 143 } 144 145 public boolean disconnect(BluetoothDevice device) { 146 HeadsetService service = getService(); 147 if (service == null) return false; 148 return service.disconnect(device); 149 } 150 151 public List<BluetoothDevice> getConnectedDevices() { 152 HeadsetService service = getService(); 153 if (service == null) return new ArrayList<BluetoothDevice>(0); 154 return service.getConnectedDevices(); 155 } 156 157 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 158 HeadsetService service = getService(); 159 if (service == null) return new ArrayList<BluetoothDevice>(0); 160 return service.getDevicesMatchingConnectionStates(states); 161 } 162 163 public int getConnectionState(BluetoothDevice device) { 164 HeadsetService service = getService(); 165 if (service == null) return BluetoothProfile.STATE_DISCONNECTED; 166 return service.getConnectionState(device); 167 } 168 169 public boolean setPriority(BluetoothDevice device, int priority) { 170 HeadsetService service = getService(); 171 if (service == null) return false; 172 return service.setPriority(device, priority); 173 } 174 175 public int getPriority(BluetoothDevice device) { 176 HeadsetService service = getService(); 177 if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED; 178 return service.getPriority(device); 179 } 180 181 public boolean startVoiceRecognition(BluetoothDevice device) { 182 HeadsetService service = getService(); 183 if (service == null) return false; 184 return service.startVoiceRecognition(device); 185 } 186 187 public boolean stopVoiceRecognition(BluetoothDevice device) { 188 HeadsetService service = getService(); 189 if (service == null) return false; 190 return service.stopVoiceRecognition(device); 191 } 192 193 public boolean isAudioOn() { 194 HeadsetService service = getService(); 195 if (service == null) return false; 196 return service.isAudioOn(); 197 } 198 199 public boolean isAudioConnected(BluetoothDevice device) { 200 HeadsetService service = getService(); 201 if (service == null) return false; 202 return service.isAudioConnected(device); 203 } 204 205 public int getBatteryUsageHint(BluetoothDevice device) { 206 HeadsetService service = getService(); 207 if (service == null) return 0; 208 return service.getBatteryUsageHint(device); 209 } 210 211 public boolean acceptIncomingConnect(BluetoothDevice device) { 212 HeadsetService service = getService(); 213 if (service == null) return false; 214 return service.acceptIncomingConnect(device); 215 } 216 217 public boolean rejectIncomingConnect(BluetoothDevice device) { 218 HeadsetService service = getService(); 219 if (service == null) return false; 220 return service.rejectIncomingConnect(device); 221 } 222 223 public int getAudioState(BluetoothDevice device) { 224 HeadsetService service = getService(); 225 if (service == null) return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 226 return service.getAudioState(device); 227 } 228 229 public boolean connectAudio() { 230 HeadsetService service = getService(); 231 if (service == null) return false; 232 return service.connectAudio(); 233 } 234 235 public boolean disconnectAudio() { 236 HeadsetService service = getService(); 237 if (service == null) return false; 238 return service.disconnectAudio(); 239 } 240 241 public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { 242 HeadsetService service = getService(); 243 if (service == null) return false; 244 return service.startScoUsingVirtualVoiceCall(device); 245 } 246 247 public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { 248 HeadsetService service = getService(); 249 if (service == null) return false; 250 return service.stopScoUsingVirtualVoiceCall(device); 251 } 252 253 public void phoneStateChanged(int numActive, int numHeld, int callState, 254 String number, int type) { 255 HeadsetService service = getService(); 256 if (service == null) return; 257 service.phoneStateChanged(numActive, numHeld, callState, number, type); 258 } 259 260 public void clccResponse(int index, int direction, int status, int mode, boolean mpty, 261 String number, int type) { 262 HeadsetService service = getService(); 263 if (service == null) return; 264 service.clccResponse(index, direction, status, mode, mpty, number, type); 265 } 266 267 public boolean sendVendorSpecificResultCode(BluetoothDevice device, 268 String command, 269 String arg) { 270 HeadsetService service = getService(); 271 if (service == null) { 272 return false; 273 } 274 return service.sendVendorSpecificResultCode(device, command, arg); 275 } 276 }; 277 278 //API methods 279 public static synchronized HeadsetService getHeadsetService(){ 280 if (sHeadsetService != null && sHeadsetService.isAvailable()) { 281 if (DBG) Log.d(TAG, "getHeadsetService(): returning " + sHeadsetService); 282 return sHeadsetService; 283 } 284 if (DBG) { 285 if (sHeadsetService == null) { 286 Log.d(TAG, "getHeadsetService(): service is NULL"); 287 } else if (!(sHeadsetService.isAvailable())) { 288 Log.d(TAG,"getHeadsetService(): service is not available"); 289 } 290 } 291 return null; 292 } 293 294 private static synchronized void setHeadsetService(HeadsetService instance) { 295 if (instance != null && instance.isAvailable()) { 296 if (DBG) Log.d(TAG, "setHeadsetService(): set to: " + sHeadsetService); 297 sHeadsetService = instance; 298 } else { 299 if (DBG) { 300 if (sHeadsetService == null) { 301 Log.d(TAG, "setHeadsetService(): service not available"); 302 } else if (!sHeadsetService.isAvailable()) { 303 Log.d(TAG,"setHeadsetService(): service is cleaning up"); 304 } 305 } 306 } 307 } 308 309 private static synchronized void clearHeadsetService() { 310 sHeadsetService = null; 311 } 312 313 public boolean connect(BluetoothDevice device) { 314 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 315 "Need BLUETOOTH ADMIN permission"); 316 317 if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) { 318 return false; 319 } 320 321 int connectionState = mStateMachine.getConnectionState(device); 322 if (connectionState == BluetoothProfile.STATE_CONNECTED || 323 connectionState == BluetoothProfile.STATE_CONNECTING) { 324 return false; 325 } 326 327 mStateMachine.sendMessage(HeadsetStateMachine.CONNECT, device); 328 return true; 329 } 330 331 boolean disconnect(BluetoothDevice device) { 332 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 333 "Need BLUETOOTH ADMIN permission"); 334 int connectionState = mStateMachine.getConnectionState(device); 335 if (connectionState != BluetoothProfile.STATE_CONNECTED && 336 connectionState != BluetoothProfile.STATE_CONNECTING) { 337 return false; 338 } 339 340 mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT, device); 341 return true; 342 } 343 344 public List<BluetoothDevice> getConnectedDevices() { 345 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 346 return mStateMachine.getConnectedDevices(); 347 } 348 349 private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 350 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 351 return mStateMachine.getDevicesMatchingConnectionStates(states); 352 } 353 354 int getConnectionState(BluetoothDevice device) { 355 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 356 return mStateMachine.getConnectionState(device); 357 } 358 359 public boolean setPriority(BluetoothDevice device, int priority) { 360 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 361 "Need BLUETOOTH_ADMIN permission"); 362 Settings.Global.putInt(getContentResolver(), 363 Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), 364 priority); 365 if (DBG) Log.d(TAG, "Saved priority " + device + " = " + priority); 366 return true; 367 } 368 369 public int getPriority(BluetoothDevice device) { 370 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 371 "Need BLUETOOTH_ADMIN permission"); 372 int priority = Settings.Global.getInt(getContentResolver(), 373 Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), 374 BluetoothProfile.PRIORITY_UNDEFINED); 375 return priority; 376 } 377 378 boolean startVoiceRecognition(BluetoothDevice device) { 379 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 380 int connectionState = mStateMachine.getConnectionState(device); 381 if (connectionState != BluetoothProfile.STATE_CONNECTED && 382 connectionState != BluetoothProfile.STATE_CONNECTING) { 383 return false; 384 } 385 mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START); 386 return true; 387 } 388 389 boolean stopVoiceRecognition(BluetoothDevice device) { 390 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 391 // It seem that we really need to check the AudioOn state. 392 // But since we allow startVoiceRecognition in STATE_CONNECTED and 393 // STATE_CONNECTING state, we do these 2 in this method 394 int connectionState = mStateMachine.getConnectionState(device); 395 if (connectionState != BluetoothProfile.STATE_CONNECTED && 396 connectionState != BluetoothProfile.STATE_CONNECTING) { 397 return false; 398 } 399 mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP); 400 // TODO is this return correct when the voice recognition is not on? 401 return true; 402 } 403 404 boolean isAudioOn() { 405 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 406 return mStateMachine.isAudioOn(); 407 } 408 409 boolean isAudioConnected(BluetoothDevice device) { 410 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 411 return mStateMachine.isAudioConnected(device); 412 } 413 414 int getBatteryUsageHint(BluetoothDevice device) { 415 // TODO(BT) ask for BT stack support? 416 return 0; 417 } 418 419 boolean acceptIncomingConnect(BluetoothDevice device) { 420 // TODO(BT) remove it if stack does access control 421 return false; 422 } 423 424 boolean rejectIncomingConnect(BluetoothDevice device) { 425 // TODO(BT) remove it if stack does access control 426 return false; 427 } 428 429 int getAudioState(BluetoothDevice device) { 430 return mStateMachine.getAudioState(device); 431 } 432 433 boolean connectAudio() { 434 // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission 435 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 436 if (!mStateMachine.isConnected()) { 437 return false; 438 } 439 if (mStateMachine.isAudioOn()) { 440 return false; 441 } 442 mStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO); 443 return true; 444 } 445 446 boolean disconnectAudio() { 447 // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission 448 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 449 if (!mStateMachine.isAudioOn()) { 450 return false; 451 } 452 mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO); 453 return true; 454 } 455 456 boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { 457 int connectionState = mStateMachine.getConnectionState(device); 458 if (connectionState != BluetoothProfile.STATE_CONNECTED && 459 connectionState != BluetoothProfile.STATE_CONNECTING) { 460 return false; 461 } 462 mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_START, device); 463 return true; 464 } 465 466 boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { 467 int connectionState = mStateMachine.getConnectionState(device); 468 if (connectionState != BluetoothProfile.STATE_CONNECTED && 469 connectionState != BluetoothProfile.STATE_CONNECTING) { 470 return false; 471 } 472 mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_STOP, device); 473 return true; 474 } 475 476 private void phoneStateChanged(int numActive, int numHeld, int callState, 477 String number, int type) { 478 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null); 479 Message msg = mStateMachine.obtainMessage(HeadsetStateMachine.CALL_STATE_CHANGED); 480 msg.obj = new HeadsetCallState(numActive, numHeld, callState, number, type); 481 msg.arg1 = 0; // false 482 mStateMachine.sendMessage(msg); 483 } 484 485 private void clccResponse(int index, int direction, int status, int mode, boolean mpty, 486 String number, int type) { 487 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null); 488 mStateMachine.sendMessage(HeadsetStateMachine.SEND_CCLC_RESPONSE, 489 new HeadsetClccResponse(index, direction, status, mode, mpty, number, type)); 490 } 491 492 private boolean sendVendorSpecificResultCode(BluetoothDevice device, 493 String command, 494 String arg) { 495 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 496 int connectionState = mStateMachine.getConnectionState(device); 497 if (connectionState != BluetoothProfile.STATE_CONNECTED) { 498 return false; 499 } 500 // Currently we support only "+ANDROID". 501 if (!command.equals(BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID)) { 502 Log.w(TAG, "Disallowed unsolicited result code command: " + command); 503 return false; 504 } 505 mStateMachine.sendMessage(HeadsetStateMachine.SEND_VENDOR_SPECIFIC_RESULT_CODE, 506 new HeadsetVendorSpecificResultCode(command, arg)); 507 return true; 508 } 509 510} 511