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