1 /* 2 * Copyright (C) 2017 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.annotation.RequiresPermission; 20 import android.app.ActivityThread; 21 import android.bluetooth.BluetoothA2dp; 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothHeadset; 25 import android.bluetooth.BluetoothHearingAid; 26 import android.bluetooth.BluetoothLeAudio; 27 import android.bluetooth.BluetoothProfile; 28 import android.bluetooth.BluetoothUuid; 29 import android.content.Attributable; 30 import android.content.BroadcastReceiver; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.os.Handler; 35 import android.os.Looper; 36 import android.os.Message; 37 import android.os.ParcelUuid; 38 import android.os.Parcelable; 39 import android.util.Log; 40 41 import com.android.bluetooth.a2dp.A2dpService; 42 import com.android.bluetooth.btservice.storage.DatabaseManager; 43 import com.android.bluetooth.hearingaid.HearingAidService; 44 import com.android.bluetooth.hfp.HeadsetService; 45 import com.android.bluetooth.hid.HidHostService; 46 import com.android.bluetooth.pan.PanService; 47 import com.android.internal.R; 48 import com.android.internal.annotations.VisibleForTesting; 49 import com.android.internal.util.ArrayUtils; 50 51 import java.util.HashSet; 52 import java.util.List; 53 import java.util.Objects; 54 55 // Describes the phone policy 56 // 57 // The policy should be as decoupled from the stack as possible. In an ideal world we should not 58 // need to have this policy talk with any non-public APIs and one way to enforce that would be to 59 // keep this file outside the Bluetooth process. Unfortunately, keeping a separate process alive is 60 // an expensive and a tedious task. 61 // 62 // Best practices: 63 // a) PhonePolicy should be ALL private methods 64 // -- Use broadcasts which can be listened in on the BroadcastReceiver 65 // b) NEVER call from the PhonePolicy into the Java stack, unless public APIs. It is OK to call into 66 // the non public versions as long as public versions exist (so that a 3rd party policy can mimick) 67 // us. 68 // 69 // Policy description: 70 // 71 // Policies are usually governed by outside events that may warrant an action. We talk about various 72 // events and the resulting outcome from this policy: 73 // 74 // 1. Adapter turned ON: At this point we will try to auto-connect the (device, profile) pairs which 75 // have PRIORITY_AUTO_CONNECT. The fact that we *only* auto-connect Headset and A2DP is something 76 // that is hardcoded and specific to phone policy (see autoConnect() function) 77 // 2. When the profile connection-state changes: At this point if a new profile gets CONNECTED we 78 // will try to connect other profiles on the same device. This is to avoid collision if devices 79 // somehow end up trying to connect at same time or general connection issues. 80 class PhonePolicy { 81 private static final boolean DBG = true; 82 private static final String TAG = "BluetoothPhonePolicy"; 83 84 // Message types for the handler (internal messages generated by intents or timeouts) 85 private static final int MESSAGE_PROFILE_CONNECTION_STATE_CHANGED = 1; 86 private static final int MESSAGE_PROFILE_INIT_PRIORITIES = 2; 87 private static final int MESSAGE_CONNECT_OTHER_PROFILES = 3; 88 private static final int MESSAGE_ADAPTER_STATE_TURNED_ON = 4; 89 private static final int MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED = 5; 90 private static final int MESSAGE_DEVICE_CONNECTED = 6; 91 92 // Timeouts 93 @VisibleForTesting static int sConnectOtherProfilesTimeoutMillis = 6000; // 6s 94 95 private DatabaseManager mDatabaseManager; 96 private final AdapterService mAdapterService; 97 private final ServiceFactory mFactory; 98 private final Handler mHandler; 99 private final HashSet<BluetoothDevice> mHeadsetRetrySet = new HashSet<>(); 100 private final HashSet<BluetoothDevice> mA2dpRetrySet = new HashSet<>(); 101 private final HashSet<BluetoothDevice> mConnectOtherProfilesDeviceSet = new HashSet<>(); 102 103 // Broadcast receiver for all changes to states of various profiles 104 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 105 @Override 106 public void onReceive(Context context, Intent intent) { 107 String action = intent.getAction(); 108 if (action == null) { 109 errorLog("Received intent with null action"); 110 return; 111 } 112 switch (action) { 113 case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED: 114 mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED, 115 BluetoothProfile.HEADSET, -1, // No-op argument 116 intent).sendToTarget(); 117 break; 118 case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED: 119 mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED, 120 BluetoothProfile.A2DP, -1, // No-op argument 121 intent).sendToTarget(); 122 break; 123 case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED: 124 mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED, 125 BluetoothProfile.LE_AUDIO, -1, // No-op argument 126 intent).sendToTarget(); 127 break; 128 case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED: 129 mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED, 130 BluetoothProfile.A2DP, -1, // No-op argument 131 intent).sendToTarget(); 132 break; 133 case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED: 134 mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED, 135 BluetoothProfile.HEADSET, -1, // No-op argument 136 intent).sendToTarget(); 137 break; 138 case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED: 139 mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED, 140 BluetoothProfile.HEARING_AID, -1, // No-op argument 141 intent).sendToTarget(); 142 break; 143 case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED: 144 mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED, 145 BluetoothProfile.LE_AUDIO, -1, // No-op argument 146 intent).sendToTarget(); 147 break; 148 case BluetoothAdapter.ACTION_STATE_CHANGED: 149 // Only pass the message on if the adapter has actually changed state from 150 // non-ON to ON. NOTE: ON is the state depicting BREDR ON and not just BLE ON. 151 int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); 152 if (newState == BluetoothAdapter.STATE_ON) { 153 mHandler.obtainMessage(MESSAGE_ADAPTER_STATE_TURNED_ON).sendToTarget(); 154 } 155 break; 156 case BluetoothDevice.ACTION_UUID: 157 mHandler.obtainMessage(MESSAGE_PROFILE_INIT_PRIORITIES, intent).sendToTarget(); 158 break; 159 case BluetoothDevice.ACTION_ACL_CONNECTED: 160 mHandler.obtainMessage(MESSAGE_DEVICE_CONNECTED, intent).sendToTarget(); 161 default: 162 Log.e(TAG, "Received unexpected intent, action=" + action); 163 break; 164 } 165 } 166 }; 167 168 @VisibleForTesting getBroadcastReceiver()169 BroadcastReceiver getBroadcastReceiver() { 170 return mReceiver; 171 } 172 173 // Handler to handoff intents to class thread 174 class PhonePolicyHandler extends Handler { PhonePolicyHandler(Looper looper)175 PhonePolicyHandler(Looper looper) { 176 super(looper); 177 } 178 179 @Override handleMessage(Message msg)180 public void handleMessage(Message msg) { 181 switch (msg.what) { 182 case MESSAGE_PROFILE_INIT_PRIORITIES: { 183 Intent intent = (Intent) msg.obj; 184 BluetoothDevice device = 185 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 186 Parcelable[] uuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID); 187 debugLog("Received ACTION_UUID for device " + device); 188 if (uuids != null) { 189 ParcelUuid[] uuidsToSend = new ParcelUuid[uuids.length]; 190 for (int i = 0; i < uuidsToSend.length; i++) { 191 uuidsToSend[i] = (ParcelUuid) uuids[i]; 192 debugLog("index=" + i + "uuid=" + uuidsToSend[i]); 193 } 194 processInitProfilePriorities(device, uuidsToSend); 195 } 196 } 197 break; 198 199 case MESSAGE_PROFILE_CONNECTION_STATE_CHANGED: { 200 Intent intent = (Intent) msg.obj; 201 BluetoothDevice device = 202 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 203 int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); 204 int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 205 processProfileStateChanged(device, msg.arg1, nextState, prevState); 206 } 207 break; 208 209 case MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED: { 210 Intent intent = (Intent) msg.obj; 211 BluetoothDevice activeDevice = 212 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 213 processActiveDeviceChanged(activeDevice, msg.arg1); 214 } 215 break; 216 217 case MESSAGE_CONNECT_OTHER_PROFILES: { 218 // Called when we try connect some profiles in processConnectOtherProfiles but 219 // we send a delayed message to try connecting the remaining profiles 220 BluetoothDevice device = (BluetoothDevice) msg.obj; 221 Attributable.setAttributionSource(device, 222 ActivityThread.currentAttributionSource()); 223 processConnectOtherProfiles(device); 224 mConnectOtherProfilesDeviceSet.remove(device); 225 break; 226 } 227 case MESSAGE_ADAPTER_STATE_TURNED_ON: 228 // Call auto connect when adapter switches state to ON 229 resetStates(); 230 autoConnect(); 231 break; 232 case MESSAGE_DEVICE_CONNECTED: 233 Intent intent = (Intent) msg.obj; 234 BluetoothDevice device = 235 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 236 processDeviceConnected(device); 237 } 238 } 239 } 240 241 ; 242 243 // Policy API functions for lifecycle management (protected) start()244 protected void start() { 245 IntentFilter filter = new IntentFilter(); 246 filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 247 filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 248 filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED); 249 filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); 250 filter.addAction(BluetoothDevice.ACTION_UUID); 251 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 252 filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); 253 filter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); 254 filter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED); 255 filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED); 256 mAdapterService.registerReceiver(mReceiver, filter); 257 } 258 cleanup()259 protected void cleanup() { 260 mAdapterService.unregisterReceiver(mReceiver); 261 resetStates(); 262 } 263 PhonePolicy(AdapterService service, ServiceFactory factory)264 PhonePolicy(AdapterService service, ServiceFactory factory) { 265 mAdapterService = service; 266 mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(), 267 "DatabaseManager cannot be null when PhonePolicy starts"); 268 mFactory = factory; 269 mHandler = new PhonePolicyHandler(service.getMainLooper()); 270 } 271 272 // Policy implementation, all functions MUST be private 273 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) processInitProfilePriorities(BluetoothDevice device, ParcelUuid[] uuids)274 private void processInitProfilePriorities(BluetoothDevice device, ParcelUuid[] uuids) { 275 debugLog("processInitProfilePriorities() - device " + device); 276 HidHostService hidService = mFactory.getHidHostService(); 277 A2dpService a2dpService = mFactory.getA2dpService(); 278 HeadsetService headsetService = mFactory.getHeadsetService(); 279 PanService panService = mFactory.getPanService(); 280 HearingAidService hearingAidService = mFactory.getHearingAidService(); 281 282 // Set profile priorities only for the profiles discovered on the remote device. 283 // This avoids needless auto-connect attempts to profiles non-existent on the remote device 284 if ((hidService != null) && (ArrayUtils.contains(uuids, BluetoothUuid.HID) 285 || ArrayUtils.contains(uuids, BluetoothUuid.HOGP)) && ( 286 hidService.getConnectionPolicy(device) 287 == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) { 288 mAdapterService.getDatabase().setProfileConnectionPolicy(device, 289 BluetoothProfile.HID_HOST, BluetoothProfile.CONNECTION_POLICY_ALLOWED); 290 } 291 292 // If we do not have a stored priority for HFP/A2DP (all roles) then default to on. 293 if ((headsetService != null) && ((ArrayUtils.contains(uuids, BluetoothUuid.HSP) 294 || ArrayUtils.contains(uuids, BluetoothUuid.HFP)) && ( 295 headsetService.getConnectionPolicy(device) 296 == BluetoothProfile.CONNECTION_POLICY_UNKNOWN))) { 297 mAdapterService.getDatabase().setProfileConnectionPolicy(device, 298 BluetoothProfile.HEADSET, BluetoothProfile.CONNECTION_POLICY_ALLOWED); 299 } 300 301 if ((a2dpService != null) && (ArrayUtils.contains(uuids, BluetoothUuid.A2DP_SINK) 302 || ArrayUtils.contains(uuids, BluetoothUuid.ADV_AUDIO_DIST)) && ( 303 a2dpService.getConnectionPolicy(device) 304 == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) { 305 mAdapterService.getDatabase().setProfileConnectionPolicy(device, 306 BluetoothProfile.A2DP, BluetoothProfile.CONNECTION_POLICY_ALLOWED); 307 } 308 309 if ((panService != null) && (ArrayUtils.contains(uuids, BluetoothUuid.PANU) && ( 310 panService.getConnectionPolicy(device) 311 == BluetoothProfile.CONNECTION_POLICY_UNKNOWN) 312 && mAdapterService.getResources() 313 .getBoolean(R.bool.config_bluetooth_pan_enable_autoconnect))) { 314 mAdapterService.getDatabase().setProfileConnectionPolicy(device, 315 BluetoothProfile.PAN, BluetoothProfile.CONNECTION_POLICY_ALLOWED); 316 } 317 318 if ((hearingAidService != null) && ArrayUtils.contains(uuids, 319 BluetoothUuid.HEARING_AID) && (hearingAidService.getConnectionPolicy(device) 320 == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) { 321 debugLog("setting hearing aid profile priority for device " + device); 322 mAdapterService.getDatabase().setProfileConnectionPolicy(device, 323 BluetoothProfile.HEARING_AID, BluetoothProfile.CONNECTION_POLICY_ALLOWED); 324 } 325 } 326 327 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) processProfileStateChanged(BluetoothDevice device, int profileId, int nextState, int prevState)328 private void processProfileStateChanged(BluetoothDevice device, int profileId, int nextState, 329 int prevState) { 330 debugLog("processProfileStateChanged, device=" + device + ", profile=" + profileId + ", " 331 + prevState + " -> " + nextState); 332 if (((profileId == BluetoothProfile.A2DP) || (profileId == BluetoothProfile.HEADSET) 333 || (profileId == BluetoothProfile.LE_AUDIO))) { 334 if (nextState == BluetoothProfile.STATE_CONNECTED) { 335 switch (profileId) { 336 case BluetoothProfile.A2DP: 337 mA2dpRetrySet.remove(device); 338 break; 339 case BluetoothProfile.HEADSET: 340 mHeadsetRetrySet.remove(device); 341 break; 342 } 343 connectOtherProfile(device); 344 } 345 if (nextState == BluetoothProfile.STATE_DISCONNECTED) { 346 if (profileId == BluetoothProfile.A2DP) { 347 mDatabaseManager.setDisconnection(device); 348 } 349 handleAllProfilesDisconnected(device); 350 } 351 } 352 } 353 354 /** 355 * Updates the last connection date in the connection order database for the newly active device 356 * if connected to a2dp profile 357 * 358 * @param device is the device we just made the active device 359 */ processActiveDeviceChanged(BluetoothDevice device, int profileId)360 private void processActiveDeviceChanged(BluetoothDevice device, int profileId) { 361 debugLog("processActiveDeviceChanged, device=" + device + ", profile=" + profileId); 362 363 if (device != null) { 364 mDatabaseManager.setConnection(device, profileId == BluetoothProfile.A2DP); 365 } 366 } 367 processDeviceConnected(BluetoothDevice device)368 private void processDeviceConnected(BluetoothDevice device) { 369 debugLog("processDeviceConnected, device=" + device); 370 mDatabaseManager.setConnection(device, false); 371 } 372 373 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) handleAllProfilesDisconnected(BluetoothDevice device)374 private boolean handleAllProfilesDisconnected(BluetoothDevice device) { 375 boolean atLeastOneProfileConnectedForDevice = false; 376 boolean allProfilesEmpty = true; 377 HeadsetService hsService = mFactory.getHeadsetService(); 378 A2dpService a2dpService = mFactory.getA2dpService(); 379 PanService panService = mFactory.getPanService(); 380 381 if (hsService != null) { 382 List<BluetoothDevice> hsConnDevList = hsService.getConnectedDevices(); 383 allProfilesEmpty &= hsConnDevList.isEmpty(); 384 atLeastOneProfileConnectedForDevice |= hsConnDevList.contains(device); 385 } 386 if (a2dpService != null) { 387 List<BluetoothDevice> a2dpConnDevList = a2dpService.getConnectedDevices(); 388 allProfilesEmpty &= a2dpConnDevList.isEmpty(); 389 atLeastOneProfileConnectedForDevice |= a2dpConnDevList.contains(device); 390 } 391 if (panService != null) { 392 List<BluetoothDevice> panConnDevList = panService.getConnectedDevices(); 393 allProfilesEmpty &= panConnDevList.isEmpty(); 394 atLeastOneProfileConnectedForDevice |= panConnDevList.contains(device); 395 } 396 397 if (!atLeastOneProfileConnectedForDevice) { 398 // Consider this device as fully disconnected, don't bother connecting others 399 debugLog("handleAllProfilesDisconnected: all profiles disconnected for " + device); 400 mHeadsetRetrySet.remove(device); 401 mA2dpRetrySet.remove(device); 402 if (allProfilesEmpty) { 403 debugLog("handleAllProfilesDisconnected: all profiles disconnected for all" 404 + " devices"); 405 // reset retry status so that in the next round we can start retrying connections 406 resetStates(); 407 } 408 return true; 409 } 410 return false; 411 } 412 resetStates()413 private void resetStates() { 414 mHeadsetRetrySet.clear(); 415 mA2dpRetrySet.clear(); 416 } 417 418 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) autoConnect()419 private void autoConnect() { 420 if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) { 421 errorLog("autoConnect: BT is not ON. Exiting autoConnect"); 422 return; 423 } 424 425 if (!mAdapterService.isQuietModeEnabled()) { 426 debugLog("autoConnect: Initiate auto connection on BT on..."); 427 final BluetoothDevice mostRecentlyActiveA2dpDevice = 428 mDatabaseManager.getMostRecentlyConnectedA2dpDevice(); 429 if (mostRecentlyActiveA2dpDevice == null) { 430 errorLog("autoConnect: most recently active a2dp device is null"); 431 return; 432 } 433 debugLog("autoConnect: Device " + mostRecentlyActiveA2dpDevice 434 + " attempting auto connection"); 435 autoConnectHeadset(mostRecentlyActiveA2dpDevice); 436 autoConnectA2dp(mostRecentlyActiveA2dpDevice); 437 } else { 438 debugLog("autoConnect() - BT is in quiet mode. Not initiating auto connections"); 439 } 440 } 441 autoConnectA2dp(BluetoothDevice device)442 private void autoConnectA2dp(BluetoothDevice device) { 443 final A2dpService a2dpService = mFactory.getA2dpService(); 444 if (a2dpService == null) { 445 warnLog("autoConnectA2dp: service is null, failed to connect to " + device); 446 return; 447 } 448 int a2dpConnectionPolicy = a2dpService.getConnectionPolicy(device); 449 if (a2dpConnectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 450 debugLog("autoConnectA2dp: connecting A2DP with " + device); 451 a2dpService.connect(device); 452 } else { 453 debugLog("autoConnectA2dp: skipped auto-connect A2DP with device " + device 454 + " connectionPolicy " + a2dpConnectionPolicy); 455 } 456 } 457 458 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) autoConnectHeadset(BluetoothDevice device)459 private void autoConnectHeadset(BluetoothDevice device) { 460 final HeadsetService hsService = mFactory.getHeadsetService(); 461 if (hsService == null) { 462 warnLog("autoConnectHeadset: service is null, failed to connect to " + device); 463 return; 464 } 465 int headsetConnectionPolicy = hsService.getConnectionPolicy(device); 466 if (headsetConnectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 467 debugLog("autoConnectHeadset: Connecting HFP with " + device); 468 hsService.connect(device); 469 } else { 470 debugLog("autoConnectHeadset: skipped auto-connect HFP with device " + device 471 + " connectionPolicy " + headsetConnectionPolicy); 472 } 473 } 474 connectOtherProfile(BluetoothDevice device)475 private void connectOtherProfile(BluetoothDevice device) { 476 if (mAdapterService.isQuietModeEnabled()) { 477 debugLog("connectOtherProfile: in quiet mode, skip connect other profile " + device); 478 return; 479 } 480 if (mConnectOtherProfilesDeviceSet.contains(device)) { 481 debugLog("connectOtherProfile: already scheduled callback for " + device); 482 return; 483 } 484 mConnectOtherProfilesDeviceSet.add(device); 485 Message m = mHandler.obtainMessage(MESSAGE_CONNECT_OTHER_PROFILES); 486 m.obj = device; 487 mHandler.sendMessageDelayed(m, sConnectOtherProfilesTimeoutMillis); 488 } 489 490 // This function is called whenever a profile is connected. This allows any other bluetooth 491 // profiles which are not already connected or in the process of connecting to attempt to 492 // connect to the device that initiated the connection. In the event that this function is 493 // invoked and there are no current bluetooth connections no new profiles will be connected. 494 @RequiresPermission(allOf = { 495 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 496 android.Manifest.permission.MODIFY_PHONE_STATE, 497 }) processConnectOtherProfiles(BluetoothDevice device)498 private void processConnectOtherProfiles(BluetoothDevice device) { 499 debugLog("processConnectOtherProfiles, device=" + device); 500 if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) { 501 warnLog("processConnectOtherProfiles, adapter is not ON " + mAdapterService.getState()); 502 return; 503 } 504 if (handleAllProfilesDisconnected(device)) { 505 debugLog("processConnectOtherProfiles: all profiles disconnected for " + device); 506 return; 507 } 508 509 HeadsetService hsService = mFactory.getHeadsetService(); 510 A2dpService a2dpService = mFactory.getA2dpService(); 511 PanService panService = mFactory.getPanService(); 512 513 if (hsService != null) { 514 if (!mHeadsetRetrySet.contains(device) && (hsService.getConnectionPolicy(device) 515 == BluetoothProfile.CONNECTION_POLICY_ALLOWED) 516 && (hsService.getConnectionState(device) 517 == BluetoothProfile.STATE_DISCONNECTED)) { 518 debugLog("Retrying connection to Headset with device " + device); 519 mHeadsetRetrySet.add(device); 520 hsService.connect(device); 521 } 522 } 523 if (a2dpService != null) { 524 if (!mA2dpRetrySet.contains(device) && (a2dpService.getConnectionPolicy(device) 525 == BluetoothProfile.CONNECTION_POLICY_ALLOWED) 526 && (a2dpService.getConnectionState(device) 527 == BluetoothProfile.STATE_DISCONNECTED)) { 528 debugLog("Retrying connection to A2DP with device " + device); 529 mA2dpRetrySet.add(device); 530 a2dpService.connect(device); 531 } 532 } 533 if (panService != null) { 534 List<BluetoothDevice> panConnDevList = panService.getConnectedDevices(); 535 // TODO: the panConnDevList.isEmpty() check below should be removed once 536 // Multi-PAN is supported. 537 if (panConnDevList.isEmpty() && (panService.getConnectionPolicy(device) 538 == BluetoothProfile.CONNECTION_POLICY_ALLOWED) 539 && (panService.getConnectionState(device) 540 == BluetoothProfile.STATE_DISCONNECTED)) { 541 debugLog("Retrying connection to PAN with device " + device); 542 panService.connect(device); 543 } 544 } 545 } 546 debugLog(String msg)547 private static void debugLog(String msg) { 548 if (DBG) { 549 Log.i(TAG, msg); 550 } 551 } 552 warnLog(String msg)553 private static void warnLog(String msg) { 554 Log.w(TAG, msg); 555 } 556 errorLog(String msg)557 private static void errorLog(String msg) { 558 Log.e(TAG, msg); 559 } 560 } 561