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