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