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