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.checkCaller()) { 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 return service.disconnect(device); 153 } 154 155 public List<BluetoothDevice> getConnectedDevices() { 156 HeadsetService service = getService(); 157 if (service == null) return new ArrayList<BluetoothDevice>(0); 158 return service.getConnectedDevices(); 159 } 160 161 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 162 HeadsetService service = getService(); 163 if (service == null) return new ArrayList<BluetoothDevice>(0); 164 return service.getDevicesMatchingConnectionStates(states); 165 } 166 167 public int getConnectionState(BluetoothDevice device) { 168 HeadsetService service = getService(); 169 if (service == null) return BluetoothProfile.STATE_DISCONNECTED; 170 return service.getConnectionState(device); 171 } 172 173 public boolean setPriority(BluetoothDevice device, int priority) { 174 HeadsetService service = getService(); 175 if (service == null) return false; 176 return service.setPriority(device, priority); 177 } 178 179 public int getPriority(BluetoothDevice device) { 180 HeadsetService service = getService(); 181 if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED; 182 return service.getPriority(device); 183 } 184 185 public boolean startVoiceRecognition(BluetoothDevice device) { 186 HeadsetService service = getService(); 187 if (service == null) return false; 188 return service.startVoiceRecognition(device); 189 } 190 191 public boolean stopVoiceRecognition(BluetoothDevice device) { 192 HeadsetService service = getService(); 193 if (service == null) return false; 194 return service.stopVoiceRecognition(device); 195 } 196 197 public boolean isAudioOn() { 198 HeadsetService service = getService(); 199 if (service == null) return false; 200 return service.isAudioOn(); 201 } 202 203 public boolean isAudioConnected(BluetoothDevice device) { 204 HeadsetService service = getService(); 205 if (service == null) return false; 206 return service.isAudioConnected(device); 207 } 208 209 public int getBatteryUsageHint(BluetoothDevice device) { 210 HeadsetService service = getService(); 211 if (service == null) return 0; 212 return service.getBatteryUsageHint(device); 213 } 214 215 public boolean acceptIncomingConnect(BluetoothDevice device) { 216 HeadsetService service = getService(); 217 if (service == null) return false; 218 return service.acceptIncomingConnect(device); 219 } 220 221 public boolean rejectIncomingConnect(BluetoothDevice device) { 222 HeadsetService service = getService(); 223 if (service == null) return false; 224 return service.rejectIncomingConnect(device); 225 } 226 227 public int getAudioState(BluetoothDevice device) { 228 HeadsetService service = getService(); 229 if (service == null) return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 230 return service.getAudioState(device); 231 } 232 233 public boolean connectAudio() { 234 HeadsetService service = getService(); 235 if (service == null) return false; 236 return service.connectAudio(); 237 } 238 239 public boolean disconnectAudio() { 240 HeadsetService service = getService(); 241 if (service == null) return false; 242 return service.disconnectAudio(); 243 } 244 245 public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { 246 HeadsetService service = getService(); 247 if (service == null) return false; 248 return service.startScoUsingVirtualVoiceCall(device); 249 } 250 251 public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { 252 HeadsetService service = getService(); 253 if (service == null) return false; 254 return service.stopScoUsingVirtualVoiceCall(device); 255 } 256 257 public void phoneStateChanged(int numActive, int numHeld, int callState, 258 String number, int type) { 259 HeadsetService service = getService(); 260 if (service == null) return; 261 service.phoneStateChanged(numActive, numHeld, callState, number, type); 262 } 263 264 public void clccResponse(int index, int direction, int status, int mode, boolean mpty, 265 String number, int type) { 266 HeadsetService service = getService(); 267 if (service == null) return; 268 service.clccResponse(index, direction, status, mode, mpty, number, type); 269 } 270 271 public boolean sendVendorSpecificResultCode(BluetoothDevice device, 272 String command, 273 String arg) { 274 HeadsetService service = getService(); 275 if (service == null) { 276 return false; 277 } 278 return service.sendVendorSpecificResultCode(device, command, arg); 279 } 280 }; 281 282 //API methods 283 public static synchronized HeadsetService getHeadsetService(){ 284 if (sHeadsetService != null && sHeadsetService.isAvailable()) { 285 if (DBG) Log.d(TAG, "getHeadsetService(): returning " + sHeadsetService); 286 return sHeadsetService; 287 } 288 if (DBG) { 289 if (sHeadsetService == null) { 290 Log.d(TAG, "getHeadsetService(): service is NULL"); 291 } else if (!(sHeadsetService.isAvailable())) { 292 Log.d(TAG,"getHeadsetService(): service is not available"); 293 } 294 } 295 return null; 296 } 297 298 private static synchronized void setHeadsetService(HeadsetService instance) { 299 if (instance != null && instance.isAvailable()) { 300 if (DBG) Log.d(TAG, "setHeadsetService(): set to: " + sHeadsetService); 301 sHeadsetService = instance; 302 } else { 303 if (DBG) { 304 if (sHeadsetService == null) { 305 Log.d(TAG, "setHeadsetService(): service not available"); 306 } else if (!sHeadsetService.isAvailable()) { 307 Log.d(TAG,"setHeadsetService(): service is cleaning up"); 308 } 309 } 310 } 311 } 312 313 private static synchronized void clearHeadsetService() { 314 sHeadsetService = null; 315 } 316 317 public boolean connect(BluetoothDevice device) { 318 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 319 "Need BLUETOOTH ADMIN permission"); 320 321 if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) { 322 return false; 323 } 324 325 int connectionState = mStateMachine.getConnectionState(device); 326 if (connectionState == BluetoothProfile.STATE_CONNECTED || 327 connectionState == BluetoothProfile.STATE_CONNECTING) { 328 return false; 329 } 330 331 mStateMachine.sendMessage(HeadsetStateMachine.CONNECT, device); 332 return true; 333 } 334 335 boolean disconnect(BluetoothDevice device) { 336 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 337 "Need BLUETOOTH ADMIN permission"); 338 int connectionState = mStateMachine.getConnectionState(device); 339 if (connectionState != BluetoothProfile.STATE_CONNECTED && 340 connectionState != BluetoothProfile.STATE_CONNECTING) { 341 return false; 342 } 343 344 mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT, device); 345 return true; 346 } 347 348 public List<BluetoothDevice> getConnectedDevices() { 349 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 350 return mStateMachine.getConnectedDevices(); 351 } 352 353 private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 354 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 355 return mStateMachine.getDevicesMatchingConnectionStates(states); 356 } 357 358 int getConnectionState(BluetoothDevice device) { 359 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 360 return mStateMachine.getConnectionState(device); 361 } 362 363 public boolean setPriority(BluetoothDevice device, int priority) { 364 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 365 "Need BLUETOOTH_ADMIN permission"); 366 Settings.Global.putInt(getContentResolver(), 367 Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), 368 priority); 369 if (DBG) Log.d(TAG, "Saved priority " + device + " = " + priority); 370 return true; 371 } 372 373 public int getPriority(BluetoothDevice device) { 374 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 375 "Need BLUETOOTH_ADMIN permission"); 376 int priority = Settings.Global.getInt(getContentResolver(), 377 Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), 378 BluetoothProfile.PRIORITY_UNDEFINED); 379 return priority; 380 } 381 382 boolean startVoiceRecognition(BluetoothDevice device) { 383 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 384 int connectionState = mStateMachine.getConnectionState(device); 385 if (connectionState != BluetoothProfile.STATE_CONNECTED && 386 connectionState != BluetoothProfile.STATE_CONNECTING) { 387 return false; 388 } 389 mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START); 390 return true; 391 } 392 393 boolean stopVoiceRecognition(BluetoothDevice device) { 394 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 395 // It seem that we really need to check the AudioOn state. 396 // But since we allow startVoiceRecognition in STATE_CONNECTED and 397 // STATE_CONNECTING state, we do these 2 in this method 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_STOP); 404 // TODO is this return correct when the voice recognition is not on? 405 return true; 406 } 407 408 boolean isAudioOn() { 409 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 410 return mStateMachine.isAudioOn(); 411 } 412 413 boolean isAudioConnected(BluetoothDevice device) { 414 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 415 return mStateMachine.isAudioConnected(device); 416 } 417 418 int getBatteryUsageHint(BluetoothDevice device) { 419 // TODO(BT) ask for BT stack support? 420 return 0; 421 } 422 423 boolean acceptIncomingConnect(BluetoothDevice device) { 424 // TODO(BT) remove it if stack does access control 425 return false; 426 } 427 428 boolean rejectIncomingConnect(BluetoothDevice device) { 429 // TODO(BT) remove it if stack does access control 430 return false; 431 } 432 433 int getAudioState(BluetoothDevice device) { 434 return mStateMachine.getAudioState(device); 435 } 436 437 boolean connectAudio() { 438 // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission 439 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 440 if (!mStateMachine.isConnected()) { 441 return false; 442 } 443 if (mStateMachine.isAudioOn()) { 444 return false; 445 } 446 mStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO); 447 return true; 448 } 449 450 boolean disconnectAudio() { 451 // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission 452 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 453 if (!mStateMachine.isAudioOn()) { 454 return false; 455 } 456 mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO); 457 return true; 458 } 459 460 boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { 461 int connectionState = mStateMachine.getConnectionState(device); 462 if (connectionState != BluetoothProfile.STATE_CONNECTED && 463 connectionState != BluetoothProfile.STATE_CONNECTING) { 464 return false; 465 } 466 mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_START, device); 467 return true; 468 } 469 470 boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { 471 int connectionState = mStateMachine.getConnectionState(device); 472 if (connectionState != BluetoothProfile.STATE_CONNECTED && 473 connectionState != BluetoothProfile.STATE_CONNECTING) { 474 return false; 475 } 476 mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_STOP, device); 477 return true; 478 } 479 480 private void phoneStateChanged(int numActive, int numHeld, int callState, 481 String number, int type) { 482 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null); 483 Message msg = mStateMachine.obtainMessage(HeadsetStateMachine.CALL_STATE_CHANGED); 484 msg.obj = new HeadsetCallState(numActive, numHeld, callState, number, type); 485 msg.arg1 = 0; // false 486 mStateMachine.sendMessage(msg); 487 } 488 489 private void clccResponse(int index, int direction, int status, int mode, boolean mpty, 490 String number, int type) { 491 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null); 492 mStateMachine.sendMessage(HeadsetStateMachine.SEND_CCLC_RESPONSE, 493 new HeadsetClccResponse(index, direction, status, mode, mpty, number, type)); 494 } 495 496 private boolean sendVendorSpecificResultCode(BluetoothDevice device, 497 String command, 498 String arg) { 499 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 500 int connectionState = mStateMachine.getConnectionState(device); 501 if (connectionState != BluetoothProfile.STATE_CONNECTED) { 502 return false; 503 } 504 // Currently we support only "+ANDROID". 505 if (!command.equals(BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID)) { 506 Log.w(TAG, "Disallowed unsolicited result code command: " + command); 507 return false; 508 } 509 mStateMachine.sendMessage(HeadsetStateMachine.SEND_VENDOR_SPECIFIC_RESULT_CODE, 510 new HeadsetVendorSpecificResultCode(command, arg)); 511 return true; 512 } 513 514 } 515