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