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 17 package com.android.bluetooth.hfp; 18 19 import android.bluetooth.BluetoothDevice; 20 import android.bluetooth.BluetoothHeadset; 21 import android.bluetooth.BluetoothProfile; 22 import android.bluetooth.IBluetoothHeadset; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.pm.PackageManager; 28 import android.media.AudioManager; 29 import android.os.Handler; 30 import android.os.Message; 31 import android.provider.Settings; 32 import android.util.Log; 33 import com.android.bluetooth.btservice.ProfileService; 34 import com.android.bluetooth.Utils; 35 import java.util.ArrayList; 36 import java.util.List; 37 import java.util.Iterator; 38 import java.util.Map; 39 40 /** 41 * Provides Bluetooth Headset and Handsfree profile, as a service in 42 * the Bluetooth application. 43 * @hide 44 */ 45 public 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 int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 108 BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); 109 if (requestType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) { 110 Log.v(TAG, "Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY"); 111 mStateMachine.handleAccessPermissionResult(intent); 112 } 113 } 114 } 115 }; 116 117 /** 118 * Handlers for incoming service calls 119 */ 120 private static class BluetoothHeadsetBinder extends IBluetoothHeadset.Stub implements IProfileServiceBinder { 121 private HeadsetService mService; 122 123 public BluetoothHeadsetBinder(HeadsetService svc) { 124 mService = svc; 125 } 126 public boolean cleanup() { 127 mService = null; 128 return true; 129 } 130 131 private HeadsetService getService() { 132 if (!Utils.checkCallerAllowManagedProfiles(mService)) { 133 Log.w(TAG,"Headset call not allowed for non-active user"); 134 return null; 135 } 136 137 if (mService != null && mService.isAvailable()) { 138 return mService; 139 } 140 return null; 141 } 142 143 public boolean connect(BluetoothDevice device) { 144 HeadsetService service = getService(); 145 if (service == null) return false; 146 return service.connect(device); 147 } 148 149 public boolean disconnect(BluetoothDevice device) { 150 HeadsetService service = getService(); 151 if (service == null) return false; 152 if (DBG) Log.d(TAG, "disconnect in HeadsetService"); 153 return service.disconnect(device); 154 } 155 156 public List<BluetoothDevice> getConnectedDevices() { 157 HeadsetService service = getService(); 158 if (service == null) return new ArrayList<BluetoothDevice>(0); 159 return service.getConnectedDevices(); 160 } 161 162 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 163 HeadsetService service = getService(); 164 if (service == null) return new ArrayList<BluetoothDevice>(0); 165 return service.getDevicesMatchingConnectionStates(states); 166 } 167 168 public int getConnectionState(BluetoothDevice device) { 169 HeadsetService service = getService(); 170 if (service == null) return BluetoothProfile.STATE_DISCONNECTED; 171 return service.getConnectionState(device); 172 } 173 174 public boolean setPriority(BluetoothDevice device, int priority) { 175 HeadsetService service = getService(); 176 if (service == null) return false; 177 return service.setPriority(device, priority); 178 } 179 180 public int getPriority(BluetoothDevice device) { 181 HeadsetService service = getService(); 182 if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED; 183 return service.getPriority(device); 184 } 185 186 public boolean startVoiceRecognition(BluetoothDevice device) { 187 HeadsetService service = getService(); 188 if (service == null) return false; 189 return service.startVoiceRecognition(device); 190 } 191 192 public boolean stopVoiceRecognition(BluetoothDevice device) { 193 HeadsetService service = getService(); 194 if (service == null) return false; 195 return service.stopVoiceRecognition(device); 196 } 197 198 public boolean isAudioOn() { 199 HeadsetService service = getService(); 200 if (service == null) return false; 201 return service.isAudioOn(); 202 } 203 204 public boolean isAudioConnected(BluetoothDevice device) { 205 HeadsetService service = getService(); 206 if (service == null) return false; 207 return service.isAudioConnected(device); 208 } 209 210 public int getBatteryUsageHint(BluetoothDevice device) { 211 HeadsetService service = getService(); 212 if (service == null) return 0; 213 return service.getBatteryUsageHint(device); 214 } 215 216 public boolean acceptIncomingConnect(BluetoothDevice device) { 217 HeadsetService service = getService(); 218 if (service == null) return false; 219 return service.acceptIncomingConnect(device); 220 } 221 222 public boolean rejectIncomingConnect(BluetoothDevice device) { 223 HeadsetService service = getService(); 224 if (service == null) return false; 225 return service.rejectIncomingConnect(device); 226 } 227 228 public int getAudioState(BluetoothDevice device) { 229 HeadsetService service = getService(); 230 if (service == null) return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 231 return service.getAudioState(device); 232 } 233 234 public boolean connectAudio() { 235 HeadsetService service = getService(); 236 if (service == null) return false; 237 return service.connectAudio(); 238 } 239 240 public boolean disconnectAudio() { 241 HeadsetService service = getService(); 242 if (service == null) return false; 243 return service.disconnectAudio(); 244 } 245 246 public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { 247 HeadsetService service = getService(); 248 if (service == null) return false; 249 return service.startScoUsingVirtualVoiceCall(device); 250 } 251 252 public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { 253 HeadsetService service = getService(); 254 if (service == null) return false; 255 return service.stopScoUsingVirtualVoiceCall(device); 256 } 257 258 public void phoneStateChanged(int numActive, int numHeld, int callState, 259 String number, int type) { 260 HeadsetService service = getService(); 261 if (service == null) return; 262 service.phoneStateChanged(numActive, numHeld, callState, number, type); 263 } 264 265 public void clccResponse(int index, int direction, int status, int mode, boolean mpty, 266 String number, int type) { 267 HeadsetService service = getService(); 268 if (service == null) return; 269 service.clccResponse(index, direction, status, mode, mpty, number, type); 270 } 271 272 public boolean sendVendorSpecificResultCode(BluetoothDevice device, 273 String command, 274 String arg) { 275 HeadsetService service = getService(); 276 if (service == null) { 277 return false; 278 } 279 return service.sendVendorSpecificResultCode(device, command, arg); 280 } 281 282 public boolean enableWBS() { 283 HeadsetService service = getService(); 284 if (service == null) return false; 285 return service.enableWBS(); 286 } 287 288 public boolean disableWBS() { 289 HeadsetService service = getService(); 290 if (service == null) return false; 291 return service.disableWBS(); 292 } 293 }; 294 295 //API methods 296 public static synchronized HeadsetService getHeadsetService(){ 297 if (sHeadsetService != null && sHeadsetService.isAvailable()) { 298 if (DBG) Log.d(TAG, "getHeadsetService(): returning " + sHeadsetService); 299 return sHeadsetService; 300 } 301 if (DBG) { 302 if (sHeadsetService == null) { 303 Log.d(TAG, "getHeadsetService(): service is NULL"); 304 } else if (!(sHeadsetService.isAvailable())) { 305 Log.d(TAG,"getHeadsetService(): service is not available"); 306 } 307 } 308 return null; 309 } 310 311 private static synchronized void setHeadsetService(HeadsetService instance) { 312 if (instance != null && instance.isAvailable()) { 313 if (DBG) Log.d(TAG, "setHeadsetService(): set to: " + sHeadsetService); 314 sHeadsetService = instance; 315 } else { 316 if (DBG) { 317 if (sHeadsetService == null) { 318 Log.d(TAG, "setHeadsetService(): service not available"); 319 } else if (!sHeadsetService.isAvailable()) { 320 Log.d(TAG,"setHeadsetService(): service is cleaning up"); 321 } 322 } 323 } 324 } 325 326 private static synchronized void clearHeadsetService() { 327 sHeadsetService = null; 328 } 329 330 public boolean connect(BluetoothDevice device) { 331 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 332 "Need BLUETOOTH ADMIN permission"); 333 334 if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) { 335 return false; 336 } 337 338 int connectionState = mStateMachine.getConnectionState(device); 339 Log.d(TAG,"connectionState = " + connectionState); 340 if (connectionState == BluetoothProfile.STATE_CONNECTED || 341 connectionState == BluetoothProfile.STATE_CONNECTING) { 342 return false; 343 } 344 345 mStateMachine.sendMessage(HeadsetStateMachine.CONNECT, device); 346 return true; 347 } 348 349 boolean disconnect(BluetoothDevice device) { 350 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 351 "Need BLUETOOTH ADMIN permission"); 352 int connectionState = mStateMachine.getConnectionState(device); 353 if (connectionState != BluetoothProfile.STATE_CONNECTED && 354 connectionState != BluetoothProfile.STATE_CONNECTING) { 355 return false; 356 } 357 358 mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT, device); 359 return true; 360 } 361 362 public List<BluetoothDevice> getConnectedDevices() { 363 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 364 return mStateMachine.getConnectedDevices(); 365 } 366 367 private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 368 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 369 return mStateMachine.getDevicesMatchingConnectionStates(states); 370 } 371 372 int getConnectionState(BluetoothDevice device) { 373 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 374 return mStateMachine.getConnectionState(device); 375 } 376 377 public boolean setPriority(BluetoothDevice device, int priority) { 378 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 379 "Need BLUETOOTH_ADMIN permission"); 380 Settings.Global.putInt(getContentResolver(), 381 Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), 382 priority); 383 if (DBG) Log.d(TAG, "Saved priority " + device + " = " + priority); 384 return true; 385 } 386 387 public int getPriority(BluetoothDevice device) { 388 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 389 "Need BLUETOOTH_ADMIN permission"); 390 int priority = Settings.Global.getInt(getContentResolver(), 391 Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), 392 BluetoothProfile.PRIORITY_UNDEFINED); 393 return priority; 394 } 395 396 boolean startVoiceRecognition(BluetoothDevice device) { 397 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 398 int connectionState = mStateMachine.getConnectionState(device); 399 if (connectionState != BluetoothProfile.STATE_CONNECTED && 400 connectionState != BluetoothProfile.STATE_CONNECTING) { 401 return false; 402 } 403 mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START); 404 return true; 405 } 406 407 boolean stopVoiceRecognition(BluetoothDevice device) { 408 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 409 // It seem that we really need to check the AudioOn state. 410 // But since we allow startVoiceRecognition in STATE_CONNECTED and 411 // STATE_CONNECTING state, we do these 2 in this method 412 int connectionState = mStateMachine.getConnectionState(device); 413 if (connectionState != BluetoothProfile.STATE_CONNECTED && 414 connectionState != BluetoothProfile.STATE_CONNECTING) { 415 return false; 416 } 417 mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP); 418 // TODO is this return correct when the voice recognition is not on? 419 return true; 420 } 421 422 boolean isAudioOn() { 423 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 424 return mStateMachine.isAudioOn(); 425 } 426 427 boolean isAudioConnected(BluetoothDevice device) { 428 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 429 return mStateMachine.isAudioConnected(device); 430 } 431 432 int getBatteryUsageHint(BluetoothDevice device) { 433 // TODO(BT) ask for BT stack support? 434 return 0; 435 } 436 437 boolean acceptIncomingConnect(BluetoothDevice device) { 438 // TODO(BT) remove it if stack does access control 439 return false; 440 } 441 442 boolean rejectIncomingConnect(BluetoothDevice device) { 443 // TODO(BT) remove it if stack does access control 444 return false; 445 } 446 447 int getAudioState(BluetoothDevice device) { 448 return mStateMachine.getAudioState(device); 449 } 450 451 boolean connectAudio() { 452 // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission 453 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 454 if (!mStateMachine.isConnected()) { 455 return false; 456 } 457 if (mStateMachine.isAudioOn()) { 458 return false; 459 } 460 mStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO); 461 return true; 462 } 463 464 boolean disconnectAudio() { 465 // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission 466 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 467 if (!mStateMachine.isAudioOn()) { 468 return false; 469 } 470 mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO); 471 return true; 472 } 473 474 boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { 475 int connectionState = mStateMachine.getConnectionState(device); 476 if (connectionState != BluetoothProfile.STATE_CONNECTED && 477 connectionState != BluetoothProfile.STATE_CONNECTING) { 478 return false; 479 } 480 mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_START, device); 481 return true; 482 } 483 484 boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { 485 int connectionState = mStateMachine.getConnectionState(device); 486 if (connectionState != BluetoothProfile.STATE_CONNECTED && 487 connectionState != BluetoothProfile.STATE_CONNECTING) { 488 return false; 489 } 490 mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_STOP, device); 491 return true; 492 } 493 494 private void phoneStateChanged(int numActive, int numHeld, int callState, 495 String number, int type) { 496 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null); 497 Message msg = mStateMachine.obtainMessage(HeadsetStateMachine.CALL_STATE_CHANGED); 498 msg.obj = new HeadsetCallState(numActive, numHeld, callState, number, type); 499 msg.arg1 = 0; // false 500 mStateMachine.sendMessage(msg); 501 } 502 503 private void clccResponse(int index, int direction, int status, int mode, boolean mpty, 504 String number, int type) { 505 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null); 506 mStateMachine.sendMessage(HeadsetStateMachine.SEND_CCLC_RESPONSE, 507 new HeadsetClccResponse(index, direction, status, mode, mpty, number, type)); 508 } 509 510 private boolean sendVendorSpecificResultCode(BluetoothDevice device, 511 String command, 512 String arg) { 513 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 514 int connectionState = mStateMachine.getConnectionState(device); 515 if (connectionState != BluetoothProfile.STATE_CONNECTED) { 516 return false; 517 } 518 // Currently we support only "+ANDROID". 519 if (!command.equals(BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID)) { 520 Log.w(TAG, "Disallowed unsolicited result code command: " + command); 521 return false; 522 } 523 mStateMachine.sendMessage(HeadsetStateMachine.SEND_VENDOR_SPECIFIC_RESULT_CODE, 524 new HeadsetVendorSpecificResultCode(device, command, arg)); 525 return true; 526 } 527 528 boolean enableWBS() { 529 // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission 530 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 531 if (!mStateMachine.isConnected()) { 532 return false; 533 } 534 if (mStateMachine.isAudioOn()) { 535 return false; 536 } 537 538 for (BluetoothDevice device: getConnectedDevices()) { 539 mStateMachine.sendMessage(HeadsetStateMachine.ENABLE_WBS,device); 540 } 541 542 return true; 543 } 544 545 boolean disableWBS() { 546 // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission 547 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 548 if (!mStateMachine.isConnected()) { 549 return false; 550 } 551 if (mStateMachine.isAudioOn()) { 552 return false; 553 } 554 for (BluetoothDevice device: getConnectedDevices()) { 555 mStateMachine.sendMessage(HeadsetStateMachine.DISABLE_WBS,device); 556 } 557 return true; 558 } 559 560 @Override 561 public void dump(StringBuilder sb) { 562 super.dump(sb); 563 if (mStateMachine != null) { 564 mStateMachine.dump(sb); 565 } 566 } 567 } 568