1 /* 2 * Copyright (C) 2018 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.btservice; 18 19 import android.bluetooth.BluetoothA2dp; 20 import android.bluetooth.BluetoothAdapter; 21 import android.bluetooth.BluetoothDevice; 22 import android.bluetooth.BluetoothHeadset; 23 import android.bluetooth.BluetoothHearingAid; 24 import android.bluetooth.BluetoothProfile; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.media.AudioDeviceCallback; 30 import android.media.AudioDeviceInfo; 31 import android.media.AudioManager; 32 import android.os.Handler; 33 import android.os.HandlerThread; 34 import android.os.Looper; 35 import android.os.Message; 36 import android.util.Log; 37 38 import com.android.bluetooth.a2dp.A2dpService; 39 import com.android.bluetooth.hearingaid.HearingAidService; 40 import com.android.bluetooth.hfp.HeadsetService; 41 import com.android.internal.annotations.VisibleForTesting; 42 43 import java.util.LinkedList; 44 import java.util.List; 45 import java.util.Objects; 46 47 /** 48 * The active device manager is responsible for keeping track of the 49 * connected A2DP/HFP/AVRCP/HearingAid devices and select which device is 50 * active (for each profile). 51 * 52 * Current policy (subject to change): 53 * 1) If the maximum number of connected devices is one, the manager doesn't 54 * do anything. Each profile is responsible for automatically selecting 55 * the connected device as active. Only if the maximum number of connected 56 * devices is more than one, the rules below will apply. 57 * 2) The selected A2DP active device is the one used for AVRCP as well. 58 * 3) The HFP active device might be different from the A2DP active device. 59 * 4) The Active Device Manager always listens for ACTION_ACTIVE_DEVICE_CHANGED 60 * broadcasts for each profile: 61 * - BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED for A2DP 62 * - BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED for HFP 63 * - BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED for HearingAid 64 * If such broadcast is received (e.g., triggered indirectly by user 65 * action on the UI), the device in the received broacast is marked 66 * as the current active device for that profile. 67 * 5) If there is a HearingAid active device, then A2DP and HFP active devices 68 * must be set to null (i.e., A2DP and HFP cannot have active devices). 69 * The reason is because A2DP or HFP cannot be used together with HearingAid. 70 * 6) If there are no connected devices (e.g., during startup, or after all 71 * devices have been disconnected, the active device per profile 72 * (A2DP/HFP/HearingAid) is selected as follows: 73 * 6.1) The last connected HearingAid device is selected as active. 74 * If there is an active A2DP or HFP device, those must be set to null. 75 * 6.2) The last connected A2DP or HFP device is selected as active. 76 * However, if there is an active HearingAid device, then the 77 * A2DP or HFP active device is not set (must remain null). 78 * 7) If the currently active device (per profile) is disconnected, the 79 * Active Device Manager just marks that the profile has no active device, 80 * but does not attempt to select a new one. Currently, the expectation is 81 * that the user will explicitly select the new active device. 82 * 8) If there is already an active device, and the corresponding 83 * ACTION_ACTIVE_DEVICE_CHANGED broadcast is received, the device 84 * contained in the broadcast is marked as active. However, if 85 * the contained device is null, the corresponding profile is marked 86 * as having no active device. 87 * 9) If a wired audio device is connected, the audio output is switched 88 * by the Audio Framework itself to that device. We detect this here, 89 * and the active device for each profile (A2DP/HFP/HearingAid) is set 90 * to null to reflect the output device state change. However, if the 91 * wired audio device is disconnected, we don't do anything explicit 92 * and apply the default behavior instead: 93 * 9.1) If the wired headset is still the selected output device (i.e. the 94 * active device is set to null), the Phone itself will become the output 95 * device (i.e., the active device will remain null). If music was 96 * playing, it will stop. 97 * 9.2) If one of the Bluetooth devices is the selected active device 98 * (e.g., by the user in the UI), disconnecting the wired audio device 99 * will have no impact. E.g., music will continue streaming over the 100 * active Bluetooth device. 101 */ 102 class ActiveDeviceManager { 103 private static final boolean DBG = true; 104 private static final String TAG = "BluetoothActiveDeviceManager"; 105 106 // Message types for the handler 107 private static final int MESSAGE_ADAPTER_ACTION_STATE_CHANGED = 1; 108 private static final int MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED = 2; 109 private static final int MESSAGE_A2DP_ACTION_ACTIVE_DEVICE_CHANGED = 3; 110 private static final int MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED = 4; 111 private static final int MESSAGE_HFP_ACTION_ACTIVE_DEVICE_CHANGED = 5; 112 private static final int MESSAGE_HEARING_AID_ACTION_ACTIVE_DEVICE_CHANGED = 6; 113 114 private final AdapterService mAdapterService; 115 private final ServiceFactory mFactory; 116 private HandlerThread mHandlerThread = null; 117 private Handler mHandler = null; 118 private final AudioManager mAudioManager; 119 private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback; 120 121 private final List<BluetoothDevice> mA2dpConnectedDevices = new LinkedList<>(); 122 private final List<BluetoothDevice> mHfpConnectedDevices = new LinkedList<>(); 123 private BluetoothDevice mA2dpActiveDevice = null; 124 private BluetoothDevice mHfpActiveDevice = null; 125 private BluetoothDevice mHearingAidActiveDevice = null; 126 127 // Broadcast receiver for all changes 128 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 129 @Override 130 public void onReceive(Context context, Intent intent) { 131 String action = intent.getAction(); 132 if (action == null) { 133 Log.e(TAG, "Received intent with null action"); 134 return; 135 } 136 switch (action) { 137 case BluetoothAdapter.ACTION_STATE_CHANGED: 138 mHandler.obtainMessage(MESSAGE_ADAPTER_ACTION_STATE_CHANGED, 139 intent).sendToTarget(); 140 break; 141 case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED: 142 mHandler.obtainMessage(MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED, 143 intent).sendToTarget(); 144 break; 145 case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED: 146 mHandler.obtainMessage(MESSAGE_A2DP_ACTION_ACTIVE_DEVICE_CHANGED, 147 intent).sendToTarget(); 148 break; 149 case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED: 150 mHandler.obtainMessage(MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED, 151 intent).sendToTarget(); 152 break; 153 case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED: 154 mHandler.obtainMessage(MESSAGE_HFP_ACTION_ACTIVE_DEVICE_CHANGED, 155 intent).sendToTarget(); 156 break; 157 case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED: 158 mHandler.obtainMessage(MESSAGE_HEARING_AID_ACTION_ACTIVE_DEVICE_CHANGED, 159 intent).sendToTarget(); 160 break; 161 default: 162 Log.e(TAG, "Received unexpected intent, action=" + action); 163 break; 164 } 165 } 166 }; 167 168 class ActiveDeviceManagerHandler extends Handler { ActiveDeviceManagerHandler(Looper looper)169 ActiveDeviceManagerHandler(Looper looper) { 170 super(looper); 171 } 172 173 @Override handleMessage(Message msg)174 public void handleMessage(Message msg) { 175 switch (msg.what) { 176 case MESSAGE_ADAPTER_ACTION_STATE_CHANGED: { 177 Intent intent = (Intent) msg.obj; 178 int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); 179 if (DBG) { 180 Log.d(TAG, "handleMessage(MESSAGE_ADAPTER_ACTION_STATE_CHANGED): newState=" 181 + newState); 182 } 183 if (newState == BluetoothAdapter.STATE_ON) { 184 resetState(); 185 } 186 } 187 break; 188 189 case MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED: { 190 Intent intent = (Intent) msg.obj; 191 BluetoothDevice device = 192 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 193 int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); 194 int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 195 if (prevState == nextState) { 196 // Nothing has changed 197 break; 198 } 199 if (nextState == BluetoothProfile.STATE_CONNECTED) { 200 // Device connected 201 if (DBG) { 202 Log.d(TAG, 203 "handleMessage(MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED): " 204 + "device " + device + " connected"); 205 } 206 if (mA2dpConnectedDevices.contains(device)) { 207 break; // The device is already connected 208 } 209 mA2dpConnectedDevices.add(device); 210 if (mHearingAidActiveDevice == null) { 211 // New connected device: select it as active 212 setA2dpActiveDevice(device); 213 break; 214 } 215 break; 216 } 217 if (prevState == BluetoothProfile.STATE_CONNECTED) { 218 // Device disconnected 219 if (DBG) { 220 Log.d(TAG, 221 "handleMessage(MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED): " 222 + "device " + device + " disconnected"); 223 } 224 mA2dpConnectedDevices.remove(device); 225 if (Objects.equals(mA2dpActiveDevice, device)) { 226 setA2dpActiveDevice(null); 227 } 228 } 229 } 230 break; 231 232 case MESSAGE_A2DP_ACTION_ACTIVE_DEVICE_CHANGED: { 233 Intent intent = (Intent) msg.obj; 234 BluetoothDevice device = 235 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 236 if (DBG) { 237 Log.d(TAG, "handleMessage(MESSAGE_A2DP_ACTION_ACTIVE_DEVICE_CHANGED): " 238 + "device= " + device); 239 } 240 if (device != null && !Objects.equals(mA2dpActiveDevice, device)) { 241 setHearingAidActiveDevice(null); 242 } 243 // Just assign locally the new value 244 mA2dpActiveDevice = device; 245 } 246 break; 247 248 case MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED: { 249 Intent intent = (Intent) msg.obj; 250 BluetoothDevice device = 251 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 252 int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); 253 int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 254 if (prevState == nextState) { 255 // Nothing has changed 256 break; 257 } 258 if (nextState == BluetoothProfile.STATE_CONNECTED) { 259 // Device connected 260 if (DBG) { 261 Log.d(TAG, 262 "handleMessage(MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED): " 263 + "device " + device + " connected"); 264 } 265 if (mHfpConnectedDevices.contains(device)) { 266 break; // The device is already connected 267 } 268 mHfpConnectedDevices.add(device); 269 if (mHearingAidActiveDevice == null) { 270 // New connected device: select it as active 271 setHfpActiveDevice(device); 272 break; 273 } 274 break; 275 } 276 if (prevState == BluetoothProfile.STATE_CONNECTED) { 277 // Device disconnected 278 if (DBG) { 279 Log.d(TAG, 280 "handleMessage(MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED): " 281 + "device " + device + " disconnected"); 282 } 283 mHfpConnectedDevices.remove(device); 284 if (Objects.equals(mHfpActiveDevice, device)) { 285 setHfpActiveDevice(null); 286 } 287 } 288 } 289 break; 290 291 case MESSAGE_HFP_ACTION_ACTIVE_DEVICE_CHANGED: { 292 Intent intent = (Intent) msg.obj; 293 BluetoothDevice device = 294 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 295 if (DBG) { 296 Log.d(TAG, "handleMessage(MESSAGE_HFP_ACTION_ACTIVE_DEVICE_CHANGED): " 297 + "device= " + device); 298 } 299 if (device != null && !Objects.equals(mHfpActiveDevice, device)) { 300 setHearingAidActiveDevice(null); 301 } 302 // Just assign locally the new value 303 mHfpActiveDevice = device; 304 } 305 break; 306 307 case MESSAGE_HEARING_AID_ACTION_ACTIVE_DEVICE_CHANGED: { 308 Intent intent = (Intent) msg.obj; 309 BluetoothDevice device = 310 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 311 if (DBG) { 312 Log.d(TAG, "handleMessage(MESSAGE_HA_ACTION_ACTIVE_DEVICE_CHANGED): " 313 + "device= " + device); 314 } 315 // Just assign locally the new value 316 mHearingAidActiveDevice = device; 317 if (device != null) { 318 setA2dpActiveDevice(null); 319 setHfpActiveDevice(null); 320 } 321 } 322 break; 323 } 324 } 325 } 326 327 /** Notifications of audio device connection and disconnection events. */ 328 private class AudioManagerAudioDeviceCallback extends AudioDeviceCallback { isWiredAudioHeadset(AudioDeviceInfo deviceInfo)329 private boolean isWiredAudioHeadset(AudioDeviceInfo deviceInfo) { 330 switch (deviceInfo.getType()) { 331 case AudioDeviceInfo.TYPE_WIRED_HEADSET: 332 case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: 333 case AudioDeviceInfo.TYPE_USB_HEADSET: 334 return true; 335 default: 336 break; 337 } 338 return false; 339 } 340 341 @Override onAudioDevicesAdded(AudioDeviceInfo[] addedDevices)342 public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { 343 if (DBG) { 344 Log.d(TAG, "onAudioDevicesAdded"); 345 } 346 boolean hasAddedWiredDevice = false; 347 for (AudioDeviceInfo deviceInfo : addedDevices) { 348 if (DBG) { 349 Log.d(TAG, "Audio device added: " + deviceInfo.getProductName() + " type: " 350 + deviceInfo.getType()); 351 } 352 if (isWiredAudioHeadset(deviceInfo)) { 353 hasAddedWiredDevice = true; 354 break; 355 } 356 } 357 if (hasAddedWiredDevice) { 358 wiredAudioDeviceConnected(); 359 } 360 } 361 362 @Override onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices)363 public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { 364 } 365 } 366 ActiveDeviceManager(AdapterService service, ServiceFactory factory)367 ActiveDeviceManager(AdapterService service, ServiceFactory factory) { 368 mAdapterService = service; 369 mFactory = factory; 370 mAudioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE); 371 mAudioManagerAudioDeviceCallback = new AudioManagerAudioDeviceCallback(); 372 } 373 start()374 void start() { 375 if (DBG) { 376 Log.d(TAG, "start()"); 377 } 378 379 mHandlerThread = new HandlerThread("BluetoothActiveDeviceManager"); 380 mHandlerThread.start(); 381 mHandler = new ActiveDeviceManagerHandler(mHandlerThread.getLooper()); 382 383 IntentFilter filter = new IntentFilter(); 384 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 385 filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 386 filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); 387 filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 388 filter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); 389 filter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED); 390 mAdapterService.registerReceiver(mReceiver, filter); 391 392 mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler); 393 } 394 cleanup()395 void cleanup() { 396 if (DBG) { 397 Log.d(TAG, "cleanup()"); 398 } 399 400 mAudioManager.unregisterAudioDeviceCallback(mAudioManagerAudioDeviceCallback); 401 mAdapterService.unregisterReceiver(mReceiver); 402 if (mHandlerThread != null) { 403 mHandlerThread.quit(); 404 mHandlerThread = null; 405 } 406 resetState(); 407 } 408 409 /** 410 * Get the {@link Looper} for the handler thread. This is used in testing and helper 411 * objects 412 * 413 * @return {@link Looper} for the handler thread 414 */ 415 @VisibleForTesting getHandlerLooper()416 public Looper getHandlerLooper() { 417 if (mHandlerThread == null) { 418 return null; 419 } 420 return mHandlerThread.getLooper(); 421 } 422 setA2dpActiveDevice(BluetoothDevice device)423 private void setA2dpActiveDevice(BluetoothDevice device) { 424 if (DBG) { 425 Log.d(TAG, "setA2dpActiveDevice(" + device + ")"); 426 } 427 final A2dpService a2dpService = mFactory.getA2dpService(); 428 if (a2dpService == null) { 429 return; 430 } 431 if (!a2dpService.setActiveDevice(device)) { 432 return; 433 } 434 mA2dpActiveDevice = device; 435 } 436 setHfpActiveDevice(BluetoothDevice device)437 private void setHfpActiveDevice(BluetoothDevice device) { 438 if (DBG) { 439 Log.d(TAG, "setHfpActiveDevice(" + device + ")"); 440 } 441 final HeadsetService headsetService = mFactory.getHeadsetService(); 442 if (headsetService == null) { 443 return; 444 } 445 if (!headsetService.setActiveDevice(device)) { 446 return; 447 } 448 mHfpActiveDevice = device; 449 } 450 setHearingAidActiveDevice(BluetoothDevice device)451 private void setHearingAidActiveDevice(BluetoothDevice device) { 452 if (DBG) { 453 Log.d(TAG, "setHearingAidActiveDevice(" + device + ")"); 454 } 455 final HearingAidService hearingAidService = mFactory.getHearingAidService(); 456 if (hearingAidService == null) { 457 return; 458 } 459 if (!hearingAidService.setActiveDevice(device)) { 460 return; 461 } 462 mHearingAidActiveDevice = device; 463 } 464 resetState()465 private void resetState() { 466 mA2dpConnectedDevices.clear(); 467 mA2dpActiveDevice = null; 468 469 mHfpConnectedDevices.clear(); 470 mHfpActiveDevice = null; 471 472 mHearingAidActiveDevice = null; 473 } 474 475 @VisibleForTesting getBroadcastReceiver()476 BroadcastReceiver getBroadcastReceiver() { 477 return mReceiver; 478 } 479 480 @VisibleForTesting getA2dpActiveDevice()481 BluetoothDevice getA2dpActiveDevice() { 482 return mA2dpActiveDevice; 483 } 484 485 @VisibleForTesting getHfpActiveDevice()486 BluetoothDevice getHfpActiveDevice() { 487 return mHfpActiveDevice; 488 } 489 490 @VisibleForTesting getHearingAidActiveDevice()491 BluetoothDevice getHearingAidActiveDevice() { 492 return mHearingAidActiveDevice; 493 } 494 495 /** 496 * Called when a wired audio device is connected. 497 * It might be called multiple times each time a wired audio device is connected. 498 */ 499 @VisibleForTesting wiredAudioDeviceConnected()500 void wiredAudioDeviceConnected() { 501 if (DBG) { 502 Log.d(TAG, "wiredAudioDeviceConnected"); 503 } 504 setA2dpActiveDevice(null); 505 setHfpActiveDevice(null); 506 setHearingAidActiveDevice(null); 507 } 508 } 509