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 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 roamChanged(boolean roam) { 261 HeadsetService service = getService(); 262 if (service == null) return; 263 service.roamChanged(roam); 264 } 265 266 public void clccResponse(int index, int direction, int status, int mode, boolean mpty, 267 String number, int type) { 268 HeadsetService service = getService(); 269 if (service == null) return; 270 service.clccResponse(index, direction, status, mode, mpty, number, type); 271 } 272 }; 273 274 //API methods 275 public static synchronized HeadsetService getHeadsetService(){ 276 if (sHeadsetService != null && sHeadsetService.isAvailable()) { 277 if (DBG) Log.d(TAG, "getHeadsetService(): returning " + sHeadsetService); 278 return sHeadsetService; 279 } 280 if (DBG) { 281 if (sHeadsetService == null) { 282 Log.d(TAG, "getHeadsetService(): service is NULL"); 283 } else if (!(sHeadsetService.isAvailable())) { 284 Log.d(TAG,"getHeadsetService(): service is not available"); 285 } 286 } 287 return null; 288 } 289 290 private static synchronized void setHeadsetService(HeadsetService instance) { 291 if (instance != null && instance.isAvailable()) { 292 if (DBG) Log.d(TAG, "setHeadsetService(): set to: " + sHeadsetService); 293 sHeadsetService = instance; 294 } else { 295 if (DBG) { 296 if (sHeadsetService == null) { 297 Log.d(TAG, "setHeadsetService(): service not available"); 298 } else if (!sHeadsetService.isAvailable()) { 299 Log.d(TAG,"setHeadsetService(): service is cleaning up"); 300 } 301 } 302 } 303 } 304 305 private static synchronized void clearHeadsetService() { 306 sHeadsetService = null; 307 } 308 309 public boolean connect(BluetoothDevice device) { 310 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 311 "Need BLUETOOTH ADMIN permission"); 312 313 if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) { 314 return false; 315 } 316 317 int connectionState = mStateMachine.getConnectionState(device); 318 if (connectionState == BluetoothProfile.STATE_CONNECTED || 319 connectionState == BluetoothProfile.STATE_CONNECTING) { 320 return false; 321 } 322 323 mStateMachine.sendMessage(HeadsetStateMachine.CONNECT, device); 324 return true; 325 } 326 327 boolean disconnect(BluetoothDevice device) { 328 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 329 "Need BLUETOOTH ADMIN permission"); 330 int connectionState = mStateMachine.getConnectionState(device); 331 if (connectionState != BluetoothProfile.STATE_CONNECTED && 332 connectionState != BluetoothProfile.STATE_CONNECTING) { 333 return false; 334 } 335 336 mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT, device); 337 return true; 338 } 339 340 public List<BluetoothDevice> getConnectedDevices() { 341 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 342 return mStateMachine.getConnectedDevices(); 343 } 344 345 private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 346 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 347 return mStateMachine.getDevicesMatchingConnectionStates(states); 348 } 349 350 int getConnectionState(BluetoothDevice device) { 351 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 352 return mStateMachine.getConnectionState(device); 353 } 354 355 public boolean setPriority(BluetoothDevice device, int priority) { 356 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 357 "Need BLUETOOTH_ADMIN permission"); 358 Settings.Global.putInt(getContentResolver(), 359 Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), 360 priority); 361 if (DBG) Log.d(TAG, "Saved priority " + device + " = " + priority); 362 return true; 363 } 364 365 public int getPriority(BluetoothDevice device) { 366 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 367 "Need BLUETOOTH_ADMIN permission"); 368 int priority = Settings.Global.getInt(getContentResolver(), 369 Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), 370 BluetoothProfile.PRIORITY_UNDEFINED); 371 return priority; 372 } 373 374 boolean startVoiceRecognition(BluetoothDevice device) { 375 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 376 int connectionState = mStateMachine.getConnectionState(device); 377 if (connectionState != BluetoothProfile.STATE_CONNECTED && 378 connectionState != BluetoothProfile.STATE_CONNECTING) { 379 return false; 380 } 381 mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START); 382 return true; 383 } 384 385 boolean stopVoiceRecognition(BluetoothDevice device) { 386 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 387 // It seem that we really need to check the AudioOn state. 388 // But since we allow startVoiceRecognition in STATE_CONNECTED and 389 // STATE_CONNECTING state, we do these 2 in this method 390 int connectionState = mStateMachine.getConnectionState(device); 391 if (connectionState != BluetoothProfile.STATE_CONNECTED && 392 connectionState != BluetoothProfile.STATE_CONNECTING) { 393 return false; 394 } 395 mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP); 396 // TODO is this return correct when the voice recognition is not on? 397 return true; 398 } 399 400 boolean isAudioOn() { 401 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 402 return mStateMachine.isAudioOn(); 403 } 404 405 boolean isAudioConnected(BluetoothDevice device) { 406 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 407 return mStateMachine.isAudioConnected(device); 408 } 409 410 int getBatteryUsageHint(BluetoothDevice device) { 411 // TODO(BT) ask for BT stack support? 412 return 0; 413 } 414 415 boolean acceptIncomingConnect(BluetoothDevice device) { 416 // TODO(BT) remove it if stack does access control 417 return false; 418 } 419 420 boolean rejectIncomingConnect(BluetoothDevice device) { 421 // TODO(BT) remove it if stack does access control 422 return false; 423 } 424 425 int getAudioState(BluetoothDevice device) { 426 return mStateMachine.getAudioState(device); 427 } 428 429 boolean connectAudio() { 430 // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission 431 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 432 if (!mStateMachine.isConnected()) { 433 return false; 434 } 435 if (mStateMachine.isAudioOn()) { 436 return false; 437 } 438 mStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO); 439 return true; 440 } 441 442 boolean disconnectAudio() { 443 // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission 444 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 445 if (!mStateMachine.isAudioOn()) { 446 return false; 447 } 448 mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO); 449 return true; 450 } 451 452 boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { 453 int connectionState = mStateMachine.getConnectionState(device); 454 if (connectionState != BluetoothProfile.STATE_CONNECTED && 455 connectionState != BluetoothProfile.STATE_CONNECTING) { 456 return false; 457 } 458 mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_START, device); 459 return true; 460 } 461 462 boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { 463 int connectionState = mStateMachine.getConnectionState(device); 464 if (connectionState != BluetoothProfile.STATE_CONNECTED && 465 connectionState != BluetoothProfile.STATE_CONNECTING) { 466 return false; 467 } 468 mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_STOP, device); 469 return true; 470 } 471 472 private void phoneStateChanged(int numActive, int numHeld, int callState, 473 String number, int type) { 474 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null); 475 Message msg = mStateMachine.obtainMessage(HeadsetStateMachine.CALL_STATE_CHANGED); 476 msg.obj = new HeadsetCallState(numActive, numHeld, callState, number, type); 477 msg.arg1 = 0; // false 478 mStateMachine.sendMessage(msg); 479 } 480 481 private void roamChanged(boolean roam) { 482 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null); 483 mStateMachine.sendMessage(HeadsetStateMachine.ROAM_CHANGED, roam); 484 } 485 486 private void clccResponse(int index, int direction, int status, int mode, boolean mpty, 487 String number, int type) { 488 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null); 489 mStateMachine.sendMessage(HeadsetStateMachine.SEND_CCLC_RESPONSE, 490 new HeadsetClccResponse(index, direction, status, mode, mpty, number, type)); 491 } 492 493 } 494