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