ActiveDeviceManager.java revision 4bc7daecf842b2cfbd0741668df798d531f7dcda
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 17package com.android.bluetooth.btservice; 18 19import android.bluetooth.BluetoothA2dp; 20import android.bluetooth.BluetoothAdapter; 21import android.bluetooth.BluetoothDevice; 22import android.bluetooth.BluetoothHeadset; 23import android.bluetooth.BluetoothHearingAid; 24import android.bluetooth.BluetoothProfile; 25import android.content.BroadcastReceiver; 26import android.content.Context; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.os.Handler; 30import android.os.HandlerThread; 31import android.os.Looper; 32import android.os.Message; 33import android.util.Log; 34 35import com.android.bluetooth.a2dp.A2dpService; 36import com.android.bluetooth.hearingaid.HearingAidService; 37import com.android.bluetooth.hfp.HeadsetService; 38 39import java.util.LinkedList; 40import java.util.List; 41import java.util.Objects; 42 43/** 44 * The active device manager is responsible for keeping track of the 45 * connected A2DP/HFP/AVRCP devices and select which device is 46 * active (for each profile). 47 * 48 * Current policy (subject to change): 49 * 1) If the maximum number of connected devices is one, the manager doesn't 50 * do anything. Each profile is responsible for automatically selecting 51 * the connected device as active. Only if the maximum number of connected 52 * devices is more than one, the rules below will apply. 53 * 2) The selected A2DP active device is the one used for AVRCP as well. 54 * 3) The HFP active device might be different from the A2DP active device. 55 * 4) The Active Device Manager always listens for 56 * ACTION_ACTIVE_DEVICE_CHANGED broadcasts for each profile: 57 * - BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED for A2DP 58 * - BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED for HFP 59 * If such broadcast is received (e.g., triggered indirectly by user 60 * action on the UI), the device in the received broacast is marked 61 * as the current active device for that profile. 62 * 5) If there are no connected devices (e.g., during startup, or after all 63 * devices have been disconnected, the active device per profile 64 * (either A2DP or HFP) is selected as follows: 65 * 5.1) The first connected device (for either A2DP or HFP) is immediately 66 * selected as active for that profile. Assume the first connected device 67 * is for A2DP. 68 * 5.2) A timer is started: if the same device is connected for the other 69 * profile as well (HFP in this example) while the timer is running, 70 * and there is no active HFP device yet, that device is selected as 71 * active for HFP as well. The purpose is to select by default the same 72 * device as active for both profiles. 73 * 5.3) While the timer is running, all other HFP connected devices are 74 * listed locally, but none of those devices is selected as active. 75 * 5.4) While the timer is running, if ACTION_ACTIVE_DEVICE_CHANGED broadcast 76 * is received for HFP, the device contained in the broadcast is 77 * marked as active. 78 * 5.5) If the timer expires and no HFP device has been selected as active, 79 * the first HFP connected device is selected as active. 80 * 6) If the currently active device (per profile) is disconnected, the 81 * Active Device Manager just marks that the profile has no active device, 82 * but does not attempt to select a new one. Currently, the expectation is 83 * that the user will explicitly select the new active device. 84 * 7) If there is already an active device, and the corresponding 85 * ACTION_ACTIVE_DEVICE_CHANGED broadcast is received, the device 86 * contained in the broadcast is marked as active. However, if 87 * the contained device is null, the corresponding profile is marked 88 * as having no active device. 89 */ 90class ActiveDeviceManager { 91 private static final boolean DBG = true; 92 private static final String TAG = "BluetoothActiveDeviceManager"; 93 94 // Message types for the handler 95 private static final int MESSAGE_ADAPTER_ACTION_STATE_CHANGED = 1; 96 private static final int MESSAGE_SELECT_ACTICE_DEVICE_TIMEOUT = 2; 97 private static final int MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED = 3; 98 private static final int MESSAGE_A2DP_ACTION_ACTIVE_DEVICE_CHANGED = 4; 99 private static final int MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED = 5; 100 private static final int MESSAGE_HFP_ACTION_ACTIVE_DEVICE_CHANGED = 6; 101 private static final int MESSAGE_HEARING_AID_ACTION_ACTIVE_DEVICE_CHANGED = 7; 102 103 // Timeouts 104 private static final int SELECT_ACTIVE_DEVICE_TIMEOUT_MS = 6000; // 6s 105 106 private final AdapterService mAdapterService; 107 private final ServiceFactory mFactory; 108 private HandlerThread mHandlerThread = null; 109 private Handler mHandler = null; 110 111 private final List<BluetoothDevice> mA2dpConnectedDevices = new LinkedList<>(); 112 private final List<BluetoothDevice> mHfpConnectedDevices = new LinkedList<>(); 113 private BluetoothDevice mA2dpActiveDevice = null; 114 private BluetoothDevice mHfpActiveDevice = null; 115 private BluetoothDevice mHearingAidActiveDevice = null; 116 117 // Broadcast receiver for all changes 118 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 119 @Override 120 public void onReceive(Context context, Intent intent) { 121 String action = intent.getAction(); 122 if (action == null) { 123 Log.e(TAG, "Received intent with null action"); 124 return; 125 } 126 switch (action) { 127 case BluetoothAdapter.ACTION_STATE_CHANGED: 128 mHandler.obtainMessage(MESSAGE_ADAPTER_ACTION_STATE_CHANGED, 129 intent).sendToTarget(); 130 break; 131 case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED: 132 mHandler.obtainMessage(MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED, 133 intent).sendToTarget(); 134 break; 135 case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED: 136 mHandler.obtainMessage(MESSAGE_A2DP_ACTION_ACTIVE_DEVICE_CHANGED, 137 intent).sendToTarget(); 138 break; 139 case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED: 140 mHandler.obtainMessage(MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED, 141 intent).sendToTarget(); 142 break; 143 case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED: 144 mHandler.obtainMessage(MESSAGE_HFP_ACTION_ACTIVE_DEVICE_CHANGED, 145 intent).sendToTarget(); 146 break; 147 case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED: 148 mHandler.obtainMessage(MESSAGE_HEARING_AID_ACTION_ACTIVE_DEVICE_CHANGED, 149 intent).sendToTarget(); 150 break; 151 default: 152 Log.e(TAG, "Received unexpected intent, action=" + action); 153 break; 154 } 155 } 156 }; 157 158 class ActivePoliceManagerHandler extends Handler { 159 ActivePoliceManagerHandler(Looper looper) { 160 super(looper); 161 } 162 163 @Override 164 public void handleMessage(Message msg) { 165 switch (msg.what) { 166 case MESSAGE_ADAPTER_ACTION_STATE_CHANGED: { 167 Intent intent = (Intent) msg.obj; 168 int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); 169 if (DBG) { 170 Log.d(TAG, "handleMessage(MESSAGE_ADAPTER_ACTION_STATE_CHANGED): newState=" 171 + newState); 172 } 173 if (newState == BluetoothAdapter.STATE_ON) { 174 resetState(); 175 } 176 } 177 break; 178 179 case MESSAGE_SELECT_ACTICE_DEVICE_TIMEOUT: { 180 if (DBG) { 181 Log.d(TAG, "handleMessage(MESSAGE_SELECT_ACTICE_DEVICE_TIMEOUT)"); 182 } 183 // Set the first connected device as active 184 if ((mA2dpActiveDevice == null) && !mA2dpConnectedDevices.isEmpty() 185 && mHearingAidActiveDevice == null) { 186 setA2dpActiveDevice(mA2dpConnectedDevices.get(0)); 187 } 188 if ((mHfpActiveDevice == null) && !mHfpConnectedDevices.isEmpty() 189 && mHearingAidActiveDevice == null) { 190 setHfpActiveDevice(mHfpConnectedDevices.get(0)); 191 } 192 } 193 break; 194 195 case MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED: { 196 Intent intent = (Intent) msg.obj; 197 BluetoothDevice device = 198 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 199 int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); 200 int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 201 if (nextState == BluetoothProfile.STATE_CONNECTED) { 202 // Device connected 203 if (DBG) { 204 Log.d(TAG, 205 "handleMessage(MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED): " 206 + "device " + device + " connected"); 207 } 208 if (mA2dpConnectedDevices.contains(device)) { 209 break; 210 } 211 if (!hasConnectedClassicDevices() && mHearingAidActiveDevice == null) { 212 // First connected device: select it as active and start the timer 213 mA2dpConnectedDevices.add(device); 214 Message m = obtainMessage(MESSAGE_SELECT_ACTICE_DEVICE_TIMEOUT); 215 sendMessageDelayed(m, SELECT_ACTIVE_DEVICE_TIMEOUT_MS); 216 setA2dpActiveDevice(device); 217 break; 218 } 219 mA2dpConnectedDevices.add(device); 220 // Check whether the active device for the other profile is same 221 if ((mA2dpActiveDevice == null) && matchesActiveDevice(device) 222 && mHearingAidActiveDevice == null) { 223 setA2dpActiveDevice(device); 224 break; 225 } 226 // Check whether the active device selection timer is not running 227 if ((mA2dpActiveDevice == null) 228 && !hasMessages(MESSAGE_SELECT_ACTICE_DEVICE_TIMEOUT) 229 && mHearingAidActiveDevice == null) { 230 setA2dpActiveDevice(mA2dpConnectedDevices.get(0)); 231 break; 232 } 233 break; 234 } 235 if ((prevState == BluetoothProfile.STATE_CONNECTED) 236 && (nextState != prevState)) { 237 // Device disconnected 238 if (DBG) { 239 Log.d(TAG, 240 "handleMessage(MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED): " 241 + "device " + device + " disconnected"); 242 } 243 mA2dpConnectedDevices.remove(device); 244 if (Objects.equals(mA2dpActiveDevice, device)) { 245 setA2dpActiveDevice(null); 246 } 247 } 248 } 249 break; 250 251 case MESSAGE_A2DP_ACTION_ACTIVE_DEVICE_CHANGED: { 252 Intent intent = (Intent) msg.obj; 253 BluetoothDevice device = 254 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 255 if (DBG) { 256 Log.d(TAG, "handleMessage(MESSAGE_A2DP_ACTION_ACTIVE_DEVICE_CHANGED): " 257 + "device= " + device); 258 } 259 removeMessages(MESSAGE_SELECT_ACTICE_DEVICE_TIMEOUT); 260 if (device != null && !Objects.equals(mA2dpActiveDevice, device)) { 261 setHearingAidActiveDevice(null); 262 } 263 // Just assign locally the new value 264 mA2dpActiveDevice = device; 265 } 266 break; 267 268 case MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED: { 269 Intent intent = (Intent) msg.obj; 270 BluetoothDevice device = 271 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 272 int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); 273 int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 274 // TODO: Copy the corresponding logic from the processing of 275 // message MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED 276 } 277 break; 278 279 case MESSAGE_HFP_ACTION_ACTIVE_DEVICE_CHANGED: { 280 Intent intent = (Intent) msg.obj; 281 BluetoothDevice device = 282 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 283 if (DBG) { 284 Log.d(TAG, "handleMessage(MESSAGE_HFP_ACTION_ACTIVE_DEVICE_CHANGED): " 285 + "device= " + device); 286 } 287 removeMessages(MESSAGE_SELECT_ACTICE_DEVICE_TIMEOUT); 288 if (device != null && !Objects.equals(mHfpActiveDevice, device)) { 289 setHearingAidActiveDevice(null); 290 } 291 // Just assign locally the new value 292 mHfpActiveDevice = device; 293 } 294 break; 295 296 case MESSAGE_HEARING_AID_ACTION_ACTIVE_DEVICE_CHANGED: { 297 Intent intent = (Intent) msg.obj; 298 BluetoothDevice device = 299 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 300 if (DBG) { 301 Log.d(TAG, "handleMessage(MESSAGE_HA_ACTION_ACTIVE_DEVICE_CHANGED): " 302 + "device= " + device); 303 } 304 removeMessages(MESSAGE_SELECT_ACTICE_DEVICE_TIMEOUT); 305 // Just assign locally the new value 306 mHearingAidActiveDevice = device; 307 if (device != null) { 308 setA2dpActiveDevice(null); 309 setHfpActiveDevice(null); 310 } 311 } 312 break; 313 } 314 } 315 } 316 317 ActiveDeviceManager(AdapterService service, ServiceFactory factory) { 318 mAdapterService = service; 319 mFactory = factory; 320 } 321 322 void start() { 323 if (DBG) { 324 Log.d(TAG, "start()"); 325 } 326 327 mHandlerThread = new HandlerThread("BluetoothActiveDeviceManager"); 328 mHandlerThread.start(); 329 mHandler = new ActivePoliceManagerHandler(mHandlerThread.getLooper()); 330 331 IntentFilter filter = new IntentFilter(); 332 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 333 filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 334 filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); 335 filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 336 filter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); 337 filter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED); 338 mAdapterService.registerReceiver(mReceiver, filter); 339 } 340 341 void cleanup() { 342 if (DBG) { 343 Log.d(TAG, "cleanup()"); 344 } 345 346 mAdapterService.unregisterReceiver(mReceiver); 347 if (mHandlerThread != null) { 348 mHandlerThread.quit(); 349 mHandlerThread = null; 350 } 351 resetState(); 352 } 353 354 private void setA2dpActiveDevice(BluetoothDevice device) { 355 if (DBG) { 356 Log.d(TAG, "setA2dpActiveDevice(" + device + ")"); 357 } 358 final A2dpService a2dpService = mFactory.getA2dpService(); 359 if (a2dpService == null) { 360 return; 361 } 362 if (!a2dpService.setActiveDevice(device)) { 363 return; 364 } 365 mA2dpActiveDevice = device; 366 } 367 368 private void setHfpActiveDevice(BluetoothDevice device) { 369 if (DBG) { 370 Log.d(TAG, "setHfpActiveDevice(" + device + ")"); 371 } 372 final HeadsetService headsetService = mFactory.getHeadsetService(); 373 if (headsetService == null) { 374 return; 375 } 376 if (!headsetService.setActiveDevice(device)) { 377 return; 378 } 379 mHfpActiveDevice = device; 380 } 381 382 private void setHearingAidActiveDevice(BluetoothDevice device) { 383 if (DBG) { 384 Log.d(TAG, "setHearingAidActiveDevice(" + device + ")"); 385 } 386 final HearingAidService hearingAidService = mFactory.getHearingAidService(); 387 if (hearingAidService == null) { 388 return; 389 } 390 if (!hearingAidService.setActiveDevice(device)) { 391 return; 392 } 393 mHearingAidActiveDevice = device; 394 } 395 396 private boolean hasConnectedClassicDevices() { 397 return (!mA2dpConnectedDevices.isEmpty() || !mHfpConnectedDevices.isEmpty()); 398 } 399 400 private boolean matchesActiveDevice(BluetoothDevice device) { 401 return (Objects.equals(mA2dpActiveDevice, device) 402 || Objects.equals(mHfpActiveDevice, device)); 403 } 404 405 private void resetState() { 406 if (mHandler != null) { 407 mHandler.removeMessages(MESSAGE_SELECT_ACTICE_DEVICE_TIMEOUT); 408 } 409 mA2dpConnectedDevices.clear(); 410 mA2dpActiveDevice = null; 411 412 mHfpConnectedDevices.clear(); 413 mHfpActiveDevice = null; 414 } 415} 416