• 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.car;
18 
19 import android.annotation.Nullable;
20 import android.app.ActivityManager;
21 import android.bluetooth.BluetoothA2dpSink;
22 import android.bluetooth.BluetoothAdapter;
23 import android.bluetooth.BluetoothDevice;
24 import android.bluetooth.BluetoothHeadsetClient;
25 import android.bluetooth.BluetoothMapClient;
26 import android.bluetooth.BluetoothPbapClient;
27 import android.bluetooth.BluetoothProfile;
28 import android.bluetooth.BluetoothUuid;
29 import android.car.hardware.CarPropertyValue;
30 import android.car.hardware.CarSensorEvent;
31 import android.car.hardware.CarSensorManager;
32 import android.car.hardware.ICarSensorEventListener;
33 import android.car.hardware.cabin.CarCabinManager;
34 import android.car.hardware.property.CarPropertyEvent;
35 import android.car.hardware.property.ICarPropertyEventListener;
36 import android.car.ICarBluetoothUserService;
37 import android.car.ICarUserService;
38 
39 import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICES;
40 import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICES;
41 import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICES;
42 
43 import android.car.CarBluetoothManager;
44 import android.os.ParcelUuid;
45 import android.os.Parcelable;
46 import android.os.UserHandle;
47 import android.provider.Settings;
48 
49 import android.content.BroadcastReceiver;
50 import android.content.Context;
51 import android.content.Intent;
52 import android.content.IntentFilter;
53 import android.os.RemoteException;
54 import android.util.Log;
55 
56 import com.android.internal.annotations.VisibleForTesting;
57 
58 import java.lang.StringBuilder;
59 import java.io.PrintWriter;
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.HashMap;
63 import java.util.List;
64 import java.util.Map;
65 import java.util.Set;
66 import java.util.concurrent.locks.ReentrantLock;
67 
68 
69 /**
70  * A Bluetooth Device Connection policy that is specific to the use cases of a Car.  A car's
71  * bluetooth capabilities in terms of the profiles it supports and its use cases are unique.
72  * Hence the CarService manages the policy that drives when and what to connect to.
73  *
74  * When to connect:
75  * The policy can be configured to listen to various vehicle events that are appropriate to
76  * trigger a connection attempt.  Signals like door unlock/open, ignition state changes indicate
77  * user entry and there by attempt to connect to their devices. This removes the need for the user
78  * to manually connect his device everytime they get in a car.
79  *
80  * Which device to connect:
81  * The policy also keeps track of the {Profile : DevicesThatCanConnectOnTheProfile} and when
82  * it is time to connect, picks the device that is appropriate and available.
83  * For every profile, the policy attempts to connect to the last connected device first. The policy
84  * maintains a list of connect-able devices for every profile, in the order of how recently they
85  * connected.  The device that successfully connects on a profile is moved to the top of the list
86  * of devices for that profile, so the next time a connection attempt is made, the policy starts
87  * with the last connected device first.
88  */
89 
90 public class BluetoothDeviceConnectionPolicy {
91     private static final String TAG = "BTDevConnectionPolicy";
92     private static final String SETTINGS_DELIMITER = ",";
93     private static final boolean DBG = false;
94     private final Context mContext;
95     private boolean mInitialized = false;
96     private boolean mUserSpecificInfoInitialized = false;
97     private final Object mSetupLock = new Object();
98 
99     // The main data structure that holds on to the {profile:list of known and connectible devices}
100     private HashMap<Integer, BluetoothDevicesInfo> mProfileToConnectableDevicesMap;
101 
102     // The foll. number of connections are what the Bluetooth services and stack supports
103     // and has been tested with.  MAP and A2DP are limited to one connection only.  HFP and PBAP,
104     // though having the capability to support more than 2, has been tested with 2 connections.
105     private static final int NUM_SUPPORTED_PHONE_CONNECTIONS = 2; // num of HFP and PBAP connections
106     private static final int NUM_SUPPORTED_MSG_CONNECTIONS = 1; // num of MAP connections
107     private static final int NUM_SUPPORTED_MUSIC_CONNECTIONS = 1; // num of A2DP connections
108     private Map<Integer, Integer> mNumSupportedActiveConnections;
109 
110     private BluetoothAutoConnectStateMachine mBluetoothAutoConnectStateMachine;
111     private final BluetoothAdapter mBluetoothAdapter;
112     private BroadcastReceiver mBluetoothBroadcastReceiver;
113 
114     private ICarUserService mCarUserService;
115     private PerUserCarServiceHelper mUserServiceHelper;
116     private ICarBluetoothUserService mCarBluetoothUserService;
117     private ReentrantLock mCarUserServiceAccessLock;
118 
119     // Events that are listened to for triggering an auto-connect:
120     // Cabin events like Door unlock coming from the Cabin Service.
121     private final CarCabinService mCarCabinService;
122     private final CarPropertyListener mCabinEventListener;
123     // Sensor events like Ignition switch ON from the Car Sensor Service
124     private final CarSensorService mCarSensorService;
125     private final CarSensorEventListener mCarSensorEventListener;
126 
127     // PerUserCarService related listeners
128     private final UserServiceConnectionCallback mServiceCallback;
129 
130     // Car Bluetooth Priority Settings Manager
131     private final CarBluetoothService mCarBluetoothService;
132 
133     // The Bluetooth profiles that the CarService will try to auto-connect on.
134     private final List<Integer> mProfilesToConnect;
135     private final List<Integer> mPrioritiesSupported;
136     private static final int MAX_CONNECT_RETRIES = 1;
137     private static final int PROFILE_NOT_AVAILABLE = -1;
138 
139     // Device & Profile currently being connected on
140     private ConnectionParams mConnectionInFlight;
141     // Allow write to Settings.Secure
142     private boolean mAllowReadWriteToSettings = true;
143 
create(Context context, CarCabinService carCabinService, CarSensorService carSensorService, PerUserCarServiceHelper userServiceHelper, CarBluetoothService bluetoothService)144     public static BluetoothDeviceConnectionPolicy create(Context context,
145             CarCabinService carCabinService, CarSensorService carSensorService,
146             PerUserCarServiceHelper userServiceHelper, CarBluetoothService bluetoothService) {
147         return new BluetoothDeviceConnectionPolicy(context, carCabinService, carSensorService,
148                 userServiceHelper, bluetoothService);
149     }
150 
BluetoothDeviceConnectionPolicy(Context context, CarCabinService carCabinService, CarSensorService carSensorService, PerUserCarServiceHelper userServiceHelper, CarBluetoothService bluetoothService)151     private BluetoothDeviceConnectionPolicy(Context context, CarCabinService carCabinService,
152             CarSensorService carSensorService, PerUserCarServiceHelper userServiceHelper,
153             CarBluetoothService bluetoothService) {
154         mContext = context;
155         mCarCabinService = carCabinService;
156         mCarSensorService = carSensorService;
157         mUserServiceHelper = userServiceHelper;
158         mCarBluetoothService = bluetoothService;
159         mCarUserServiceAccessLock = new ReentrantLock();
160         mProfilesToConnect = Arrays.asList(
161                 BluetoothProfile.HEADSET_CLIENT, BluetoothProfile.A2DP_SINK,
162                 BluetoothProfile.PBAP_CLIENT, BluetoothProfile.MAP_CLIENT);
163         mPrioritiesSupported = Arrays.asList(
164                 CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_0,
165                 CarBluetoothManager.BLUETOOTH_DEVICE_CONNECTION_PRIORITY_1
166         );
167         // mNumSupportedActiveConnections is a HashMap of mProfilesToConnect and the number of
168         // connections each profile supports currently.
169         mNumSupportedActiveConnections = new HashMap<>(mProfilesToConnect.size());
170         for (Integer profile : mProfilesToConnect) {
171             switch (profile) {
172                 case BluetoothProfile.HEADSET_CLIENT:
173                     mNumSupportedActiveConnections.put(BluetoothProfile.HEADSET_CLIENT,
174                             NUM_SUPPORTED_PHONE_CONNECTIONS);
175                     break;
176                 case BluetoothProfile.PBAP_CLIENT:
177                     mNumSupportedActiveConnections.put(BluetoothProfile.PBAP_CLIENT,
178                             NUM_SUPPORTED_PHONE_CONNECTIONS);
179                     break;
180                 case BluetoothProfile.A2DP_SINK:
181                     mNumSupportedActiveConnections.put(BluetoothProfile.A2DP_SINK,
182                             NUM_SUPPORTED_MUSIC_CONNECTIONS);
183                     break;
184                 case BluetoothProfile.MAP_CLIENT:
185                     mNumSupportedActiveConnections.put(BluetoothProfile.MAP_CLIENT,
186                             NUM_SUPPORTED_MSG_CONNECTIONS);
187                     break;
188             }
189         }
190 
191         // Listen to Cabin events for triggering auto connect
192         mCabinEventListener = new CarPropertyListener();
193         mCarSensorEventListener = new CarSensorEventListener();
194         // Listen to User switching to connect to per User device.
195         mServiceCallback = new UserServiceConnectionCallback();
196         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
197         if (mBluetoothAdapter == null) {
198             Log.w(TAG, "No Bluetooth Adapter Available");
199         }
200     }
201 
202     /**
203      * ConnectionParams - parameters/objects relevant to the bluetooth connection calls.
204      * This encapsulates the information that is passed around across different methods in the
205      * policy. Contains the bluetooth device {@link BluetoothDevice} and the list of profiles that
206      * we want that device to connect on.
207      * Used as the currency that methods use to talk to each other in the policy.
208      */
209     public static class ConnectionParams {
210         private BluetoothDevice mBluetoothDevice;
211         private Integer mBluetoothProfile;
212 
ConnectionParams()213         public ConnectionParams() {
214             // default constructor
215         }
216 
ConnectionParams(Integer profile)217         public ConnectionParams(Integer profile) {
218             mBluetoothProfile = profile;
219         }
220 
ConnectionParams(Integer profile, BluetoothDevice device)221         public ConnectionParams(Integer profile, BluetoothDevice device) {
222             mBluetoothProfile = profile;
223             mBluetoothDevice = device;
224         }
225 
226         // getters & Setters
setBluetoothDevice(BluetoothDevice device)227         public void setBluetoothDevice(BluetoothDevice device) {
228             mBluetoothDevice = device;
229         }
230 
setBluetoothProfile(Integer profile)231         public void setBluetoothProfile(Integer profile) {
232             mBluetoothProfile = profile;
233         }
234 
getBluetoothDevice()235         public BluetoothDevice getBluetoothDevice() {
236             return mBluetoothDevice;
237         }
238 
getBluetoothProfile()239         public Integer getBluetoothProfile() {
240             return mBluetoothProfile;
241         }
242     }
243 
244     /**
245      * BluetoothBroadcastReceiver receives the bluetooth related intents that are relevant to
246      * connection
247      * and bonding state changes.  Reports the information to the {@link
248      * BluetoothDeviceConnectionPolicy}
249      * for it update its status.
250      */
251     public class BluetoothBroadcastReceiver extends BroadcastReceiver {
252         @Override
onReceive(Context context, Intent intent)253         public void onReceive(Context context, Intent intent) {
254             String action = intent.getAction();
255             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
256             if (DBG) {
257                 if (device != null) {
258                     Log.d(TAG, "Received Intent for device: " + device + " " + action);
259                 } else {
260                     Log.d(TAG, "Received Intent no device: " + action);
261                 }
262             }
263             ConnectionParams connectParams;
264             if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
265                 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
266                         BluetoothDevice.ERROR);
267                 updateBondState(device, bondState);
268 
269             } else if (BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
270                 connectParams = new ConnectionParams(BluetoothProfile.A2DP_SINK, device);
271                 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
272                         BluetoothProfile.STATE_DISCONNECTED);
273                 notifyConnectionStatus(connectParams, currState);
274 
275             } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
276                 connectParams = new ConnectionParams(BluetoothProfile.HEADSET_CLIENT, device);
277                 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
278                         BluetoothProfile.STATE_DISCONNECTED);
279                 notifyConnectionStatus(connectParams, currState);
280 
281             } else if (BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
282                 connectParams = new ConnectionParams(BluetoothProfile.PBAP_CLIENT, device);
283                 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
284                         BluetoothProfile.STATE_DISCONNECTED);
285                 notifyConnectionStatus(connectParams, currState);
286 
287             } else if (BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
288                 connectParams = new ConnectionParams(BluetoothProfile.MAP_CLIENT, device);
289                 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
290                         BluetoothProfile.STATE_DISCONNECTED);
291                 notifyConnectionStatus(connectParams, currState);
292 
293             } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
294                 int currState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
295                 if (DBG) {
296                     Log.d(TAG, "Bluetooth Adapter State: " + currState);
297                 }
298                 if (currState == BluetoothAdapter.STATE_ON) {
299                     // Read from Settings which devices to connect to and populate
300                     // mProfileToConnectableDevicesMap
301                     readAndRebuildDeviceMapFromSettings();
302                     initiateConnection();
303                 } else if (currState == BluetoothAdapter.STATE_OFF) {
304                     // Write currently connected device snapshot to file.
305                     writeDeviceInfoToSettings();
306                     resetBluetoothDevicesConnectionInfo();
307                 }
308             } else if (BluetoothDevice.ACTION_UUID.equals(action)) {
309                 // Received during pairing with the UUIDs of the Bluetooth profiles supported by
310                 // the remote device.
311                 if (DBG) {
312                     Log.d(TAG, "Received UUID intent for device " + device);
313                 }
314                 Parcelable[] uuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID);
315                 if (uuids != null) {
316                     ParcelUuid[] uuidsToSend = new ParcelUuid[uuids.length];
317                     for (int i = 0; i < uuidsToSend.length; i++) {
318                         uuidsToSend[i] = (ParcelUuid) uuids[i];
319                     }
320                     setProfilePriorities(device, uuidsToSend, BluetoothProfile.PRIORITY_ON);
321                 }
322 
323             }
324         }
325     }
326 
327     /**
328      * Set priority for the Bluetooth profiles.
329      *
330      * The Bluetooth service stores the priority of a Bluetooth profile per device as a key value
331      * pair - BluetoothProfile_device:<Priority>.
332      * When we pair a device from the Settings App, the expected behavior is for the app to connect
333      * on all appropriate profiles after successful pairing automatically, without the user having
334      * to explicitly issue a connect. The settings app checks for the priority of the device from
335      * the above key-value pair and if the priority is set to PRIORITY_OFF or PRIORITY_UNDEFINED,
336      * the settings app will stop with just pairing and not connect.
337      * This scenario will happen when we pair a device, then unpair it and then pair it again.  When
338      * the device is unpaired, the BT stack sets the priority for that device to PRIORITY_UNDEFINED
339      * ( as a way of resetting).  So, the next time the same device is paired, the Settings app will
340      * stop with just pairing and not connect as explained above. Here, we register to receive the
341      * ACTION_UUID intent, which will broadcast the UUIDs corresponding to the profiles supported by
342      * the remote device which is successfully paired and we turn on the priority so when the
343      * Settings app tries to check before connecting, the priority is set to the expected value.
344      *
345      * @param device   - Remote Bluetooth device
346      * @param uuids    - UUIDs of the Bluetooth Profiles supported by the remote device
347      * @param priority - priority to set
348      */
setProfilePriorities(BluetoothDevice device, ParcelUuid[] uuids, int priority)349     private void setProfilePriorities(BluetoothDevice device, ParcelUuid[] uuids, int priority) {
350         // need the BluetoothProfile proxy to be able to call the setPriority API
351         if (mCarBluetoothUserService == null) {
352             mCarBluetoothUserService = setupBluetoothUserService();
353         }
354         if (mCarBluetoothUserService != null) {
355             for (Integer profile : mProfilesToConnect) {
356                 setBluetoothProfilePriorityIfUuidFound(uuids, profile, device, priority);
357             }
358         }
359     }
360 
setBluetoothProfilePriorityIfUuidFound(ParcelUuid[] uuids, int profile, BluetoothDevice device, int priority)361     private void setBluetoothProfilePriorityIfUuidFound(ParcelUuid[] uuids, int profile,
362             BluetoothDevice device, int priority) {
363         if (mCarBluetoothUserService == null || device == null) {
364             return;
365         }
366         // Build a list of UUIDs that represent a profile.
367         List<ParcelUuid> uuidsToCheck = new ArrayList<>();
368         switch (profile) {
369             case BluetoothProfile.A2DP_SINK:
370                 uuidsToCheck.add(BluetoothUuid.AudioSource);
371                 break;
372             case BluetoothProfile.HEADSET_CLIENT:
373                 uuidsToCheck.add(BluetoothUuid.Handsfree_AG);
374                 uuidsToCheck.add(BluetoothUuid.HSP_AG);
375                 break;
376             case BluetoothProfile.PBAP_CLIENT:
377                 uuidsToCheck.add(BluetoothUuid.PBAP_PSE);
378                 break;
379             case BluetoothProfile.MAP_CLIENT:
380                 uuidsToCheck.add(BluetoothUuid.MAS);
381                 break;
382         }
383 
384         for (ParcelUuid uuid : uuidsToCheck) {
385             if (BluetoothUuid.isUuidPresent(uuids, uuid)) {
386                 try {
387                     mCarBluetoothUserService.setProfilePriority(profile, device, priority);
388                 } catch (RemoteException e) {
389                     Log.e(TAG, "RemoteException calling setProfilePriority");
390                 }
391                 // if any one of the uuid in uuidsTocheck is present, set the priority and break
392                 break;
393             }
394         }
395     }
396 
397     /**
398      * Cleanup state and reinitialize whenever we connect to the PerUserCarService.
399      * This happens in init() and whenever the PerUserCarService is restarted on User Switch Events
400      */
401     @VisibleForTesting
402     class UserServiceConnectionCallback implements PerUserCarServiceHelper.ServiceCallback {
403         @Override
onServiceConnected(ICarUserService carUserService)404         public void onServiceConnected(ICarUserService carUserService) {
405             if (mCarUserServiceAccessLock != null) {
406                 mCarUserServiceAccessLock.lock();
407                 try {
408                     mCarUserService = carUserService;
409                 } finally {
410                     mCarUserServiceAccessLock.unlock();
411                 }
412             }
413             if (DBG) {
414                 Log.d(TAG, "Connected to PerUserCarService");
415             }
416             // Get the BluetoothUserService and also setup the Bluetooth Connection Proxy for
417             // all profiles.
418             mCarBluetoothUserService = setupBluetoothUserService();
419             // re-initialize for current user.
420             initializeUserSpecificInfo();
421         }
422 
423         @Override
onPreUnbind()424         public void onPreUnbind() {
425             if (DBG) {
426                 Log.d(TAG, "Before Unbinding from UserService");
427             }
428             try {
429                 if (mCarBluetoothUserService != null) {
430                     mCarBluetoothUserService.closeBluetoothConnectionProxy();
431                 }
432             } catch (RemoteException e) {
433                 Log.e(TAG,
434                         "Remote Exception during closeBluetoothConnectionProxy(): "
435                                 + e.getMessage());
436             }
437             // Clean up information related to user who went background.
438             cleanupUserSpecificInfo();
439         }
440 
441         @Override
onServiceDisconnected()442         public void onServiceDisconnected() {
443             if (DBG) {
444                 Log.d(TAG, "Disconnected from PerUserCarService");
445             }
446             if (mCarUserServiceAccessLock != null) {
447                 mCarUserServiceAccessLock.lock();
448                 try {
449                     mCarBluetoothUserService = null;
450                     mCarUserService = null;
451                 } finally {
452                     mCarUserServiceAccessLock.unlock();
453                 }
454             }
455         }
456     }
457 
458     /**
459      * Gets the Per User Car Bluetooth Service (ICarBluetoothService) from the PerUserCarService
460      * which acts as a top level Service running in the current user context.
461      * Also sets up the connection proxy objects required to communicate with the Bluetooth
462      * Profile Services.
463      *
464      * @return ICarBluetoothUserService running in current user
465      */
setupBluetoothUserService()466     private ICarBluetoothUserService setupBluetoothUserService() {
467         ICarBluetoothUserService carBluetoothUserService = null;
468         if (mCarUserService != null) {
469             try {
470                 carBluetoothUserService = mCarUserService.getBluetoothUserService();
471                 if (carBluetoothUserService != null) {
472                     if (DBG) {
473                         Log.d(TAG, "Got CarBTUsrSvc");
474                     }
475                     carBluetoothUserService.setupBluetoothConnectionProxy();
476                 }
477             } catch (RemoteException e) {
478                 Log.e(TAG, "Remote Service Exception on ServiceConnection Callback: "
479                         + e.getMessage());
480             }
481         } else {
482             if (DBG) {
483                 Log.d(TAG, "PerUserCarService not connected");
484             }
485         }
486         return carBluetoothUserService;
487     }
488 
489     /**
490      * Setup the Bluetooth profile service connections and Vehicle Event listeners.
491      * and start the state machine -{@link BluetoothAutoConnectStateMachine}
492      */
init()493     public synchronized void init() {
494         if (DBG) {
495             Log.d(TAG, "init()");
496         }
497         // Initialize information specific to current user.
498         initializeUserSpecificInfo();
499         // Listen to various events coming from the vehicle.
500         setupEventListenersLocked();
501         mInitialized = true;
502     }
503 
504     /**
505      * Setup and initialize information that is specific per User account, which involves:
506      * 1. Reading the list of devices to connect for current user and initialize the deviceMap
507      * with that information.
508      * 2. Register a BroadcastReceiver for bluetooth related events for the current user.
509      * 3. Start and bind to {@link PerUserCarService} as current user.
510      * 4. Start the {@link BluetoothAutoConnectStateMachine}
511      */
initializeUserSpecificInfo()512     private void initializeUserSpecificInfo() {
513         synchronized (mSetupLock) {
514             if (DBG) {
515                 Log.d(TAG, "initializeUserSpecificInfo()");
516             }
517             if (mUserSpecificInfoInitialized) {
518                 if (DBG) {
519                     Log.d(TAG, "Already Initialized");
520                 }
521                 return;
522             }
523             mBluetoothAutoConnectStateMachine = BluetoothAutoConnectStateMachine.make(this);
524             readAndRebuildDeviceMapFromSettings();
525             setupBluetoothEventsIntentFilterLocked();
526 
527             mConnectionInFlight = new ConnectionParams();
528             mUserSpecificInfoInitialized = true;
529         }
530     }
531 
532     /**
533      * Setting up the Intent filter for Bluetooth related broadcasts
534      * This includes knowing when the
535      * 1. Bluetooth Adapter turned on/off
536      * 2. Bonding State of a device changes
537      * 3. A specific profile's connection state changes.
538      */
setupBluetoothEventsIntentFilterLocked()539     private void setupBluetoothEventsIntentFilterLocked() {
540         mBluetoothBroadcastReceiver = new BluetoothBroadcastReceiver();
541         IntentFilter profileFilter = new IntentFilter();
542         profileFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
543         profileFilter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
544         profileFilter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
545         profileFilter.addAction(BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED);
546         profileFilter.addAction(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED);
547         profileFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
548         profileFilter.addAction(BluetoothDevice.ACTION_UUID);
549         if (mContext != null) {
550             mContext.registerReceiverAsUser(mBluetoothBroadcastReceiver, UserHandle.CURRENT,
551                     profileFilter, null, null);
552         }
553     }
554 
555     /**
556      * Initialize the {@link #mProfileToConnectableDevicesMap}.
557      * {@link #mProfileToConnectableDevicesMap} stores the profile:DeviceList information.  This
558      * method retrieves it from persistent memory.
559      */
initDeviceMap()560     private synchronized void initDeviceMap() {
561         if (mProfileToConnectableDevicesMap == null) {
562             mProfileToConnectableDevicesMap = new HashMap<>();
563             for (Integer profile : mProfilesToConnect) {
564                 // Build the BluetoothDevicesInfo for this profile.
565                 BluetoothDevicesInfo devicesInfo = new BluetoothDevicesInfo(profile,
566                         mNumSupportedActiveConnections.get(profile));
567                 mProfileToConnectableDevicesMap.put(profile, devicesInfo);
568             }
569             if (DBG) {
570                 Log.d(TAG, "Created a new empty Device Map");
571             }
572         }
573     }
574 
575     /**
576      * Setting up Listeners to the various events we are interested in listening to for initiating
577      * Bluetooth connection attempts.
578      */
setupEventListenersLocked()579     private void setupEventListenersLocked() {
580         // Setting up a listener for events from CarCabinService
581         // For now, we listen to door unlock signal coming from {@link CarCabinService},
582         // and Ignition state START from {@link CarSensorService}
583         mCarCabinService.registerListener(mCabinEventListener);
584         mCarSensorService.registerOrUpdateSensorListener(
585                 CarSensorManager.SENSOR_TYPE_IGNITION_STATE, 0, mCarSensorEventListener);
586         mUserServiceHelper.registerServiceCallback(mServiceCallback);
587     }
588 
589     /**
590      * Handles events coming in from the {@link CarCabinService}
591      * The events that can trigger Bluetooth Scanning from CarCabinService is Door Unlock.
592      * Upon receiving the event that is of interest, initiate a connection attempt by calling
593      * the policy {@link BluetoothDeviceConnectionPolicy}
594      */
595     @VisibleForTesting
596     class CarPropertyListener extends ICarPropertyEventListener.Stub {
597         @Override
onEvent(CarPropertyEvent event)598         public void onEvent(CarPropertyEvent event) throws RemoteException {
599             if (DBG) {
600                 Log.d(TAG, "Cabin change Event : " + event.getEventType());
601             }
602             Boolean locked;
603             CarPropertyValue value = event.getCarPropertyValue();
604             Object o = value.getValue();
605 
606             if (value.getPropertyId() == CarCabinManager.ID_DOOR_LOCK) {
607                 if (o instanceof Boolean) {
608                     locked = (Boolean) o;
609                     if (DBG) {
610                         Log.d(TAG, "Door Lock: " + locked);
611                     }
612                     // Attempting a connection only on a door unlock
613                     if (!locked) {
614                         initiateConnection();
615                     }
616                 }
617             }
618         }
619     }
620 
621     /**
622      * Handles events coming in from the {@link CarSensorService}
623      * The events that can trigger Bluetooth Scanning from CarSensorService is Ignition START.
624      * Upon receiving the event that is of interest, initiate a connection attempt by calling
625      * the policy {@link BluetoothDeviceConnectionPolicy}
626      */
627     private class CarSensorEventListener extends ICarSensorEventListener.Stub {
628         @Override
onSensorChanged(List<CarSensorEvent> events)629         public void onSensorChanged(List<CarSensorEvent> events) throws RemoteException {
630             if (events != null & !events.isEmpty()) {
631                 CarSensorEvent event = events.get(0);
632                 if (DBG) {
633                     Log.d(TAG, "Sensor event Type : " + event.sensorType);
634                 }
635                 if (event.sensorType == CarSensorManager.SENSOR_TYPE_IGNITION_STATE) {
636                     if (DBG) {
637                         Log.d(TAG, "Sensor value : " + event.intValues[0]);
638                     }
639                     if (event.intValues[0] == CarSensorEvent.IGNITION_STATE_START) {
640                         initiateConnection();
641                     }
642                 }
643             }
644         }
645     }
646 
647     /**
648      * Clean up slate. Close the Bluetooth profile service connections and quit the state machine -
649      * {@link BluetoothAutoConnectStateMachine}
650      */
release()651     public synchronized void release() {
652         if (DBG) {
653             Log.d(TAG, "release()");
654         }
655         mInitialized = false;
656         writeDeviceInfoToSettings();
657         cleanupUserSpecificInfo();
658         closeEventListeners();
659     }
660 
661     /**
662      * Clean up information related to user who went background.
663      */
cleanupUserSpecificInfo()664     private void cleanupUserSpecificInfo() {
665         synchronized (mSetupLock) {
666             if (DBG) {
667                 Log.d(TAG, "cleanupUserSpecificInfo()");
668             }
669             if (!mUserSpecificInfoInitialized) {
670                 if (DBG) {
671                     Log.d(TAG, "User specific Info Not initialized..Not cleaning up");
672                 }
673                 return;
674             }
675             mUserSpecificInfoInitialized = false;
676             // quit the state machine
677             mBluetoothAutoConnectStateMachine.doQuit();
678             mProfileToConnectableDevicesMap = null;
679             mConnectionInFlight = null;
680             if (mBluetoothBroadcastReceiver != null) {
681                 if (mContext != null) {
682                     mContext.unregisterReceiver(mBluetoothBroadcastReceiver);
683                 }
684                 mBluetoothBroadcastReceiver = null;
685             }
686         }
687     }
688 
689     /**
690      * Unregister the listeners to the various Vehicle events coming from other parts of the
691      * CarService
692      */
closeEventListeners()693     private void closeEventListeners() {
694         if (DBG) {
695             Log.d(TAG, "closeEventListeners()");
696         }
697         mCarCabinService.unregisterListener(mCabinEventListener);
698         mCarSensorService.unregisterSensorListener(CarSensorManager.SENSOR_TYPE_IGNITION_STATE,
699                 mCarSensorEventListener);
700         mUserServiceHelper.unregisterServiceCallback(mServiceCallback);
701     }
702 
703     /**
704      * Resets the {@link BluetoothDevicesInfo#mConnectionInfo} of all the profiles to start from
705      * a clean slate.  The ConnectionInfo has all the book keeping information regarding the state
706      * of connection attempts - like which device in the device list for the profile is the next
707      * to try connecting etc.
708      * This method does not clear the {@link BluetoothDevicesInfo#mDeviceInfoList} like the {@link
709      * #resetProfileToConnectableDevicesMap()} method does.
710      */
resetBluetoothDevicesConnectionInfo()711     private synchronized void resetBluetoothDevicesConnectionInfo() {
712         if (DBG) {
713             Log.d(TAG, "Resetting ConnectionInfo for all profiles");
714         }
715         for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) {
716             devInfo.resetConnectionInfoLocked();
717         }
718     }
719 
720     @VisibleForTesting
getBluetoothBroadcastReceiver()721     BroadcastReceiver getBluetoothBroadcastReceiver() {
722         return mBluetoothBroadcastReceiver;
723     }
724 
725     @VisibleForTesting
getServiceCallback()726     UserServiceConnectionCallback getServiceCallback() {
727         return mServiceCallback;
728     }
729 
730     @VisibleForTesting
getCarPropertyListener()731     CarPropertyListener getCarPropertyListener() {
732         return mCabinEventListener;
733     }
734 
735     @VisibleForTesting
setAllowReadWriteToSettings(boolean allowWrite)736     synchronized void setAllowReadWriteToSettings(boolean allowWrite) {
737         mAllowReadWriteToSettings = allowWrite;
738     }
739 
740     @VisibleForTesting
getBluetoothDevicesInfo(int profile)741     BluetoothDevicesInfo getBluetoothDevicesInfo(int profile) {
742         return mProfileToConnectableDevicesMap.get(profile);
743     }
744 
745     /**
746      * Resets the {@link #mProfileToConnectableDevicesMap} to a clean and empty slate.
747      */
resetProfileToConnectableDevicesMap()748     public synchronized void resetProfileToConnectableDevicesMap() {
749         if (DBG) {
750             Log.d(TAG, "Resetting the mProfilesToConnectableDevicesMap");
751         }
752         for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) {
753             devInfo.resetDeviceListLocked();
754         }
755     }
756 
757     /**
758      * Returns the list of profiles that the Autoconnection policy attempts to connect on
759      *
760      * @return profile list.
761      */
getProfilesToConnect()762     public synchronized List<Integer> getProfilesToConnect() {
763         return mProfilesToConnect;
764     }
765 
766     /**
767      * Add a new Profile to the list of To Be Connected profiles.
768      *
769      * @param profile - ProfileInfo of the new profile to be added.
770      */
addProfile(Integer profile)771     public synchronized void addProfile(Integer profile) {
772         mProfilesToConnect.add(profile);
773     }
774 
775     /**
776      * Add or remove a device based on the bonding state change.
777      *
778      * @param device    - device to add/remove
779      * @param bondState - current bonding state
780      */
updateBondState(BluetoothDevice device, int bondState)781     private void updateBondState(BluetoothDevice device, int bondState) {
782         if (device == null) {
783             Log.e(TAG, "updateBondState: device Null");
784             return;
785         }
786         if (DBG) {
787             Log.d(TAG, "BondState :" + bondState + " Device: " + device);
788         }
789         // Bonded devices are added to a profile's device list after the device CONNECTS on the
790         // profile.  When unpaired, we remove the device from all of the profiles' device list.
791         if (bondState == BluetoothDevice.BOND_NONE) {
792             for (Integer profile : mProfilesToConnect) {
793                 if (DBG) {
794                     Log.d(TAG, "Removing " + device + " from profile: " + profile);
795                 }
796                 removeDeviceFromProfile(device, profile);
797             }
798         }
799 
800     }
801 
802     /**
803      * Add a new device to the list of devices connect-able on the given profile
804      *
805      * @param device  - Bluetooth device to be added
806      * @param profile - profile to add the device to.
807      */
addDeviceToProfile(BluetoothDevice device, Integer profile)808     private synchronized void addDeviceToProfile(BluetoothDevice device, Integer profile) {
809         BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
810         if (devInfo == null) {
811             if (DBG) {
812                 Log.d(TAG, "Creating devInfo for profile: " + profile);
813             }
814             devInfo = new BluetoothDevicesInfo(profile);
815             mProfileToConnectableDevicesMap.put(profile, devInfo);
816         }
817         devInfo.addDeviceLocked(device);
818     }
819 
820     /**
821      * Remove the device from the list of devices connect-able on the gievn profile.
822      *
823      * @param device  - Bluetooth device to be removed
824      * @param profile - profile to remove the device from
825      */
removeDeviceFromProfile(BluetoothDevice device, Integer profile)826     private synchronized void removeDeviceFromProfile(BluetoothDevice device, Integer profile) {
827         BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
828         if (devInfo != null) {
829             devInfo.removeDeviceLocked(device);
830         }
831     }
832 
833     /**
834      * Initiate a bluetooth connection.
835      */
initiateConnection()836     private void initiateConnection() {
837         // Make sure the bluetooth adapter is available & enabled.
838         if (mBluetoothAdapter == null) {
839             Log.w(TAG, "Bluetooth Adapter null");
840             return;
841         }
842         if (mBluetoothAdapter.isEnabled()) {
843             if (isDeviceMapEmpty()) {
844                 if (DBG) {
845                     Log.d(TAG, "Device Map is empty. Nothing to connect to");
846                 }
847                 return;
848             }
849             resetDeviceAvailableToConnect();
850             if (DBG) {
851                 Log.d(TAG, "initiateConnection() Reset Device Availability");
852             }
853             mBluetoothAutoConnectStateMachine.sendMessage(BluetoothAutoConnectStateMachine
854                     .CONNECT);
855         } else {
856             if (DBG) {
857                 Log.d(TAG, "Bluetooth Adapter not enabled.");
858             }
859         }
860     }
861 
862     /**
863      * Find an unconnected profile and find a device to connect on it.
864      * Finds the appropriate device for the profile from the information available in
865      * {@link #mProfileToConnectableDevicesMap}
866      *
867      * @return true - if we found a device to connect on for any of the {@link #mProfilesToConnect}
868      * false - if we cannot find a device to connect to or if we are not ready to connect yet.
869      */
findDeviceToConnect()870     public synchronized boolean findDeviceToConnect() {
871         if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()
872                 || mProfileToConnectableDevicesMap == null || !mInitialized) {
873             if (DBG) {
874                 if (mProfileToConnectableDevicesMap == null) {
875                     Log.d(TAG, "findDeviceToConnect(): Device Map null");
876                 } else {
877                     Log.d(TAG, "findDeviceToConnect(): BT Adapter not enabled");
878                 }
879             }
880             return false;
881         }
882         boolean connectingToADevice = false;
883         // Get the first unconnected profile that we can try to make a connection
884         Integer nextProfile = getNextProfileToConnectLocked();
885         // Keep going through the profiles until we find a device that we can connect to
886         while (nextProfile != PROFILE_NOT_AVAILABLE) {
887             if (DBG) {
888                 Log.d(TAG, "connectToProfile(): " + nextProfile);
889             }
890             // find a device that is next in line for a connection attempt for that profile
891             // and try connecting to it.
892             connectingToADevice = connectToNextDeviceInQueueLocked(nextProfile);
893             // If we found a device to connect, break out of the loop
894             if (connectingToADevice) {
895                 if (DBG) {
896                     Log.d(TAG, "Found device to connect to");
897                 }
898                 BluetoothDeviceConnectionPolicy.ConnectionParams btParams =
899                         new BluetoothDeviceConnectionPolicy.ConnectionParams(
900                                 mConnectionInFlight.getBluetoothProfile(),
901                                 mConnectionInFlight.getBluetoothDevice());
902                 // set up a time out
903                 mBluetoothAutoConnectStateMachine.sendMessageDelayed(
904                         BluetoothAutoConnectStateMachine.CONNECT_TIMEOUT, btParams,
905                         BluetoothAutoConnectStateMachine.CONNECTION_TIMEOUT_MS);
906                 break;
907             } else {
908                 // result will be false, if there are no more devices to connect
909                 // or if the ProfileProxy objects are null (ServiceConnection
910                 // not yet established for this profile)
911                 if (DBG) {
912                     Log.d(TAG, "No more device to connect on Profile: " + nextProfile);
913                 }
914                 nextProfile = getNextProfileToConnectLocked();
915             }
916         }
917         return connectingToADevice;
918     }
919 
920     /**
921      * Get the first unconnected profile.
922      *
923      * @return profile to connect.
924      * Special return value 0 if
925      * 1. all profiles have been connected on.
926      * 2. no profile connected but no nearby known device that can be connected to
927      */
getNextProfileToConnectLocked()928     private Integer getNextProfileToConnectLocked() {
929         for (Integer profile : mProfilesToConnect) {
930             BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
931             if (devInfo != null) {
932                 if (devInfo.isProfileConnectableLocked()) {
933                     return profile;
934                 }
935             } else {
936                 Log.e(TAG, "Unexpected: devInfo null for profile: " + profile);
937             }
938         }
939         // Reaching here denotes all profiles are connected or No devices available for any profile
940         if (DBG) {
941             Log.d(TAG, "No disconnected profiles");
942         }
943         return PROFILE_NOT_AVAILABLE;
944     }
945 
946     /**
947      * Try to connect to the next device in the device list for the given profile.
948      *
949      * @param profile - profile to connect on
950      * @return - true if we found a device to connect on for this profile
951      * false - if we cannot find a device to connect to.
952      */
connectToNextDeviceInQueueLocked(Integer profile)953     private boolean connectToNextDeviceInQueueLocked(Integer profile) {
954         // Get the Device Information for the given profile and find the next device to connect on
955         boolean connecting = true;
956         boolean proxyAvailable = true;
957         BluetoothDevice devToConnect = null;
958         BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
959         if (devInfo == null) {
960             Log.e(TAG, "Unexpected: No device Queue for this profile: " + profile);
961             return false;
962         }
963         // Check if the Bluetooth profile service's proxy object is available before
964         // attempting to connect.
965         if (mCarBluetoothUserService == null) {
966             mCarBluetoothUserService = setupBluetoothUserService();
967         }
968         if (mCarBluetoothUserService != null) {
969             try {
970                 if (!mCarBluetoothUserService.isBluetoothConnectionProxyAvailable(profile)) {
971                     // proxy unavailable.
972                     if (DBG) {
973                         Log.d(TAG,
974                                 "Proxy for Bluetooth Profile Service Unavailable: " + profile);
975                     }
976                     proxyAvailable = false;
977                 }
978             } catch (RemoteException e) {
979                 Log.e(TAG, "Car BT Service Remote Exception.");
980                 proxyAvailable = false;
981             }
982         } else {
983             Log.d(TAG, "CarBluetoothUserSvc null.  Car service not bound to PerUserCarSvc.");
984             proxyAvailable = false;
985         }
986 
987         if (proxyAvailable) {
988             // Get the next device in the device list for this profile.
989             devToConnect = devInfo.getNextDeviceInQueueLocked();
990             if (devToConnect != null) {
991                 // deviceAvailable && proxyAvailable
992                 try {
993                     if (mCarBluetoothUserService != null) {
994                         mCarBluetoothUserService.bluetoothConnectToProfile((int) profile,
995                                 devToConnect);
996                     } else {
997                         Log.e(TAG, "CarBluetoothUserSvc null");
998                         connecting = false;
999                     }
1000                 } catch (RemoteException e) {
1001                     Log.e(TAG, "Remote User Service stopped responding: " + e.getMessage());
1002                     connecting = false;
1003                 }
1004             } else {
1005                 // device unavailable
1006                 if (DBG) {
1007                     Log.d(TAG, "No paired nearby device to connect to for profile: " + profile);
1008                 }
1009                 connecting = false;
1010             }
1011         } else {
1012             connecting = false;
1013         }
1014 
1015         if (connecting && devToConnect != null) {
1016             devInfo.setConnectionStateLocked(devToConnect, BluetoothProfile.STATE_CONNECTING);
1017             // Increment the retry count & cache what is being connected to
1018             // This method is already called from a synchronized context.
1019             mConnectionInFlight.setBluetoothDevice(devToConnect);
1020             mConnectionInFlight.setBluetoothProfile(profile);
1021             devInfo.incrementRetryCountLocked();
1022             if (DBG) {
1023                 Log.d(TAG, "Increment Retry to: " + devInfo.getRetryCountLocked());
1024             }
1025         } else {
1026             // reset the mConnectionInFlight
1027             mConnectionInFlight.setBluetoothProfile(0);
1028             mConnectionInFlight.setBluetoothDevice(null);
1029             devInfo.setDeviceAvailableToConnectLocked(false);
1030         }
1031         return connecting;
1032     }
1033 
1034     /**
1035      * Update the device connection status for a profile and also notify the state machine.
1036      * This gets called from {@link BluetoothBroadcastReceiver} when it receives a Profile's
1037      * CONNECTION_STATE_CHANGED intent.
1038      *
1039      * @param params       - {@link ConnectionParams} device and profile list info
1040      * @param currentState - connection result to update
1041      */
notifyConnectionStatus(ConnectionParams params, int currentState)1042     private void notifyConnectionStatus(ConnectionParams params, int currentState) {
1043         // Update the profile's BluetoothDevicesInfo.
1044         boolean isConnected;
1045         switch (currentState) {
1046             case BluetoothProfile.STATE_DISCONNECTED: {
1047                 isConnected = false;
1048                 break;
1049             }
1050 
1051             case BluetoothProfile.STATE_CONNECTED: {
1052                 isConnected = true;
1053                 break;
1054             }
1055 
1056             default: {
1057                 if (DBG) {
1058                     Log.d(TAG, "notifyConnectionStatus() Ignoring state: " + currentState);
1059                 }
1060                 return;
1061             }
1062 
1063         }
1064 
1065         boolean updateSuccessful = updateDeviceConnectionStatus(params, isConnected);
1066         if (updateSuccessful) {
1067             if (isConnected) {
1068                 mBluetoothAutoConnectStateMachine.sendMessage(
1069                         BluetoothAutoConnectStateMachine.DEVICE_CONNECTED,
1070                         params);
1071             } else {
1072                 mBluetoothAutoConnectStateMachine.sendMessage(
1073                         BluetoothAutoConnectStateMachine.DEVICE_DISCONNECTED,
1074                         params);
1075             }
1076         }
1077     }
1078 
1079     /**
1080      * Update the profile's {@link BluetoothDevicesInfo} with the result of the connection
1081      * attempt.  This gets called from the {@link BluetoothAutoConnectStateMachine} when the
1082      * connection attempt times out or from {@link BluetoothBroadcastReceiver} when it receives
1083      * a Profile's CONNECTION_STATE_CHANGED intent.
1084      *
1085      * @param params     - {@link ConnectionParams} device and profile list info
1086      * @param didConnect - connection result to update
1087      */
updateDeviceConnectionStatus(ConnectionParams params, boolean didConnect)1088     public synchronized boolean updateDeviceConnectionStatus(ConnectionParams params,
1089             boolean didConnect) {
1090         if (params == null || params.getBluetoothDevice() == null) {
1091             Log.e(TAG, "updateDeviceConnectionStatus: null params");
1092             return false;
1093         }
1094         // Get the profile to update
1095         Integer profileToUpdate = params.getBluetoothProfile();
1096         BluetoothDevice deviceThatConnected = params.getBluetoothDevice();
1097         if (DBG) {
1098             Log.d(TAG, "Profile: " + profileToUpdate + " Connected: " + didConnect + " on "
1099                     + deviceThatConnected);
1100         }
1101 
1102         // If the connection update is on a different profile or device (a very rare possibility),
1103         // it is handled automatically.  Just logging it here.
1104         if (DBG) {
1105             if (mConnectionInFlight != null && mConnectionInFlight.getBluetoothProfile() != null) {
1106                 if (profileToUpdate.equals(mConnectionInFlight.getBluetoothProfile()) == false) {
1107                     Log.d(TAG, "Updating profile " + profileToUpdate
1108                             + " different from connection in flight "
1109                             + mConnectionInFlight.getBluetoothProfile());
1110                 }
1111             }
1112 
1113             if (mConnectionInFlight != null && mConnectionInFlight.getBluetoothDevice() != null) {
1114                 if (deviceThatConnected.equals(mConnectionInFlight.getBluetoothDevice()) == false) {
1115                     Log.d(TAG, "Updating device: " + deviceThatConnected
1116                             + " different from connection in flight: "
1117                             + mConnectionInFlight.getBluetoothDevice());
1118 
1119                 }
1120             }
1121         }
1122         BluetoothDevicesInfo devInfo = null;
1123         devInfo = mProfileToConnectableDevicesMap.get(profileToUpdate);
1124         if (devInfo == null) {
1125             Log.e(TAG, "Unexpected: devInfo null for profile: " + profileToUpdate);
1126             return false;
1127         }
1128 
1129         boolean retry = canRetryConnection(profileToUpdate);
1130         // Update the status and also if a retry attempt can be made if the
1131         // connection timed out in the previous attempt.
1132         if (DBG) {
1133             Log.d(TAG, "Retry? : " + retry);
1134         }
1135         devInfo.updateConnectionStatusLocked(deviceThatConnected, didConnect, retry);
1136         // Write to persistent memory to have the latest snapshot available
1137         writeDeviceInfoToSettings(params);
1138         return true;
1139     }
1140 
1141     /**
1142      * Returns if we can retry connection attempt on the given profile for the device that is
1143      * currently in the head of the queue.
1144      *
1145      * @param profile - Profile to check
1146      */
canRetryConnection(Integer profile)1147     private synchronized boolean canRetryConnection(Integer profile) {
1148         BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
1149         if (devInfo == null) {
1150             Log.e(TAG, "Unexpected: No device Queue for this profile: " + profile);
1151             return false;
1152         }
1153         if (devInfo.getRetryCountLocked() < MAX_CONNECT_RETRIES) {
1154             return true;
1155         } else {
1156             return false;
1157         }
1158     }
1159 
1160     /**
1161      * Helper method to see if there are any connect-able devices on any of the
1162      * profiles.
1163      *
1164      * @return true - if {@link #mProfileToConnectableDevicesMap} does not have any devices for any
1165      * profiles.
1166      * false - if {@link #mProfileToConnectableDevicesMap} has a device for at least one profile.
1167      */
isDeviceMapEmpty()1168     private synchronized boolean isDeviceMapEmpty() {
1169         boolean empty = true;
1170         for (Integer profile : mProfilesToConnect) {
1171             BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
1172             if (devInfo != null) {
1173                 if (devInfo.getNumberOfPairedDevicesLocked() != 0) {
1174                     if (DBG) {
1175                         Log.d(TAG, "Device map not empty. Profile: " + profile + " has "
1176                                 + devInfo.getNumberOfPairedDevicesLocked() + " paired devices");
1177                     }
1178                     empty = false;
1179                     break;
1180                 }
1181             }
1182         }
1183         return empty;
1184     }
1185 
1186     /**
1187      * Reset the Device Available to Connect information for all profiles to Available.
1188      * If in a previous connection attempt, we failed to connect on all devices for a profile,
1189      * we would update deviceAvailableToConnect for that profile to false.  That information
1190      * is used to deduce if we should move to the next profile. If marked false, we will not
1191      * try to connect on that profile anymore as part of that connection attempt.
1192      * However, when we get another connection trigger from the vehicle, we need to reset the
1193      * deviceAvailableToConnect information so we can start the connection attempts all over
1194      * again.
1195      */
resetDeviceAvailableToConnect()1196     private synchronized void resetDeviceAvailableToConnect() {
1197         for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) {
1198             devInfo.setDeviceAvailableToConnectLocked(true);
1199             devInfo.resetDeviceIndex();
1200         }
1201     }
1202 
1203     /**
1204      * Utility function - Prints the Profile: list of devices information to log
1205      * Caller should wrap a DBG around this, since this is for debugging purpose.
1206      *
1207      * @param writer - PrintWriter
1208      */
printDeviceMap(PrintWriter writer)1209     private synchronized void printDeviceMap(PrintWriter writer) {
1210         if (mProfileToConnectableDevicesMap == null) {
1211             return;
1212         }
1213         for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) {
1214             writer.print("Profile: " + devInfo.getProfileLocked() + "\t");
1215             writer.print(
1216                     "Num of Paired devices: " + devInfo.getNumberOfPairedDevicesLocked() + "\t");
1217             writer.print("Active Connections: " + devInfo.getNumberOfActiveConnectionsLocked());
1218             writer.println("Num of paired devices: " + devInfo.getNumberOfPairedDevicesLocked());
1219             writer.println();
1220             List<BluetoothDevicesInfo.DeviceInfo> deviceInfoList = devInfo.getDeviceInfoList();
1221             if (deviceInfoList != null) {
1222                 for (BluetoothDevicesInfo.DeviceInfo devicesInfo : deviceInfoList) {
1223                     if (devicesInfo.getBluetoothDevice() != null) {
1224                         writer.print(devicesInfo.getBluetoothDevice() + ":");
1225                         writer.print(devicesInfo.getConnectionState() + "\t");
1226                     }
1227                 }
1228                 writer.println();
1229             }
1230         }
1231     }
1232 
1233     /**
1234      * Write the device list for all bluetooth profiles that connected.
1235      *
1236      * @return true if the write was successful, false otherwise
1237      */
writeDeviceInfoToSettings()1238     private synchronized boolean writeDeviceInfoToSettings() {
1239         ConnectionParams params;
1240         boolean writeResult;
1241         for (Integer profile : mProfilesToConnect) {
1242             params = new ConnectionParams(profile);
1243             writeResult = writeDeviceInfoToSettings(params);
1244             if (!writeResult) {
1245                 Log.e(TAG, "Error writing Device Info for profile:" + profile);
1246                 return writeResult;
1247             }
1248         }
1249         return true;
1250     }
1251 
1252     /**
1253      * Write information about which devices connected on which profile to Settings.Secure.
1254      * Essentially the list of devices that a profile can connect on the next auto-connect
1255      * attempt.
1256      *
1257      * @param params - ConnectionParams indicating which bluetooth profile to write this
1258      *               information
1259      *               for.
1260      * @return true if the write was successful, false otherwise
1261      */
writeDeviceInfoToSettings(ConnectionParams params)1262     public synchronized boolean writeDeviceInfoToSettings(ConnectionParams params) {
1263         if (!mAllowReadWriteToSettings) {
1264             return false;
1265         }
1266         boolean writeSuccess = true;
1267         Integer profileToUpdate = params.getBluetoothProfile();
1268 
1269         if (mProfileToConnectableDevicesMap == null) {
1270             writeSuccess = false;
1271         } else {
1272             List<String> deviceNames = new ArrayList<>();
1273             BluetoothDevicesInfo devicesInfo = mProfileToConnectableDevicesMap.get(profileToUpdate);
1274             StringBuilder sb = new StringBuilder();
1275             String delimiter = ""; // start off with no delimiter.
1276 
1277             // Iterate through the List<BluetoothDevice> and build a String that is
1278             // names of all devices to be connected for this profile joined together and
1279             // delimited by a delimiter (its a ',' here)
1280             if (devicesInfo != null && devicesInfo.getDeviceList() != null) {
1281                 for (BluetoothDevice device : devicesInfo.getDeviceList()) {
1282                     sb.append(delimiter);
1283                     sb.append(device.getAddress());
1284                     delimiter = SETTINGS_DELIMITER;
1285                 }
1286 
1287             }
1288             // joinedDeviceNames has something like "22:22:33:44:55:AB,22:23:xx:xx:xx:xx"
1289             // mac addresses of connectable devices separated by a delimiter
1290             String joinedDeviceNames = sb.toString();
1291             if (DBG) {
1292                 Log.d(TAG, "Profile: " + profileToUpdate + " Writing: " + joinedDeviceNames);
1293             }
1294             long userId = ActivityManager.getCurrentUser();
1295             switch (profileToUpdate) {
1296                 case BluetoothProfile.A2DP_SINK:
1297                     Settings.Secure.putStringForUser(mContext.getContentResolver(),
1298                             KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICES,
1299                             joinedDeviceNames, (int) userId);
1300                     break;
1301 
1302                 case BluetoothProfile.HEADSET_CLIENT:
1303                     Settings.Secure.putStringForUser(mContext.getContentResolver(),
1304                             KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICES,
1305                             joinedDeviceNames, (int) userId);
1306                     break;
1307 
1308                 case BluetoothProfile.PBAP_CLIENT:
1309                     // use the phone
1310                     break;
1311 
1312                 case BluetoothProfile.MAP_CLIENT:
1313                     Settings.Secure.putStringForUser(mContext.getContentResolver(),
1314                             KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICES,
1315                             joinedDeviceNames, (int) userId);
1316                     break;
1317 
1318             }
1319         }
1320         return writeSuccess;
1321     }
1322 
1323     /**
1324      * Read the device information from Settings.Secure and populate the
1325      * {@link #mProfileToConnectableDevicesMap}
1326      *
1327      * Device MAC addresses are written to Settings.Secure delimited by a ','.
1328      * Ex: android.car.BLUETOOTH_AUTOCONNECT_PHONE_DEVICES: xx:xx:xx:xx:xx:xx,yy:yy:yy:yy:yy
1329      * denotes that two devices with addresses xx:xx:xx:xx:xx:xx & yy:yy:yy:yy:yy:yy were connected
1330      * as phones (in HFP and PBAP profiles) the last time this user was logged in.
1331      *
1332      * @return - true if the read was successful, false if 1. BT Adapter not enabled 2. No prior
1333      * bonded devices 3. No information stored in Settings for this user.
1334      */
readAndRebuildDeviceMapFromSettings()1335     public synchronized boolean readAndRebuildDeviceMapFromSettings() {
1336         List<String> deviceList;
1337         String devices = null;
1338         // Create and initialize mProfileToConnectableDevicesMap if needed.
1339         initDeviceMap();
1340         if (mBluetoothAdapter != null) {
1341             if (DBG) {
1342                 Log.d(TAG,
1343                         "Number of Bonded devices:" + mBluetoothAdapter.getBondedDevices().size());
1344             }
1345             if (mBluetoothAdapter.getBondedDevices().isEmpty()) {
1346                 if (DBG) {
1347                     Log.d(TAG, "No Bonded Devices available. Quit rebuilding");
1348                 }
1349                 return false;
1350             }
1351         }
1352         if (!mAllowReadWriteToSettings) {
1353             return false;
1354         }
1355         // Read from Settings.Secure for the current user.  There are 3 keys 1 each for Phone
1356         // (HFP & PBAP), 1 for Music (A2DP) and 1 for Messaging device (MAP)
1357         long userId = ActivityManager.getCurrentUser();
1358         for (Integer profile : mProfilesToConnect) {
1359             switch (profile) {
1360                 case BluetoothProfile.A2DP_SINK:
1361                     devices = Settings.Secure.getStringForUser(mContext.getContentResolver(),
1362                             KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICES, (int) userId);
1363                     break;
1364                 case BluetoothProfile.PBAP_CLIENT:
1365                     // fall through
1366                 case BluetoothProfile.HEADSET_CLIENT:
1367                     devices = Settings.Secure.getStringForUser(mContext.getContentResolver(),
1368                             KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICES, (int) userId);
1369                     break;
1370                 case BluetoothProfile.MAP_CLIENT:
1371                     devices = Settings.Secure.getStringForUser(mContext.getContentResolver(),
1372                             KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICES, (int) userId);
1373                     break;
1374                 default:
1375                     Log.e(TAG, "Unexpected profile");
1376                     break;
1377             }
1378 
1379             if (devices == null) {
1380                 if (DBG) {
1381                     Log.d(TAG, "No device information stored in Settings");
1382                 }
1383                 return false;
1384             }
1385             if (DBG) {
1386                 Log.d(TAG, "Devices in Settings: " + devices);
1387             }
1388             // Get a list of Device Mac Addresses from the value
1389             deviceList = Arrays.asList(devices.split(SETTINGS_DELIMITER));
1390             if (deviceList == null) {
1391                 return false;
1392             }
1393             BluetoothDevicesInfo devicesInfo = mProfileToConnectableDevicesMap.get(profile);
1394             // Do we have a bonded device with this name?  If so, get it and populate the device
1395             // map.
1396             for (String address : deviceList) {
1397                 BluetoothDevice deviceToAdd = getBondedDeviceWithGivenName(address);
1398                 if (deviceToAdd != null) {
1399                     devicesInfo.addDeviceLocked(deviceToAdd);
1400                 } else {
1401                     if (DBG) {
1402                         Log.d(TAG, "No device with name " + address + " found in bonded devices");
1403                     }
1404                 }
1405             }
1406             mProfileToConnectableDevicesMap.put(profile, devicesInfo);
1407             // Check to see if there are any  primary or secondary devices for this profile and
1408             // update BluetoothDevicesInfo with the priority information.
1409             for (int priority : mPrioritiesSupported) {
1410                 readAndTagDeviceWithPriorityFromSettings(profile, priority);
1411             }
1412         }
1413         return true;
1414     }
1415 
1416     /**
1417      * Read from Secure Settings if there are primary or secondary devices marked for this
1418      * Bluetooth profile.  If there are tagged devices, update the BluetoothDevicesInfo so the
1419      * policy can prioritize those devices when making connection attempts.
1420      *
1421      * @param profile - Bluetooth Profile to check
1422      * @param priority - Priority to check
1423      */
readAndTagDeviceWithPriorityFromSettings(int profile, int priority)1424     private void readAndTagDeviceWithPriorityFromSettings(int profile, int priority) {
1425         BluetoothDevicesInfo devicesInfo = mProfileToConnectableDevicesMap.get(profile);
1426         if (devicesInfo == null) {
1427             return;
1428         }
1429         if (!mCarBluetoothService.isPriorityDevicePresent(profile, priority)) {
1430             // There is no device for this priority - either it hasn't been set or has been removed.
1431             // So check if the policy has a device associated with this priority and remove it.
1432             BluetoothDevice deviceToClear = devicesInfo.getBluetoothDeviceForPriorityLocked(
1433                     priority);
1434             if (deviceToClear != null) {
1435                 if (DBG) {
1436                     Log.d(TAG, "Clearing priority for: " + deviceToClear.getAddress());
1437                 }
1438                 devicesInfo.removeBluetoothDevicePriorityLocked(deviceToClear);
1439             }
1440         } else {
1441             // There is a device with the given priority for the given profile.  Update the
1442             // policy's records.
1443             String deviceName = mCarBluetoothService.getDeviceNameWithPriority(profile,
1444                     priority);
1445             if (deviceName != null) {
1446                 BluetoothDevice bluetoothDevice = getBondedDeviceWithGivenName(deviceName);
1447                 if (bluetoothDevice != null) {
1448                     if (DBG) {
1449                         Log.d(TAG, "Setting priority: " + priority + " for " + deviceName);
1450                     }
1451                     tagDeviceWithPriority(bluetoothDevice, profile, priority);
1452                 }
1453             }
1454         }
1455     }
1456 
1457     /**
1458      * Tag a Bluetooth device with priority - Primary or Secondary.  This only updates the policy's
1459      * record (BluetoothDevicesInfo) of the priority information.
1460      *
1461      * @param device   - BluetoothDevice to tag
1462      * @param profile  - BluetoothProfile to tag
1463      * @param priority - Priority to tag with
1464      */
1465     @VisibleForTesting
tagDeviceWithPriority(BluetoothDevice device, int profile, int priority)1466     void tagDeviceWithPriority(BluetoothDevice device, int profile, int priority) {
1467         BluetoothDevicesInfo devicesInfo = mProfileToConnectableDevicesMap.get(profile);
1468         if (device != null) {
1469             if (DBG) {
1470                 Log.d(TAG, "Profile: " + profile + " : " + device + " Priority: " + priority);
1471             }
1472             devicesInfo.setBluetoothDevicePriorityLocked(device, priority);
1473         }
1474     }
1475 
1476     /**
1477      * Given the device name, find the corresponding {@link BluetoothDevice} from the list of
1478      * Bonded devices.
1479      *
1480      * @param name Bluetooth Device name
1481      */
1482     @Nullable
getBondedDeviceWithGivenName(String name)1483     private BluetoothDevice getBondedDeviceWithGivenName(String name) {
1484         if (mBluetoothAdapter == null) {
1485             if (DBG) {
1486                 Log.d(TAG, "Bluetooth Adapter Null");
1487             }
1488             return null;
1489         }
1490         if (name == null) {
1491             Log.w(TAG, "getBondedDeviceWithGivenName() Passing in a null name");
1492             return null;
1493         }
1494         if (DBG) {
1495             Log.d(TAG, "Looking for bonded device: " + name);
1496         }
1497         BluetoothDevice btDevice = null;
1498         Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
1499         for (BluetoothDevice bd : bondedDevices) {
1500             if (name.equals(bd.getAddress())) {
1501                 btDevice = bd;
1502                 break;
1503             }
1504         }
1505         return btDevice;
1506     }
1507 
1508 
dump(PrintWriter writer)1509     public void dump(PrintWriter writer) {
1510         writer.println("*BluetoothDeviceConnectionPolicy*");
1511         printDeviceMap(writer);
1512         mBluetoothAutoConnectStateMachine.dump(writer);
1513     }
1514 }
1515