• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static com.android.bluetooth.Utils.isDualModeAudioEnabled;
20 
21 import android.annotation.RequiresPermission;
22 import android.bluetooth.BluetoothA2dp;
23 import android.bluetooth.BluetoothAdapter;
24 import android.bluetooth.BluetoothCsipSetCoordinator;
25 import android.bluetooth.BluetoothDevice;
26 import android.bluetooth.BluetoothHeadset;
27 import android.bluetooth.BluetoothHearingAid;
28 import android.bluetooth.BluetoothLeAudio;
29 import android.bluetooth.BluetoothProfile;
30 import android.bluetooth.BluetoothUuid;
31 import android.bluetooth.BluetoothVolumeControl;
32 import android.content.BroadcastReceiver;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.os.Handler;
37 import android.os.Looper;
38 import android.os.Message;
39 import android.os.ParcelUuid;
40 import android.os.SystemProperties;
41 import android.provider.DeviceConfig;
42 import android.util.Log;
43 
44 import com.android.bluetooth.R;
45 import com.android.bluetooth.Utils;
46 import com.android.bluetooth.a2dp.A2dpService;
47 import com.android.bluetooth.bas.BatteryService;
48 import com.android.bluetooth.bass_client.BassClientService;
49 import com.android.bluetooth.btservice.storage.DatabaseManager;
50 import com.android.bluetooth.csip.CsipSetCoordinatorService;
51 import com.android.bluetooth.hap.HapClientService;
52 import com.android.bluetooth.hearingaid.HearingAidService;
53 import com.android.bluetooth.hfp.HeadsetService;
54 import com.android.bluetooth.hid.HidHostService;
55 import com.android.bluetooth.le_audio.LeAudioService;
56 import com.android.bluetooth.pan.PanService;
57 import com.android.bluetooth.vc.VolumeControlService;
58 import com.android.internal.annotations.VisibleForTesting;
59 
60 import java.util.HashSet;
61 import java.util.List;
62 import java.util.Objects;
63 
64 // Describes the phone policy
65 //
66 // The policy should be as decoupled from the stack as possible. In an ideal world we should not
67 // need to have this policy talk with any non-public APIs and one way to enforce that would be to
68 // keep this file outside the Bluetooth process. Unfortunately, keeping a separate process alive is
69 // an expensive and a tedious task.
70 //
71 // Best practices:
72 // a) PhonePolicy should be ALL private methods
73 //    -- Use broadcasts which can be listened in on the BroadcastReceiver
74 // b) NEVER call from the PhonePolicy into the Java stack, unless public APIs. It is OK to call into
75 // the non public versions as long as public versions exist (so that a 3rd party policy can mimick)
76 // us.
77 //
78 // Policy description:
79 //
80 // Policies are usually governed by outside events that may warrant an action. We talk about various
81 // events and the resulting outcome from this policy:
82 //
83 // 1. Adapter turned ON: At this point we will try to auto-connect the (device, profile) pairs which
84 // have PRIORITY_AUTO_CONNECT. The fact that we *only* auto-connect Headset and A2DP is something
85 // that is hardcoded and specific to phone policy (see autoConnect() function)
86 // 2. When the profile connection-state changes: At this point if a new profile gets CONNECTED we
87 // will try to connect other profiles on the same device. This is to avoid collision if devices
88 // somehow end up trying to connect at same time or general connection issues.
89 class PhonePolicy {
90     private static final boolean DBG = true;
91     private static final String TAG = "BluetoothPhonePolicy";
92 
93     // Message types for the handler (internal messages generated by intents or timeouts)
94     private static final int MESSAGE_PROFILE_CONNECTION_STATE_CHANGED = 1;
95     private static final int MESSAGE_CONNECT_OTHER_PROFILES = 3;
96     private static final int MESSAGE_ADAPTER_STATE_TURNED_ON = 4;
97     private static final int MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED = 5;
98     private static final int MESSAGE_DEVICE_CONNECTED = 6;
99 
100     @VisibleForTesting static final String AUTO_CONNECT_PROFILES_PROPERTY =
101             "bluetooth.auto_connect_profiles.enabled";
102     private static final String CONFIG_LE_AUDIO_ENABLED_BY_DEFAULT = "le_audio_enabled_by_default";
103 
104     private static boolean sLeAudioEnabledByDefault = DeviceConfig.getBoolean(
105             DeviceConfig.NAMESPACE_BLUETOOTH, CONFIG_LE_AUDIO_ENABLED_BY_DEFAULT, false);
106 
107     // Timeouts
108     @VisibleForTesting static int sConnectOtherProfilesTimeoutMillis = 6000; // 6s
109 
110     private DatabaseManager mDatabaseManager;
111     private final AdapterService mAdapterService;
112     private final ServiceFactory mFactory;
113     private final Handler mHandler;
114     private final HashSet<BluetoothDevice> mHeadsetRetrySet = new HashSet<>();
115     private final HashSet<BluetoothDevice> mA2dpRetrySet = new HashSet<>();
116     private final HashSet<BluetoothDevice> mConnectOtherProfilesDeviceSet = new HashSet<>();
117     @VisibleForTesting boolean mAutoConnectProfilesSupported;
118 
119     // Broadcast receiver for all changes to states of various profiles
120     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
121         @Override
122         public void onReceive(Context context, Intent intent) {
123             String action = intent.getAction();
124             if (action == null) {
125                 errorLog("Received intent with null action");
126                 return;
127             }
128             switch (action) {
129                 case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
130                     mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED,
131                             BluetoothProfile.HEADSET, -1, // No-op argument
132                             intent).sendToTarget();
133                     break;
134                 case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
135                     mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED,
136                             BluetoothProfile.A2DP, -1, // No-op argument
137                             intent).sendToTarget();
138                     break;
139                 case BluetoothCsipSetCoordinator.ACTION_CSIS_CONNECTION_STATE_CHANGED:
140                     mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED,
141                             BluetoothProfile.CSIP_SET_COORDINATOR, -1, // No-op argument
142                             intent).sendToTarget();
143                     break;
144                 case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED:
145                     mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED,
146                             BluetoothProfile.LE_AUDIO, -1, // No-op argument
147                             intent).sendToTarget();
148                     break;
149                 case BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED:
150                     mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED,
151                             BluetoothProfile.VOLUME_CONTROL, -1, // No-op argument
152                             intent).sendToTarget();
153                     break;
154                 case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
155                     mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED,
156                             BluetoothProfile.A2DP, -1, // No-op argument
157                             intent).sendToTarget();
158                     break;
159                 case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED:
160                     mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED,
161                             BluetoothProfile.HEADSET, -1, // No-op argument
162                             intent).sendToTarget();
163                     break;
164                 case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
165                     mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED,
166                             BluetoothProfile.HEARING_AID, -1, // No-op argument
167                             intent).sendToTarget();
168                     break;
169                 case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED:
170                     mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED,
171                             BluetoothProfile.LE_AUDIO, -1, // No-op argument
172                             intent).sendToTarget();
173                     break;
174                 case BluetoothAdapter.ACTION_STATE_CHANGED:
175                     // Only pass the message on if the adapter has actually changed state from
176                     // non-ON to ON. NOTE: ON is the state depicting BREDR ON and not just BLE ON.
177                     int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
178                     if (newState == BluetoothAdapter.STATE_ON) {
179                         mHandler.obtainMessage(MESSAGE_ADAPTER_STATE_TURNED_ON).sendToTarget();
180                     }
181                     break;
182                 case BluetoothDevice.ACTION_ACL_CONNECTED:
183                     mHandler.obtainMessage(MESSAGE_DEVICE_CONNECTED, intent).sendToTarget();
184                     break;
185                 default:
186                     Log.e(TAG, "Received unexpected intent, action=" + action);
187                     break;
188             }
189         }
190     };
191 
192     @VisibleForTesting
getBroadcastReceiver()193     BroadcastReceiver getBroadcastReceiver() {
194         return mReceiver;
195     }
196 
197     // Handler to handoff intents to class thread
198     class PhonePolicyHandler extends Handler {
PhonePolicyHandler(Looper looper)199         PhonePolicyHandler(Looper looper) {
200             super(looper);
201         }
202 
203         @Override
handleMessage(Message msg)204         public void handleMessage(Message msg) {
205             switch (msg.what) {
206                 case MESSAGE_PROFILE_CONNECTION_STATE_CHANGED: {
207                     Intent intent = (Intent) msg.obj;
208                     BluetoothDevice device =
209                             intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
210                     int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
211                     int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
212                     processProfileStateChanged(device, msg.arg1, nextState, prevState);
213                 }
214                 break;
215 
216                 case MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED: {
217                     Intent intent = (Intent) msg.obj;
218                     BluetoothDevice activeDevice =
219                             intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
220                     processActiveDeviceChanged(activeDevice, msg.arg1);
221                 }
222                 break;
223 
224                 case MESSAGE_CONNECT_OTHER_PROFILES: {
225                     // Called when we try connect some profiles in processConnectOtherProfiles but
226                     // we send a delayed message to try connecting the remaining profiles
227                     BluetoothDevice device = (BluetoothDevice) msg.obj;
228                     processConnectOtherProfiles(device);
229                     mConnectOtherProfilesDeviceSet.remove(device);
230                     break;
231                 }
232                 case MESSAGE_ADAPTER_STATE_TURNED_ON:
233                     // Call auto connect when adapter switches state to ON
234                     resetStates();
235                     autoConnect();
236                     break;
237                 case MESSAGE_DEVICE_CONNECTED:
238                     Intent intent = (Intent) msg.obj;
239                     BluetoothDevice device =
240                             intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
241                     processDeviceConnected(device);
242             }
243         }
244     }
245 
246     ;
247 
248     // Policy API functions for lifecycle management (protected)
start()249     protected void start() {
250         IntentFilter filter = new IntentFilter();
251         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
252         filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
253         filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
254         filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
255         filter.addAction(BluetoothCsipSetCoordinator.ACTION_CSIS_CONNECTION_STATE_CHANGED);
256         filter.addAction(BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED);
257         filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
258         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
259         filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
260         filter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
261         filter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
262         filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
263         mAdapterService.registerReceiver(mReceiver, filter);
264     }
265 
cleanup()266     protected void cleanup() {
267         mAdapterService.unregisterReceiver(mReceiver);
268         resetStates();
269     }
270 
PhonePolicy(AdapterService service, ServiceFactory factory)271     PhonePolicy(AdapterService service, ServiceFactory factory) {
272         mAdapterService = service;
273         mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(),
274                 "DatabaseManager cannot be null when PhonePolicy starts");
275         mFactory = factory;
276         mHandler = new PhonePolicyHandler(service.getMainLooper());
277         mAutoConnectProfilesSupported = SystemProperties.getBoolean(
278                 AUTO_CONNECT_PROFILES_PROPERTY, false);
279     }
280 
281     // Policy implementation, all functions MUST be private
282     @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
processInitProfilePriorities(BluetoothDevice device, ParcelUuid[] uuids)283     private void processInitProfilePriorities(BluetoothDevice device, ParcelUuid[] uuids) {
284         debugLog("processInitProfilePriorities() - device " + device);
285         HidHostService hidService = mFactory.getHidHostService();
286         A2dpService a2dpService = mFactory.getA2dpService();
287         HeadsetService headsetService = mFactory.getHeadsetService();
288         PanService panService = mFactory.getPanService();
289         HearingAidService hearingAidService = mFactory.getHearingAidService();
290         LeAudioService leAudioService = mFactory.getLeAudioService();
291         CsipSetCoordinatorService csipSetCoordinatorService =
292              mFactory.getCsipSetCoordinatorService();
293         VolumeControlService volumeControlService =
294              mFactory.getVolumeControlService();
295         HapClientService hapClientService = mFactory.getHapClientService();
296         BassClientService bcService = mFactory.getBassClientService();
297         BatteryService batteryService = mFactory.getBatteryService();
298 
299         boolean isLeAudioProfileAllowed = (leAudioService != null)
300                 && Utils.arrayContains(uuids, BluetoothUuid.LE_AUDIO)
301                 && (leAudioService.getConnectionPolicy(device)
302                     != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN)
303                 && mAdapterService.isLeAudioAllowed(device)
304                 && (sLeAudioEnabledByDefault || isDualModeAudioEnabled());
305 
306         // Set profile priorities only for the profiles discovered on the remote device.
307         // This avoids needless auto-connect attempts to profiles non-existent on the remote device
308         if ((hidService != null) && (Utils.arrayContains(uuids, BluetoothUuid.HID)
309                 || Utils.arrayContains(uuids, BluetoothUuid.HOGP)) && (
310                 hidService.getConnectionPolicy(device)
311                         == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
312             if (mAutoConnectProfilesSupported) {
313                 hidService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
314             } else {
315                 mAdapterService.getDatabase().setProfileConnectionPolicy(device,
316                         BluetoothProfile.HID_HOST, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
317             }
318         }
319 
320         if ((headsetService != null)
321                 && ((Utils.arrayContains(uuids, BluetoothUuid.HSP)
322                     || Utils.arrayContains(uuids, BluetoothUuid.HFP))
323                 && (headsetService.getConnectionPolicy(device)
324                     == BluetoothProfile.CONNECTION_POLICY_UNKNOWN))) {
325             if (!isDualModeAudioEnabled() && isLeAudioProfileAllowed) {
326                 debugLog("clear hfp profile priority for the le audio dual mode device "
327                         + device);
328                 mAdapterService.getDatabase().setProfileConnectionPolicy(device,
329                         BluetoothProfile.HEADSET, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
330             } else {
331                 if (mAutoConnectProfilesSupported) {
332                     headsetService.setConnectionPolicy(device,
333                             BluetoothProfile.CONNECTION_POLICY_ALLOWED);
334                 } else {
335                     mAdapterService.getDatabase().setProfileConnectionPolicy(device,
336                             BluetoothProfile.HEADSET, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
337                 }
338             }
339         }
340 
341         if ((a2dpService != null)
342                 && (Utils.arrayContains(uuids, BluetoothUuid.A2DP_SINK)
343                     || Utils.arrayContains(uuids, BluetoothUuid.ADV_AUDIO_DIST))
344                 && (a2dpService.getConnectionPolicy(device)
345                     == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
346             if (!isDualModeAudioEnabled() && isLeAudioProfileAllowed) {
347                 debugLog("clear a2dp profile priority for the le audio dual mode device "
348                         + device);
349                 mAdapterService.getDatabase().setProfileConnectionPolicy(device,
350                         BluetoothProfile.A2DP, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
351             } else {
352                 if (mAutoConnectProfilesSupported) {
353                     a2dpService.setConnectionPolicy(device,
354                             BluetoothProfile.CONNECTION_POLICY_ALLOWED);
355                 } else {
356                     mAdapterService.getDatabase().setProfileConnectionPolicy(device,
357                             BluetoothProfile.A2DP, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
358                 }
359             }
360         }
361 
362         // CSIP should be connected prior to LE Audio
363         if ((csipSetCoordinatorService != null)
364                 && (Utils.arrayContains(uuids, BluetoothUuid.COORDINATED_SET))
365                 && (csipSetCoordinatorService.getConnectionPolicy(device)
366                         == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
367             // Always allow CSIP during pairing process regardless of LE audio preference
368             if (mAutoConnectProfilesSupported) {
369                 csipSetCoordinatorService.setConnectionPolicy(device,
370                         BluetoothProfile.CONNECTION_POLICY_ALLOWED);
371             } else {
372                 mAdapterService.getDatabase().setProfileConnectionPolicy(device,
373                         BluetoothProfile.CSIP_SET_COORDINATOR,
374                         BluetoothProfile.CONNECTION_POLICY_ALLOWED);
375             }
376         }
377 
378         // If we do not have a stored priority for HFP/A2DP (all roles) then default to on.
379         if ((panService != null)
380                 && (Utils.arrayContains(uuids, BluetoothUuid.PANU)
381                 && (panService.getConnectionPolicy(device)
382                     == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)
383                 && mAdapterService.getResources()
384                     .getBoolean(R.bool.config_bluetooth_pan_enable_autoconnect))) {
385             if (mAutoConnectProfilesSupported) {
386                 panService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
387             } else {
388                 mAdapterService.getDatabase().setProfileConnectionPolicy(device,
389                         BluetoothProfile.PAN, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
390             }
391         }
392 
393         if ((leAudioService != null)
394                 && Utils.arrayContains(uuids, BluetoothUuid.LE_AUDIO)
395                 && (leAudioService.getConnectionPolicy(device)
396                     == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
397             if (isLeAudioProfileAllowed) {
398                 debugLog("setting le audio profile priority for device " + device);
399                 if (mAutoConnectProfilesSupported) {
400                     leAudioService.setConnectionPolicy(device,
401                             BluetoothProfile.CONNECTION_POLICY_ALLOWED);
402                 } else {
403                     mAdapterService.getDatabase().setProfileConnectionPolicy(device,
404                             BluetoothProfile.LE_AUDIO, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
405                 }
406             } else {
407                 debugLog("clear LEA profile priority because LE audio is not allowed");
408                 mAdapterService.getDatabase().setProfileConnectionPolicy(device,
409                         BluetoothProfile.LE_AUDIO, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
410             }
411         }
412 
413         if ((hearingAidService != null)
414                 && Utils.arrayContains(uuids, BluetoothUuid.HEARING_AID)
415                 && (hearingAidService.getConnectionPolicy(device)
416                     == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
417             if (isLeAudioProfileAllowed) {
418                 debugLog("LE Audio preferred over ASHA for device " + device);
419                 mAdapterService.getDatabase().setProfileConnectionPolicy(device,
420                         BluetoothProfile.HEARING_AID, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
421             } else {
422                 debugLog("setting hearing aid profile priority for device " + device);
423                 if (mAutoConnectProfilesSupported) {
424                     hearingAidService.setConnectionPolicy(device,
425                             BluetoothProfile.CONNECTION_POLICY_ALLOWED);
426                 } else {
427                     mAdapterService.getDatabase().setProfileConnectionPolicy(device,
428                             BluetoothProfile.HEARING_AID,
429                             BluetoothProfile.CONNECTION_POLICY_ALLOWED);
430                 }
431             }
432         }
433 
434         if ((volumeControlService != null)
435                 && Utils.arrayContains(uuids, BluetoothUuid.VOLUME_CONTROL)
436                 && (volumeControlService.getConnectionPolicy(device)
437                     == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
438             if (isLeAudioProfileAllowed) {
439                 debugLog("setting volume control profile priority for device " + device);
440                 if (mAutoConnectProfilesSupported) {
441                     volumeControlService.setConnectionPolicy(device,
442                             BluetoothProfile.CONNECTION_POLICY_ALLOWED);
443                 } else {
444                     mAdapterService.getDatabase().setProfileConnectionPolicy(device,
445                             BluetoothProfile.VOLUME_CONTROL,
446                             BluetoothProfile.CONNECTION_POLICY_ALLOWED);
447                 }
448             } else {
449                 debugLog("clear VCP priority because dual mode is disabled by default");
450                 mAdapterService.getDatabase().setProfileConnectionPolicy(device,
451                         BluetoothProfile.VOLUME_CONTROL,
452                         BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
453             }
454         }
455 
456         if ((hapClientService != null)
457                 && Utils.arrayContains(uuids, BluetoothUuid.HAS)
458                 && (hapClientService.getConnectionPolicy(device)
459                     == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
460             debugLog("setting hearing access profile priority for device " + device);
461             if (isLeAudioProfileAllowed) {
462                 if (mAutoConnectProfilesSupported) {
463                     hapClientService.setConnectionPolicy(device,
464                             BluetoothProfile.CONNECTION_POLICY_ALLOWED);
465                 } else {
466                     mAdapterService.getDatabase().setProfileConnectionPolicy(device,
467                             BluetoothProfile.HAP_CLIENT,
468                             BluetoothProfile.CONNECTION_POLICY_ALLOWED);
469                 }
470             } else {
471                 mAdapterService.getDatabase().setProfileConnectionPolicy(device,
472                         BluetoothProfile.HAP_CLIENT,
473                         BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
474             }
475         }
476 
477         if ((bcService != null)
478                 && Utils.arrayContains(uuids, BluetoothUuid.BASS)
479                 && (bcService.getConnectionPolicy(device)
480                     == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
481             debugLog("setting broadcast assistant profile priority for device " + device);
482             if (mAutoConnectProfilesSupported) {
483                 bcService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
484             } else {
485                 mAdapterService.getDatabase().setProfileConnectionPolicy(device,
486                         BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT,
487                         BluetoothProfile.CONNECTION_POLICY_ALLOWED);
488             }
489         }
490 
491         if ((batteryService != null)
492                 && Utils.arrayContains(uuids, BluetoothUuid.BATTERY)
493                 && (batteryService.getConnectionPolicy(device)
494                     == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
495             debugLog("setting battery profile priority for device " + device);
496             if (mAutoConnectProfilesSupported) {
497                 batteryService.setConnectionPolicy(device,
498                         BluetoothProfile.CONNECTION_POLICY_ALLOWED);
499             } else {
500                 mAdapterService.getDatabase().setProfileConnectionPolicy(device,
501                         BluetoothProfile.BATTERY, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
502             }
503         }
504     }
505 
506     @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
processProfileStateChanged(BluetoothDevice device, int profileId, int nextState, int prevState)507     private void processProfileStateChanged(BluetoothDevice device, int profileId, int nextState,
508             int prevState) {
509         debugLog("processProfileStateChanged, device=" + device + ", profile=" + profileId + ", "
510                 + prevState + " -> " + nextState);
511         if (((profileId == BluetoothProfile.A2DP) || (profileId == BluetoothProfile.HEADSET)
512                 || (profileId == BluetoothProfile.LE_AUDIO)
513                 || (profileId == BluetoothProfile.CSIP_SET_COORDINATOR)
514                 || (profileId == BluetoothProfile.VOLUME_CONTROL)
515                 || (profileId == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT))) {
516             if (nextState == BluetoothProfile.STATE_CONNECTED) {
517                 switch (profileId) {
518                     case BluetoothProfile.A2DP:
519                         mA2dpRetrySet.remove(device);
520                         break;
521                     case BluetoothProfile.HEADSET:
522                         mHeadsetRetrySet.remove(device);
523                         break;
524                 }
525                 connectOtherProfile(device);
526             }
527             if (nextState == BluetoothProfile.STATE_DISCONNECTED) {
528                 if (profileId == BluetoothProfile.A2DP
529                         && (prevState == BluetoothProfile.STATE_CONNECTING
530                         || prevState == BluetoothProfile.STATE_DISCONNECTING)) {
531                     mDatabaseManager.setDisconnection(device);
532                 }
533                 handleAllProfilesDisconnected(device);
534             }
535         }
536     }
537 
538     /**
539      * Updates the last connection date in the connection order database for the newly active device
540      * if connected to the A2DP profile. If this is a dual mode audio device (supports classic and
541      * LE Audio), LE Audio is made active, and {@link Utils#isDualModeAudioEnabled()} is false,
542      * A2DP and HFP will be disconnected.
543      *
544      * @param device is the device we just made the active device
545      */
processActiveDeviceChanged(BluetoothDevice device, int profileId)546     private void processActiveDeviceChanged(BluetoothDevice device, int profileId) {
547         debugLog("processActiveDeviceChanged, device=" + device + ", profile=" + profileId
548                 + " isDualModeAudioEnabled=" + isDualModeAudioEnabled());
549 
550         if (device != null) {
551             mDatabaseManager.setConnection(device, profileId == BluetoothProfile.A2DP);
552 
553             if (isDualModeAudioEnabled()) return;
554             if (profileId == BluetoothProfile.LE_AUDIO) {
555                 A2dpService a2dpService = mFactory.getA2dpService();
556                 HeadsetService hsService = mFactory.getHeadsetService();
557                 LeAudioService leAudioService = mFactory.getLeAudioService();
558                 if (leAudioService == null) {
559                     debugLog("processActiveDeviceChanged: LeAudioService is null");
560                     return;
561                 }
562                 List<BluetoothDevice> leAudioActiveGroupDevices =
563                         leAudioService.getGroupDevices(leAudioService.getGroupId(device));
564 
565                 // Disable classic audio profiles for all group devices as lead can change
566                 for (BluetoothDevice activeGroupDevice: leAudioActiveGroupDevices) {
567                     if (hsService != null) {
568                         debugLog("Disable HFP for the LE audio dual mode group device "
569                                 + activeGroupDevice);
570                         hsService.setConnectionPolicy(activeGroupDevice,
571                                 BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
572                     }
573                     if (a2dpService != null) {
574                         debugLog("Disable A2DP for the LE audio dual mode group device "
575                                 + activeGroupDevice);
576                         a2dpService.setConnectionPolicy(activeGroupDevice,
577                                 BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
578                     }
579                 }
580             }
581         }
582     }
583 
processDeviceConnected(BluetoothDevice device)584     private void processDeviceConnected(BluetoothDevice device) {
585         debugLog("processDeviceConnected, device=" + device);
586         mDatabaseManager.setConnection(device, false);
587     }
588 
589     @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
handleAllProfilesDisconnected(BluetoothDevice device)590     private boolean handleAllProfilesDisconnected(BluetoothDevice device) {
591         boolean atLeastOneProfileConnectedForDevice = false;
592         boolean allProfilesEmpty = true;
593         HeadsetService hsService = mFactory.getHeadsetService();
594         A2dpService a2dpService = mFactory.getA2dpService();
595         PanService panService = mFactory.getPanService();
596         LeAudioService leAudioService = mFactory.getLeAudioService();
597         CsipSetCoordinatorService csipSetCooridnatorService =
598         mFactory.getCsipSetCoordinatorService();
599 
600         if (hsService != null) {
601             List<BluetoothDevice> hsConnDevList = hsService.getConnectedDevices();
602             allProfilesEmpty &= hsConnDevList.isEmpty();
603             atLeastOneProfileConnectedForDevice |= hsConnDevList.contains(device);
604         }
605         if (a2dpService != null) {
606             List<BluetoothDevice> a2dpConnDevList = a2dpService.getConnectedDevices();
607             allProfilesEmpty &= a2dpConnDevList.isEmpty();
608             atLeastOneProfileConnectedForDevice |= a2dpConnDevList.contains(device);
609         }
610         if (csipSetCooridnatorService != null) {
611             List<BluetoothDevice> csipConnDevList = csipSetCooridnatorService.getConnectedDevices();
612             allProfilesEmpty &= csipConnDevList.isEmpty();
613             atLeastOneProfileConnectedForDevice |= csipConnDevList.contains(device);
614         }
615         if (panService != null) {
616             List<BluetoothDevice> panConnDevList = panService.getConnectedDevices();
617             allProfilesEmpty &= panConnDevList.isEmpty();
618             atLeastOneProfileConnectedForDevice |= panConnDevList.contains(device);
619         }
620         if (leAudioService != null) {
621             List<BluetoothDevice> leAudioConnDevList = leAudioService.getConnectedDevices();
622             allProfilesEmpty &= leAudioConnDevList.isEmpty();
623             atLeastOneProfileConnectedForDevice |= leAudioConnDevList.contains(device);
624         }
625 
626         if (!atLeastOneProfileConnectedForDevice) {
627             // Consider this device as fully disconnected, don't bother connecting others
628             debugLog("handleAllProfilesDisconnected: all profiles disconnected for " + device);
629             mHeadsetRetrySet.remove(device);
630             mA2dpRetrySet.remove(device);
631             if (allProfilesEmpty) {
632                 debugLog("handleAllProfilesDisconnected: all profiles disconnected for all"
633                         + " devices");
634                 // reset retry status so that in the next round we can start retrying connections
635                 resetStates();
636             }
637             return true;
638         }
639         return false;
640     }
641 
resetStates()642     private void resetStates() {
643         mHeadsetRetrySet.clear();
644         mA2dpRetrySet.clear();
645     }
646 
647     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
autoConnect()648     private void autoConnect() {
649         if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) {
650             errorLog("autoConnect: BT is not ON. Exiting autoConnect");
651             return;
652         }
653 
654         if (!mAdapterService.isQuietModeEnabled()) {
655             debugLog("autoConnect: Initiate auto connection on BT on...");
656             final BluetoothDevice mostRecentlyActiveA2dpDevice =
657                     mDatabaseManager.getMostRecentlyConnectedA2dpDevice();
658             if (mostRecentlyActiveA2dpDevice == null) {
659                 errorLog("autoConnect: most recently active a2dp device is null");
660                 return;
661             }
662             debugLog("autoConnect: Device " + mostRecentlyActiveA2dpDevice
663                     + " attempting auto connection");
664             autoConnectHeadset(mostRecentlyActiveA2dpDevice);
665             autoConnectA2dp(mostRecentlyActiveA2dpDevice);
666             autoConnectHidHost(mostRecentlyActiveA2dpDevice);
667         } else {
668             debugLog("autoConnect() - BT is in quiet mode. Not initiating auto connections");
669         }
670     }
671 
autoConnectA2dp(BluetoothDevice device)672     private void autoConnectA2dp(BluetoothDevice device) {
673         final A2dpService a2dpService = mFactory.getA2dpService();
674         if (a2dpService == null) {
675             warnLog("autoConnectA2dp: service is null, failed to connect to " + device);
676             return;
677         }
678         int a2dpConnectionPolicy = a2dpService.getConnectionPolicy(device);
679         if (a2dpConnectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
680             debugLog("autoConnectA2dp: connecting A2DP with " + device);
681             a2dpService.connect(device);
682         } else {
683             debugLog("autoConnectA2dp: skipped auto-connect A2DP with device " + device
684                     + " connectionPolicy " + a2dpConnectionPolicy);
685         }
686     }
687 
688     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
autoConnectHeadset(BluetoothDevice device)689     private void autoConnectHeadset(BluetoothDevice device) {
690         final HeadsetService hsService = mFactory.getHeadsetService();
691         if (hsService == null) {
692             warnLog("autoConnectHeadset: service is null, failed to connect to " + device);
693             return;
694         }
695         int headsetConnectionPolicy = hsService.getConnectionPolicy(device);
696         if (headsetConnectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
697             debugLog("autoConnectHeadset: Connecting HFP with " + device);
698             hsService.connect(device);
699         } else {
700             debugLog("autoConnectHeadset: skipped auto-connect HFP with device " + device
701                     + " connectionPolicy " + headsetConnectionPolicy);
702         }
703     }
704 
705     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
autoConnectHidHost(BluetoothDevice device)706     private void autoConnectHidHost(BluetoothDevice device) {
707         final HidHostService hidHostService = mFactory.getHidHostService();
708         if (hidHostService == null) {
709             warnLog("autoConnectHidHost: service is null, failed to connect to " + device);
710             return;
711         }
712         int hidHostConnectionPolicy = hidHostService.getConnectionPolicy(device);
713         if (hidHostConnectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
714             debugLog("autoConnectHidHost: Connecting HID with " + device);
715             hidHostService.connect(device);
716         } else {
717             debugLog("autoConnectHidHost: skipped auto-connect HID with device " + device
718                     + " connectionPolicy " + hidHostConnectionPolicy);
719         }
720     }
721 
connectOtherProfile(BluetoothDevice device)722     private void connectOtherProfile(BluetoothDevice device) {
723         if (mAdapterService.isQuietModeEnabled()) {
724             debugLog("connectOtherProfile: in quiet mode, skip connect other profile " + device);
725             return;
726         }
727         if (mConnectOtherProfilesDeviceSet.contains(device)) {
728             debugLog("connectOtherProfile: already scheduled callback for " + device);
729             return;
730         }
731         mConnectOtherProfilesDeviceSet.add(device);
732         Message m = mHandler.obtainMessage(MESSAGE_CONNECT_OTHER_PROFILES);
733         m.obj = device;
734         mHandler.sendMessageDelayed(m, sConnectOtherProfilesTimeoutMillis);
735     }
736 
737     // This function is called whenever a profile is connected.  This allows any other bluetooth
738     // profiles which are not already connected or in the process of connecting to attempt to
739     // connect to the device that initiated the connection.  In the event that this function is
740     // invoked and there are no current bluetooth connections no new profiles will be connected.
741     @RequiresPermission(allOf = {
742             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
743             android.Manifest.permission.MODIFY_PHONE_STATE,
744     })
processConnectOtherProfiles(BluetoothDevice device)745     private void processConnectOtherProfiles(BluetoothDevice device) {
746         debugLog("processConnectOtherProfiles, device=" + device);
747         if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) {
748             warnLog("processConnectOtherProfiles, adapter is not ON " + mAdapterService.getState());
749             return;
750         }
751         if (handleAllProfilesDisconnected(device)) {
752             debugLog("processConnectOtherProfiles: all profiles disconnected for " + device);
753             return;
754         }
755 
756         HeadsetService hsService = mFactory.getHeadsetService();
757         A2dpService a2dpService = mFactory.getA2dpService();
758         PanService panService = mFactory.getPanService();
759         LeAudioService leAudioService = mFactory.getLeAudioService();
760         CsipSetCoordinatorService csipSetCooridnatorService =
761             mFactory.getCsipSetCoordinatorService();
762         VolumeControlService volumeControlService =
763             mFactory.getVolumeControlService();
764         BatteryService batteryService = mFactory.getBatteryService();
765         HidHostService hidHostService = mFactory.getHidHostService();
766         BassClientService bcService = mFactory.getBassClientService();
767 
768         if (hsService != null) {
769             if (!mHeadsetRetrySet.contains(device) && (hsService.getConnectionPolicy(device)
770                     == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
771                     && (hsService.getConnectionState(device)
772                     == BluetoothProfile.STATE_DISCONNECTED)) {
773                 debugLog("Retrying connection to Headset with device " + device);
774                 mHeadsetRetrySet.add(device);
775                 hsService.connect(device);
776             }
777         }
778         if (a2dpService != null) {
779             if (!mA2dpRetrySet.contains(device) && (a2dpService.getConnectionPolicy(device)
780                     == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
781                     && (a2dpService.getConnectionState(device)
782                     == BluetoothProfile.STATE_DISCONNECTED)) {
783                 debugLog("Retrying connection to A2DP with device " + device);
784                 mA2dpRetrySet.add(device);
785                 a2dpService.connect(device);
786             }
787         }
788         if (panService != null) {
789             List<BluetoothDevice> panConnDevList = panService.getConnectedDevices();
790             // TODO: the panConnDevList.isEmpty() check below should be removed once
791             // Multi-PAN is supported.
792             if (panConnDevList.isEmpty() && (panService.getConnectionPolicy(device)
793                     == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
794                     && (panService.getConnectionState(device)
795                     == BluetoothProfile.STATE_DISCONNECTED)) {
796                 debugLog("Retrying connection to PAN with device " + device);
797                 panService.connect(device);
798             }
799         }
800         if (leAudioService != null) {
801             List<BluetoothDevice> leAudioConnDevList = leAudioService.getConnectedDevices();
802             if (!leAudioConnDevList.contains(device) && (leAudioService.getConnectionPolicy(device)
803                     == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
804                     && (leAudioService.getConnectionState(device)
805                     == BluetoothProfile.STATE_DISCONNECTED)
806                     && mAdapterService.isLeAudioAllowed(device)) {
807                 debugLog("Retrying connection to LEAudio with device " + device);
808                 leAudioService.connect(device);
809             }
810         }
811         if (csipSetCooridnatorService != null) {
812             List<BluetoothDevice> csipConnDevList = csipSetCooridnatorService.getConnectedDevices();
813             if (!csipConnDevList.contains(device) && (csipSetCooridnatorService.getConnectionPolicy(device)
814                     == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
815                     && (csipSetCooridnatorService.getConnectionState(device)
816                     == BluetoothProfile.STATE_DISCONNECTED)) {
817                 debugLog("Retrying connection to CSIP with device " + device);
818                 csipSetCooridnatorService.connect(device);
819             }
820         }
821         if (volumeControlService != null) {
822             List<BluetoothDevice> vcConnDevList = volumeControlService.getConnectedDevices();
823             if (!vcConnDevList.contains(device) && (volumeControlService.getConnectionPolicy(device)
824                     == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
825                     && (volumeControlService.getConnectionState(device)
826                     == BluetoothProfile.STATE_DISCONNECTED)) {
827                 debugLog("Retrying connection to VCP with device " + device);
828                 volumeControlService.connect(device);
829             }
830         }
831         if (batteryService != null) {
832             List<BluetoothDevice> connectedDevices = batteryService.getConnectedDevices();
833             if (!connectedDevices.contains(device) && (batteryService.getConnectionPolicy(device)
834                     == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
835                     && (batteryService.getConnectionState(device)
836                     == BluetoothProfile.STATE_DISCONNECTED)) {
837                 debugLog("Retrying connection to BAS with device " + device);
838                 batteryService.connect(device);
839             }
840         }
841         if (hidHostService != null) {
842             if ((hidHostService.getConnectionPolicy(device)
843                     == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
844                     && (hidHostService.getConnectionState(device)
845                     == BluetoothProfile.STATE_DISCONNECTED)) {
846                 debugLog("Retrying connection to HID with device " + device);
847                 hidHostService.connect(device);
848             }
849         }
850         if (bcService != null) {
851             List<BluetoothDevice> connectedDevices = bcService.getConnectedDevices();
852             if (!connectedDevices.contains(device) && (bcService.getConnectionPolicy(device)
853                     == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
854                     && (bcService.getConnectionState(device)
855                     == BluetoothProfile.STATE_DISCONNECTED)) {
856                 debugLog("Retrying connection to BASS with device " + device);
857                 bcService.connect(device);
858             }
859         }
860     }
861 
862     /**
863      * Direct call prior to sending out {@link BluetoothDevice#ACTION_UUID}. This indicates that
864      * service discovery is complete and passes the UUIDs directly to PhonePolicy.
865      *
866      * @param device is the remote device whose services have been discovered
867      * @param uuids are the services supported by the remote device
868      */
onUuidsDiscovered(BluetoothDevice device, ParcelUuid[] uuids)869     void onUuidsDiscovered(BluetoothDevice device, ParcelUuid[] uuids) {
870         debugLog("onUuidsDiscovered: discovered services for device " + device);
871         if (uuids != null) {
872             processInitProfilePriorities(device, uuids);
873         } else {
874             warnLog("onUuidsDiscovered: uuids is null for device " + device);
875         }
876     }
877 
878     @VisibleForTesting
setLeAudioEnabledByDefaultForTesting(boolean enabled)879     void setLeAudioEnabledByDefaultForTesting(boolean enabled) {
880         sLeAudioEnabledByDefault = enabled;
881     }
882 
debugLog(String msg)883     private static void debugLog(String msg) {
884         if (DBG) {
885             Log.i(TAG, msg);
886         }
887     }
888 
warnLog(String msg)889     private static void warnLog(String msg) {
890         Log.w(TAG, msg);
891     }
892 
errorLog(String msg)893     private static void errorLog(String msg) {
894         Log.e(TAG, msg);
895     }
896 }
897