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