blob: 072e2d680155aca49ee617183b2d6fdfed8ad5ac [file] [log] [blame]
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001/*
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
17package com.android.car;
18
Ram Periathiruvadiacb60242017-04-13 16:19:09 -070019import android.app.ActivityManager;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080020import android.bluetooth.BluetoothA2dpSink;
21import android.bluetooth.BluetoothAdapter;
22import android.bluetooth.BluetoothDevice;
23import android.bluetooth.BluetoothHeadsetClient;
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080024import android.bluetooth.BluetoothMapClient;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080025import android.bluetooth.BluetoothPbapClient;
26import android.bluetooth.BluetoothProfile;
27import android.car.hardware.CarPropertyValue;
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -080028import android.car.hardware.CarSensorEvent;
29import android.car.hardware.CarSensorManager;
30import android.car.hardware.ICarSensorEventListener;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080031import android.car.hardware.cabin.CarCabinManager;
32import android.car.hardware.property.CarPropertyEvent;
33import android.car.hardware.property.ICarPropertyEventListener;
Ram Periathiruvadiacb60242017-04-13 16:19:09 -070034import android.car.ICarBluetoothUserService;
35import android.car.ICarUserService;
36
37import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICES;
38import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICES;
39import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICES;
40
41import android.os.UserHandle;
42import android.provider.Settings;
43
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080044import android.content.BroadcastReceiver;
45import android.content.Context;
46import android.content.Intent;
47import android.content.IntentFilter;
48import android.os.RemoteException;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080049import android.util.Log;
50
Ram Periathiruvadiacb60242017-04-13 16:19:09 -070051import java.lang.StringBuilder;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080052import java.io.PrintWriter;
53import java.util.ArrayList;
54import java.util.Arrays;
55import java.util.HashMap;
56import java.util.List;
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080057import java.util.Map;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080058import java.util.Set;
Ram Periathiruvadiacb60242017-04-13 16:19:09 -070059import java.util.concurrent.locks.ReentrantLock;
60
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080061
62/**
63 * A Bluetooth Device Connection policy that is specific to the use cases of a Car. A car's
Ram Periathiruvadiacb60242017-04-13 16:19:09 -070064 * bluetooth capabilities in terms of the profiles it supports and its use cases are unique.
65 * Hence the CarService manages the policy that drives when and what to connect to.
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080066 *
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080067 * When to connect:
Ram Periathiruvadiacb60242017-04-13 16:19:09 -070068 * The policy can be configured to listen to various vehicle events that are appropriate to
69 * trigger a connection attempt. Signals like door unlock/open, ignition state changes indicate
70 * user entry and there by attempt to connect to their devices. This removes the need for the user
71 * to manually connect his device everytime they get in a car.
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080072 *
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080073 * Which device to connect:
Ram Periathiruvadiacb60242017-04-13 16:19:09 -070074 * The policy also keeps track of the {Profile : DevicesThatCanConnectOnTheProfile} and when
75 * it is time to connect, picks the device that is appropriate and available.
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080076 * For every profile, the policy attempts to connect to the last connected device first. The policy
77 * maintains a list of connect-able devices for every profile, in the order of how recently they
78 * connected. The device that successfully connects on a profile is moved to the top of the list
79 * of devices for that profile, so the next time a connection attempt is made, the policy starts
80 * with the last connected device first.
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080081 */
82
83public class BluetoothDeviceConnectionPolicy {
84 private static final String TAG = "BTDevConnectionPolicy";
Ram Periathiruvadiacb60242017-04-13 16:19:09 -070085 private static final String SETTINGS_DELIMITER = ",";
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080086 private static final boolean DBG = false;
Ram Periathiruvadiacb60242017-04-13 16:19:09 -070087 private final Context mContext;
Pavel Maltsevc9e86e82017-03-22 11:37:21 -070088 private boolean mInitialized = false;
Ram Periathiruvadia048c0a2017-05-09 07:35:03 -070089 private boolean mUserSpecificInfoInitialized = false;
90 private final Object mSetupLock = new Object();
Pavel Maltsevc9e86e82017-03-22 11:37:21 -070091
Ram Periathiruvadiacb60242017-04-13 16:19:09 -070092 // The main data structure that holds on to the {profile:list of known and connectible devices}
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080093 private HashMap<Integer, BluetoothDevicesInfo> mProfileToConnectableDevicesMap;
Ram Periathiruvadiacb60242017-04-13 16:19:09 -070094
95 // The foll. number of connections are what the Bluetooth services and stack supports
96 // and has been tested with. MAP and A2DP are limited to one connection only. HFP and PBAP,
97 // though having the capability to support more than 2, has been tested with 2 connections.
98 private static final int NUM_SUPPORTED_PHONE_CONNECTIONS = 2; // num of HFP and PBAP connections
99 private static final int NUM_SUPPORTED_MSG_CONNECTIONS = 1; // num of MAP connections
100 private static final int NUM_SUPPORTED_MUSIC_CONNECTIONS = 1; // num of A2DP connections
101 private Map<Integer, Integer> mNumSupportedActiveConnections;
102
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700103 private BluetoothAutoConnectStateMachine mBluetoothAutoConnectStateMachine;
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800104 private final BluetoothAdapter mBluetoothAdapter;
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700105 private BroadcastReceiver mBluetoothBroadcastReceiver;
106
107 private ICarUserService mCarUserService;
108 private PerUserCarServiceHelper mUserServiceHelper;
109 private ICarBluetoothUserService mCarBluetoothUserService;
110 private ReentrantLock mCarUserServiceAccessLock;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800111
112 // Events that are listened to for triggering an auto-connect:
113 // Cabin events like Door unlock coming from the Cabin Service.
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800114 private final CarCabinService mCarCabinService;
115 private final CarPropertyListener mCabinEventListener;
116 // Sensor events like Ignition switch ON from the Car Sensor Service
117 private final CarSensorService mCarSensorService;
118 private final CarSensorEventListener mCarSensorEventListener;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800119
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700120 // PerUserCarService related listeners
121 private final UserServiceConnectionCallback mServiceCallback;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800122
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700123 // The Bluetooth profiles that the CarService will try to auto-connect on.
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700124 private final List<Integer> mProfilesToConnect;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800125 private static final int MAX_CONNECT_RETRIES = 1;
126
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800127 private static final int PROFILE_NOT_AVAILABLE = -1;
128
129 // Device & Profile currently being connected on
130 private ConnectionParams mConnectionInFlight;
131
132 public static BluetoothDeviceConnectionPolicy create(Context context,
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700133 CarCabinService carCabinService, CarSensorService carSensorService,
134 PerUserCarServiceHelper userServiceHelper) {
135 return new BluetoothDeviceConnectionPolicy(context, carCabinService, carSensorService,
136 userServiceHelper);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800137 }
138
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800139 private BluetoothDeviceConnectionPolicy(Context context, CarCabinService carCabinService,
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700140 CarSensorService carSensorService, PerUserCarServiceHelper userServiceHelper) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800141 mContext = context;
142 mCarCabinService = carCabinService;
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800143 mCarSensorService = carSensorService;
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700144 mUserServiceHelper = userServiceHelper;
145 mCarUserServiceAccessLock = new ReentrantLock();
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700146 mProfilesToConnect = Arrays.asList(
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700147 BluetoothProfile.HEADSET_CLIENT, BluetoothProfile.A2DP_SINK,
148 BluetoothProfile.PBAP_CLIENT, BluetoothProfile.MAP_CLIENT);
149 // mNumSupportedActiveConnections is a HashMap of mProfilesToConnect and the number of
150 // connections each profile supports currently.
151 mNumSupportedActiveConnections = new HashMap<>(mProfilesToConnect.size());
152 for (Integer profile: mProfilesToConnect) {
153 switch (profile) {
154 case BluetoothProfile.HEADSET_CLIENT:
155 mNumSupportedActiveConnections.put(BluetoothProfile.HEADSET_CLIENT,
156 NUM_SUPPORTED_PHONE_CONNECTIONS);
157 break;
158 case BluetoothProfile.PBAP_CLIENT:
159 mNumSupportedActiveConnections.put(BluetoothProfile.PBAP_CLIENT,
160 NUM_SUPPORTED_PHONE_CONNECTIONS);
161 break;
162 case BluetoothProfile.A2DP_SINK:
163 mNumSupportedActiveConnections.put(BluetoothProfile.A2DP_SINK,
164 NUM_SUPPORTED_MUSIC_CONNECTIONS);
165 break;
166 case BluetoothProfile.MAP_CLIENT:
167 mNumSupportedActiveConnections.put(BluetoothProfile.MAP_CLIENT,
168 NUM_SUPPORTED_MSG_CONNECTIONS);
169 break;
170 }
171 }
172
173 // Listen to Cabin events for triggering auto connect
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800174 mCabinEventListener = new CarPropertyListener();
175 mCarSensorEventListener = new CarSensorEventListener();
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700176 // Listen to User switching to connect to per User device.
177 mServiceCallback = new UserServiceConnectionCallback();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800178 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800179 if (mBluetoothAdapter == null) {
180 Log.w(TAG, "No Bluetooth Adapter Available");
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800181 }
182 }
183
184 /**
185 * ConnectionParams - parameters/objects relevant to the bluetooth connection calls.
186 * This encapsulates the information that is passed around across different methods in the
187 * policy. Contains the bluetooth device {@link BluetoothDevice} and the list of profiles that
188 * we want that device to connect on.
189 * Used as the currency that methods use to talk to each other in the policy.
190 */
191 public static class ConnectionParams {
192 private BluetoothDevice mBluetoothDevice;
193 private Integer mBluetoothProfile;
194
195 public ConnectionParams() {
196 // default constructor
197 }
198
199 public ConnectionParams(Integer profile) {
200 mBluetoothProfile = profile;
201 }
202
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700203 public ConnectionParams(Integer profile, BluetoothDevice device) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800204 mBluetoothProfile = profile;
205 mBluetoothDevice = device;
206 }
207
208 // getters & Setters
209 public void setBluetoothDevice(BluetoothDevice device) {
210 mBluetoothDevice = device;
211 }
212
213 public void setBluetoothProfile(Integer profile) {
214 mBluetoothProfile = profile;
215 }
216
217 public BluetoothDevice getBluetoothDevice() {
218 return mBluetoothDevice;
219 }
220
221 public Integer getBluetoothProfile() {
222 return mBluetoothProfile;
223 }
224 }
225
226 /**
227 * BluetoothBroadcastReceiver receives the bluetooth related intents that are relevant to
228 * connection
229 * and bonding state changes. Reports the information to the {@link
230 * BluetoothDeviceConnectionPolicy}
231 * for it update its status.
232 */
233 public class BluetoothBroadcastReceiver extends BroadcastReceiver {
234 @Override
235 public void onReceive(Context context, Intent intent) {
236 String action = intent.getAction();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800237 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700238 if (DBG) {
239 if (device != null) {
240 Log.d(TAG, "Received Intent for device: " + device.getName() + action);
241 } else {
242 Log.d(TAG, "Received Intent no device: " + action);
243 }
244 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800245 ConnectionParams connectParams;
246 if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
247 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
248 BluetoothDevice.ERROR);
249 updateBondState(device, bondState);
250
251 } else if (BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700252 connectParams = new ConnectionParams(BluetoothProfile.A2DP_SINK, device);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800253 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
254 BluetoothProfile.STATE_DISCONNECTED);
255 notifyConnectionStatus(connectParams, currState);
256
257 } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700258 connectParams = new ConnectionParams(BluetoothProfile.HEADSET_CLIENT, device);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800259 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
260 BluetoothProfile.STATE_DISCONNECTED);
261 notifyConnectionStatus(connectParams, currState);
262
263 } else if (BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700264 connectParams = new ConnectionParams(BluetoothProfile.PBAP_CLIENT, device);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800265 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
266 BluetoothProfile.STATE_DISCONNECTED);
267 notifyConnectionStatus(connectParams, currState);
268
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800269 } else if (BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700270 connectParams = new ConnectionParams(BluetoothProfile.MAP_CLIENT, device);
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800271 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
272 BluetoothProfile.STATE_DISCONNECTED);
273 notifyConnectionStatus(connectParams, currState);
274
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800275 } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700276 int currState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800277 if (DBG) {
278 Log.d(TAG, "Bluetooth Adapter State: " + currState);
279 }
280 if (currState == BluetoothAdapter.STATE_ON) {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700281 // Read from Settings which devices to connect to and populate
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800282 // mProfileToConnectableDevicesMap
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700283 readAndRebuildDeviceMapFromSettings();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800284 initiateConnection();
285 } else if (currState == BluetoothAdapter.STATE_OFF) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800286 // Write currently connected device snapshot to file.
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700287 writeDeviceInfoToSettings();
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800288 resetBluetoothDevicesConnectionInfo();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800289 }
290 }
291 }
292 }
293
294 /**
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700295 * Cleanup state and reinitialize whenever we connect to the PerUserCarService.
296 * This happens in init() and whenever the PerUserCarService is restarted on User Switch Events
297 */
298 private class UserServiceConnectionCallback implements PerUserCarServiceHelper.ServiceCallback {
299 @Override
300 public void onServiceConnected(ICarUserService carUserService) {
301 if (mCarUserServiceAccessLock != null) {
302 mCarUserServiceAccessLock.lock();
303 try {
304 mCarUserService = carUserService;
305 } finally {
306 mCarUserServiceAccessLock.unlock();
307 }
308 }
309 if (DBG) {
310 Log.d(TAG, "Connected to PerUserCarService");
311 }
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700312 // re-initialize for current user.
313 initializeUserSpecificInfo();
314 }
315
316 @Override
317 public void onPreUnbind() {
318 if (DBG) {
319 Log.d(TAG, "Before Unbinding from UserService");
320 }
321 try {
322 if (mCarBluetoothUserService != null) {
323 mCarBluetoothUserService.closeBluetoothConnectionProxy();
324 }
325 } catch (RemoteException e) {
326 Log.e(TAG,
327 "Remote Exception during closeBluetoothConnectionProxy(): " + e.getMessage());
328 }
Ram Periathiruvadia048c0a2017-05-09 07:35:03 -0700329 // Clean up information related to user who went background.
330 cleanupUserSpecificInfo();
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700331 }
332 @Override
333 public void onServiceDisconnected() {
334 if (DBG) {
335 Log.d(TAG, "Disconnected from PerUserCarService");
336 }
337 if (mCarUserServiceAccessLock != null) {
338 mCarUserServiceAccessLock.lock();
339 try {
340 mCarBluetoothUserService = null;
341 mCarUserService = null;
342 } finally {
343 mCarUserServiceAccessLock.unlock();
344 }
345 }
346 }
347 }
348
349 /**
350 * Gets the Per User Car Bluetooth Service (ICarBluetoothService) from the PerUserCarService
351 * which acts as a top level Service running in the current user context.
352 * Also sets up the connection proxy objects required to communicate with the Bluetooth
353 * Profile Services.
354 * @return ICarBluetoothUserService running in current user
355 */
356 private ICarBluetoothUserService setupBluetoothUserService() {
357 ICarBluetoothUserService carBluetoothUserService = null;
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700358 if (mCarUserService != null) {
359 try {
360 carBluetoothUserService = mCarUserService.getBluetoothUserService();
361 if (carBluetoothUserService != null) {
362 if (DBG) {
363 Log.d(TAG, "Got CarBTUsrSvc");
364 }
365 carBluetoothUserService.setupBluetoothConnectionProxy();
366 }
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700367 } catch (RemoteException e) {
368 Log.e(TAG, "Remote Service Exception on ServiceConnection Callback: "
369 + e.getMessage());
370 }
371 } else {
372 if (DBG) {
373 Log.d(TAG, "PerUserCarService not connected");
374 }
375 }
376 return carBluetoothUserService;
377 }
378
379 /**
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800380 * Setup the Bluetooth profile service connections and Vehicle Event listeners.
381 * and start the state machine -{@link BluetoothAutoConnectStateMachine}
382 */
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700383 public synchronized void init() {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800384 if (DBG) {
385 Log.d(TAG, "init()");
386 }
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700387 // Initialize information specific to current user.
388 initializeUserSpecificInfo();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800389 // Listen to various events coming from the vehicle.
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700390 setupEventListenersLocked();
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700391 mInitialized = true;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800392 }
393
394 /**
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700395 * Setup and initialize information that is specific per User account, which involves:
396 * 1. Reading the list of devices to connect for current user and initialize the deviceMap
397 * with that information.
398 * 2. Register a BroadcastReceiver for bluetooth related events for the current user.
399 * 3. Start and bind to {@link PerUserCarService} as current user.
400 * 4. Start the {@link BluetoothAutoConnectStateMachine}
401 */
Ram Periathiruvadia048c0a2017-05-09 07:35:03 -0700402 private void initializeUserSpecificInfo() {
403 synchronized (mSetupLock) {
404 if (DBG) {
405 Log.d(TAG, "initializeUserSpecificInfo()");
406 }
407 if (mUserSpecificInfoInitialized) {
408 if (DBG) {
409 Log.d(TAG, "Already Initialized");
410 }
411 return;
412 }
413 mBluetoothAutoConnectStateMachine = BluetoothAutoConnectStateMachine.make(this);
414 readAndRebuildDeviceMapFromSettings();
415 setupBluetoothEventsIntentFilterLocked();
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700416
Ram Periathiruvadia048c0a2017-05-09 07:35:03 -0700417 mConnectionInFlight = new ConnectionParams();
418 // Get the BluetoothUserService and also setup the Bluetooth Connection Proxy for
419 // all profiles.
420 mCarBluetoothUserService = setupBluetoothUserService();
421 mUserSpecificInfoInitialized = true;
422 }
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700423 }
424
425 /**
426 * Setting up the Intent filter for Bluetooth related broadcasts
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800427 * This includes knowing when the
428 * 1. Bluetooth Adapter turned on/off
429 * 2. Bonding State of a device changes
430 * 3. A specific profile's connection state changes.
431 */
Ram Periathiruvadia048c0a2017-05-09 07:35:03 -0700432 private void setupBluetoothEventsIntentFilterLocked() {
433 mBluetoothBroadcastReceiver = new BluetoothBroadcastReceiver();
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700434 IntentFilter profileFilter = new IntentFilter();
435 profileFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
436 profileFilter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
437 profileFilter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
438 profileFilter.addAction(BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED);
439 profileFilter.addAction(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED);
440 profileFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700441 mContext.registerReceiverAsUser(mBluetoothBroadcastReceiver, UserHandle.CURRENT,
442 profileFilter, null, null);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800443 }
444
445 /**
446 * Initialize the {@link #mProfileToConnectableDevicesMap}.
447 * {@link #mProfileToConnectableDevicesMap} stores the profile:DeviceList information. This
448 * method retrieves it from persistent memory.
449 */
450 private synchronized void initDeviceMap() {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700451 if (mProfileToConnectableDevicesMap == null) {
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700452 mProfileToConnectableDevicesMap = new HashMap<>();
453 for (Integer profile : mProfilesToConnect) {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700454 // Build the BluetoothDevicesInfo for this profile.
455 BluetoothDevicesInfo devicesInfo = new BluetoothDevicesInfo(profile,
456 mNumSupportedActiveConnections.get(profile));
457 mProfileToConnectableDevicesMap.put(profile, devicesInfo);
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700458 }
459 if (DBG) {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700460 Log.d(TAG, "Created a new empty Device Map");
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800461 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800462 }
463 }
464
465 /**
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800466 * Setting up Listeners to the various events we are interested in listening to for initiating
467 * Bluetooth connection attempts.
468 */
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700469 private void setupEventListenersLocked() {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800470 // Setting up a listener for events from CarCabinService
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800471 // For now, we listen to door unlock signal coming from {@link CarCabinService},
472 // and Ignition state START from {@link CarSensorService}
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800473 mCarCabinService.registerListener(mCabinEventListener);
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800474 mCarSensorService.registerOrUpdateSensorListener(
475 CarSensorManager.SENSOR_TYPE_IGNITION_STATE, 0, mCarSensorEventListener);
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700476 mUserServiceHelper.registerServiceCallback(mServiceCallback);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800477 }
478
479 /**
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800480 * Handles events coming in from the {@link CarCabinService}
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800481 * The events that can trigger Bluetooth Scanning from CarCabinService is Door Unlock.
482 * Upon receiving the event that is of interest, initiate a connection attempt by calling
483 * the policy {@link BluetoothDeviceConnectionPolicy}
484 */
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800485 private class CarPropertyListener extends ICarPropertyEventListener.Stub {
486 @Override
487 public void onEvent(CarPropertyEvent event) throws RemoteException {
488 if (DBG) {
489 Log.d(TAG, "Cabin change Event : " + event.getEventType());
490 }
491 Boolean locked;
492 CarPropertyValue value = event.getCarPropertyValue();
493 Object o = value.getValue();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800494
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800495 if (value.getPropertyId() == CarCabinManager.ID_DOOR_LOCK) {
496 if (o instanceof Boolean) {
497 locked = (Boolean) o;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800498 if (DBG) {
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800499 Log.d(TAG, "Door Lock: " + locked);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800500 }
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800501 // Attempting a connection only on a door unlock
502 if (!locked) {
503 initiateConnection();
504 }
505 }
506 }
507 }
508 }
509
510 /**
511 * Handles events coming in from the {@link CarSensorService}
512 * The events that can trigger Bluetooth Scanning from CarSensorService is Ignition START.
513 * Upon receiving the event that is of interest, initiate a connection attempt by calling
514 * the policy {@link BluetoothDeviceConnectionPolicy}
515 */
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800516 private class CarSensorEventListener extends ICarSensorEventListener.Stub {
517 @Override
518 public void onSensorChanged(List<CarSensorEvent> events) throws RemoteException {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700519 if (events != null & !events.isEmpty()) {
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800520 CarSensorEvent event = events.get(0);
521 if (DBG) {
522 Log.d(TAG, "Sensor event Type : " + event.sensorType);
523 }
524 if (event.sensorType == CarSensorManager.SENSOR_TYPE_IGNITION_STATE) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800525 if (DBG) {
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800526 Log.d(TAG, "Sensor value : " + event.intValues[0]);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800527 }
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800528 if (event.intValues[0] == CarSensorEvent.IGNITION_STATE_START) {
529 initiateConnection();
530 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800531 }
532 }
533 }
534 }
535
536 /**
537 * Clean up slate. Close the Bluetooth profile service connections and quit the state machine -
538 * {@link BluetoothAutoConnectStateMachine}
539 */
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700540 public synchronized void release() {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800541 if (DBG) {
542 Log.d(TAG, "release()");
543 }
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700544 mInitialized = false;
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700545 writeDeviceInfoToSettings();
546 cleanupUserSpecificInfo();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800547 closeEventListeners();
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700548 }
549
550 /**
551 * Clean up information related to user who went background.
552 */
Ram Periathiruvadia048c0a2017-05-09 07:35:03 -0700553 private void cleanupUserSpecificInfo() {
554 synchronized (mSetupLock) {
555 if (DBG) {
556 Log.d(TAG, "cleanupUserSpecificInfo()");
557 }
558 if (!mUserSpecificInfoInitialized) {
559 if (DBG) {
560 Log.d(TAG, "User specific Info Not initialized..Not cleaning up");
561 }
562 return;
563 }
564 mUserSpecificInfoInitialized = false;
565 // quit the state machine
566 mBluetoothAutoConnectStateMachine.doQuit();
567 mProfileToConnectableDevicesMap = null;
568 mConnectionInFlight = null;
569 if (mBluetoothBroadcastReceiver != null) {
570 mContext.unregisterReceiver(mBluetoothBroadcastReceiver);
571 mBluetoothBroadcastReceiver = null;
572 }
573 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800574 }
575
576 /**
577 * Unregister the listeners to the various Vehicle events coming from other parts of the
578 * CarService
579 */
580 private void closeEventListeners() {
Ram Periathiruvadia048c0a2017-05-09 07:35:03 -0700581 if (DBG) {
582 Log.d(TAG, "closeEventListeners()");
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800583 }
Ram Periathiruvadia048c0a2017-05-09 07:35:03 -0700584 mCarCabinService.unregisterListener(mCabinEventListener);
585 mCarSensorService.unregisterSensorListener(CarSensorManager.SENSOR_TYPE_IGNITION_STATE,
586 mCarSensorEventListener);
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700587 mUserServiceHelper.unregisterServiceCallback(mServiceCallback);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800588 }
589
590 /**
591 * Resets the {@link BluetoothDevicesInfo#mConnectionInfo} of all the profiles to start from
592 * a clean slate. The ConnectionInfo has all the book keeping information regarding the state
593 * of connection attempts - like which device in the device list for the profile is the next
594 * to try connecting etc.
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700595 * This method does not clear the {@link BluetoothDevicesInfo#mDeviceInfoList} like the {@link
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800596 * #resetProfileToConnectableDevicesMap()} method does.
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800597 */
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800598 private synchronized void resetBluetoothDevicesConnectionInfo() {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800599 if (DBG) {
600 Log.d(TAG, "Resetting ConnectionInfo for all profiles");
601 }
602 for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) {
603 devInfo.resetConnectionInfoLocked();
604 }
605 }
606
607 /**
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800608 * Resets the {@link #mProfileToConnectableDevicesMap} to a clean and empty slate.
609 */
610 public synchronized void resetProfileToConnectableDevicesMap() {
611 if (DBG) {
612 Log.d(TAG, "Resetting the mProfilesToConnectableDevicesMap");
613 }
614 for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) {
615 devInfo.resetDeviceListLocked();
616 }
617 }
618
619 /**
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800620 * Returns the list of profiles that the Autoconnection policy attempts to connect on
621 *
622 * @return profile list.
623 */
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700624 public synchronized List<Integer> getProfilesToConnect() {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800625 return mProfilesToConnect;
626 }
627
628 /**
629 * Add a new Profile to the list of To Be Connected profiles.
630 *
631 * @param profile - ProfileInfo of the new profile to be added.
632 */
633 public synchronized void addProfile(Integer profile) {
634 mProfilesToConnect.add(profile);
635 }
636
637 /**
638 * Add or remove a device based on the bonding state change.
639 *
640 * @param device - device to add/remove
641 * @param bondState - current bonding state
642 */
643 private void updateBondState(BluetoothDevice device, int bondState) {
644 if (device == null) {
645 Log.e(TAG, "updateBondState: device Null");
646 return;
647 }
648 if (DBG) {
649 Log.d(TAG, "BondState :" + bondState + " Device: " + device.getName());
650 }
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800651 // Bonded devices are added to a profile's device list after the device CONNECTS on the
652 // profile. When unpaired, we remove the device from all of the profiles' device list.
653 if (bondState == BluetoothDevice.BOND_NONE) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800654 for (Integer profile : mProfilesToConnect) {
655 removeDeviceFromProfile(device, profile);
656 }
657 }
658
659 }
660
661 /**
662 * Add a new device to the list of devices connect-able on the given profile
663 *
664 * @param device - Bluetooth device to be added
665 * @param profile - profile to add the device to.
666 */
667 private synchronized void addDeviceToProfile(BluetoothDevice device, Integer profile) {
668 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
669 if (devInfo == null) {
670 if (DBG) {
671 Log.d(TAG, "Creating devInfo for profile: " + profile);
672 }
673 devInfo = new BluetoothDevicesInfo(profile);
674 mProfileToConnectableDevicesMap.put(profile, devInfo);
675 }
676 devInfo.addDeviceLocked(device);
677 }
678
679 /**
680 * Remove the device from the list of devices connect-able on the gievn profile.
681 *
682 * @param device - Bluetooth device to be removed
683 * @param profile - profile to remove the device from
684 */
685 private synchronized void removeDeviceFromProfile(BluetoothDevice device, Integer profile) {
686 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
687 if (devInfo != null) {
688 devInfo.removeDeviceLocked(device);
689 }
690 }
691
692 /**
693 * Initiate a bluetooth connection.
694 */
695 private void initiateConnection() {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800696 // Make sure the bluetooth adapter is available & enabled.
697 if (mBluetoothAdapter == null) {
698 Log.w(TAG, "Bluetooth Adapter null");
699 return;
700 }
701
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800702 if (mBluetoothAdapter.isEnabled()) {
703 if (isDeviceMapEmpty()) {
704 if (DBG) {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700705 Log.d(TAG, "Device Map is empty. Nothing to connect to");
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800706 }
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700707 return;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800708 }
709 resetDeviceAvailableToConnect();
710 if (DBG) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800711 Log.d(TAG, "initiateConnection() Reset Device Availability");
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800712 }
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700713 mBluetoothAutoConnectStateMachine.sendMessage(BluetoothAutoConnectStateMachine
714 .CONNECT);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800715 } else {
716 if (DBG) {
717 Log.d(TAG, "Bluetooth Adapter not enabled.");
718 }
719 }
720 }
721
722 /**
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800723 * Find an unconnected profile and find a device to connect on it.
724 * Finds the appropriate device for the profile from the information available in
725 * {@link #mProfileToConnectableDevicesMap}
726 *
727 * @return true - if we found a device to connect on for any of the {@link #mProfilesToConnect}
728 * false - if we cannot find a device to connect to or if we are not ready to connect yet.
729 */
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800730 public synchronized boolean findDeviceToConnect() {
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700731 if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()
732 || mProfileToConnectableDevicesMap == null || !mInitialized) {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700733 if (DBG) {
734 if (mProfileToConnectableDevicesMap == null) {
735 Log.d(TAG, "findDeviceToConnect(): Device Map null");
736 } else {
737 Log.d(TAG, "findDeviceToConnect(): BT Adapter not enabled");
738 }
739 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800740 return false;
741 }
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700742 boolean connectingToADevice = false;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800743 // Get the first unconnected profile that we can try to make a connection
744 Integer nextProfile = getNextProfileToConnectLocked();
745 // Keep going through the profiles until we find a device that we can connect to
746 while (nextProfile != PROFILE_NOT_AVAILABLE) {
747 if (DBG) {
748 Log.d(TAG, "connectToProfile(): " + nextProfile);
749 }
750 // find a device that is next in line for a connection attempt for that profile
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700751 // and try connecting to it.
752 connectingToADevice = connectToNextDeviceInQueueLocked(nextProfile);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800753 // If we found a device to connect, break out of the loop
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700754 if (connectingToADevice) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800755 if (DBG) {
756 Log.d(TAG, "Found device to connect to");
757 }
758 BluetoothDeviceConnectionPolicy.ConnectionParams btParams =
759 new BluetoothDeviceConnectionPolicy.ConnectionParams(
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700760 mConnectionInFlight.getBluetoothProfile(),
761 mConnectionInFlight.getBluetoothDevice());
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800762 // set up a time out
763 mBluetoothAutoConnectStateMachine.sendMessageDelayed(
764 BluetoothAutoConnectStateMachine.CONNECT_TIMEOUT, btParams,
765 BluetoothAutoConnectStateMachine.CONNECTION_TIMEOUT_MS);
766 break;
767 } else {
768 // result will be false, if there are no more devices to connect
769 // or if the ProfileProxy objects are null (ServiceConnection
770 // not yet established for this profile)
771 if (DBG) {
772 Log.d(TAG, "No more device to connect on Profile: " + nextProfile);
773 }
774 nextProfile = getNextProfileToConnectLocked();
775 }
776 }
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700777 return connectingToADevice;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800778 }
779
780 /**
781 * Get the first unconnected profile.
782 *
783 * @return profile to connect.
784 * Special return value 0 if
785 * 1. all profiles have been connected on.
786 * 2. no profile connected but no nearby known device that can be connected to
787 */
788 private Integer getNextProfileToConnectLocked() {
789 for (Integer profile : mProfilesToConnect) {
790 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
791 if (devInfo != null) {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700792 if (devInfo.isProfileConnectableLocked()) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800793 return profile;
794 }
795 } else {
796 Log.e(TAG, "Unexpected: devInfo null for profile: " + profile);
797 }
798 }
799 // Reaching here denotes all profiles are connected or No devices available for any profile
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700800 if (DBG) {
801 Log.d(TAG, "No disconnected profiles");
802 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800803 return PROFILE_NOT_AVAILABLE;
804 }
805
806 /**
807 * Try to connect to the next device in the device list for the given profile.
808 *
809 * @param profile - profile to connect on
810 * @return - true if we found a device to connect on for this profile
811 * false - if we cannot find a device to connect to.
812 */
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700813 private boolean connectToNextDeviceInQueueLocked(Integer profile) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800814 // Get the Device Information for the given profile and find the next device to connect on
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700815 boolean connecting = true;
816 boolean proxyAvailable = true;
817 BluetoothDevice devToConnect = null;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800818 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
819 if (devInfo == null) {
820 Log.e(TAG, "Unexpected: No device Queue for this profile: " + profile);
821 return false;
822 }
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700823 // Check if the Bluetooth profile service's proxy object is available before
824 // attempting to connect.
825 if (mCarBluetoothUserService == null) {
826 mCarBluetoothUserService = setupBluetoothUserService();
827 }
828 if (mCarBluetoothUserService != null) {
829 try {
830 if (!mCarBluetoothUserService.isBluetoothConnectionProxyAvailable(profile)) {
831 // proxy unavailable.
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800832 if (DBG) {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700833 Log.d(TAG,
834 "Proxy for Bluetooth Profile Service Unavailable: " + profile);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800835 }
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700836 proxyAvailable = false;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800837 }
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700838 } catch (RemoteException e) {
839 Log.e(TAG, "Car BT Service Remote Exception.");
840 proxyAvailable = false;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800841 }
842 } else {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700843 Log.d(TAG, "CarBluetoothUserSvc null. Car service not bound to PerUserCarSvc.");
844 proxyAvailable = false;
845 }
846
847 if (proxyAvailable) {
848 // Get the next device in the device list for this profile.
849 devToConnect = devInfo.getNextDeviceInQueueLocked();
850 if (devToConnect != null) {
851 // deviceAvailable && proxyAvailable
852 try {
853 if (mCarBluetoothUserService != null) {
854 mCarBluetoothUserService.bluetoothConnectToProfile((int) profile,
855 devToConnect);
856 } else {
857 Log.e(TAG, "CarBluetoothUserSvc null");
858 connecting = false;
859 }
860 } catch (RemoteException e) {
861 Log.e(TAG, "Remote User Service stopped responding: " + e.getMessage());
862 connecting = false;
863 }
864 } else {
865 // device unavailable
866 if (DBG) {
867 Log.d(TAG, "No paired nearby device to connect to for profile: " + profile);
868 }
869 connecting = false;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800870 }
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700871 } else {
872 connecting = false;
873 }
874
875 if (connecting && devToConnect != null) {
876 devInfo.setConnectionStateLocked(devToConnect, BluetoothProfile.STATE_CONNECTING);
877 // Increment the retry count & cache what is being connected to
878 // This method is already called from a synchronized context.
879 mConnectionInFlight.setBluetoothDevice(devToConnect);
880 mConnectionInFlight.setBluetoothProfile(profile);
881 devInfo.incrementRetryCountLocked();
882 if (DBG) {
883 Log.d(TAG, "Increment Retry to: " + devInfo.getRetryCountLocked());
884 }
885 } else {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800886 // reset the mConnectionInFlight
887 mConnectionInFlight.setBluetoothProfile(0);
888 mConnectionInFlight.setBluetoothDevice(null);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800889 devInfo.setDeviceAvailableToConnectLocked(false);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800890 }
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700891 return connecting;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800892 }
893
894 /**
895 * Update the device connection status for a profile and also notify the state machine.
896 * This gets called from {@link BluetoothBroadcastReceiver} when it receives a Profile's
897 * CONNECTION_STATE_CHANGED intent.
898 *
899 * @param params - {@link ConnectionParams} device and profile list info
900 * @param currentState - connection result to update
901 */
902 private void notifyConnectionStatus(ConnectionParams params, int currentState) {
903 // Update the profile's BluetoothDevicesInfo.
904 boolean isConnected;
905 switch (currentState) {
906 case BluetoothProfile.STATE_DISCONNECTED: {
907 isConnected = false;
908 break;
909 }
910
911 case BluetoothProfile.STATE_CONNECTED: {
912 isConnected = true;
913 break;
914 }
915
916 default: {
917 if (DBG) {
918 Log.d(TAG, "notifyConnectionStatus() Ignoring state: " + currentState);
919 }
920 return;
921 }
922
923 }
924
925 boolean updateSuccessful = updateDeviceConnectionStatus(params, isConnected);
926 if (updateSuccessful) {
927 if (isConnected) {
928 mBluetoothAutoConnectStateMachine.sendMessage(
929 BluetoothAutoConnectStateMachine.DEVICE_CONNECTED,
930 params);
931 } else {
932 mBluetoothAutoConnectStateMachine.sendMessage(
933 BluetoothAutoConnectStateMachine.DEVICE_DISCONNECTED,
934 params);
935 }
936 }
937 }
938
939 /**
940 * Update the profile's {@link BluetoothDevicesInfo} with the result of the connection
941 * attempt. This gets called from the {@link BluetoothAutoConnectStateMachine} when the
942 * connection attempt times out or from {@link BluetoothBroadcastReceiver} when it receives
943 * a Profile's CONNECTION_STATE_CHANGED intent.
944 *
945 * @param params - {@link ConnectionParams} device and profile list info
946 * @param didConnect - connection result to update
947 */
948 public synchronized boolean updateDeviceConnectionStatus(ConnectionParams params,
949 boolean didConnect) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800950 if (params == null || params.getBluetoothDevice() == null) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800951 Log.e(TAG, "updateDeviceConnectionStatus: null params");
952 return false;
953 }
954 // Get the profile to update
955 Integer profileToUpdate = params.getBluetoothProfile();
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800956 BluetoothDevice deviceThatConnected = params.getBluetoothDevice();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800957 if (DBG) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800958 Log.d(TAG, "Profile: " + profileToUpdate + " Connected: " + didConnect + " on "
959 + deviceThatConnected.getName());
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800960 }
961
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800962 // If the connection update is on a different profile or device (a very rare possibility),
963 // it is handled automatically. Just logging it here.
964 if (DBG) {
965 if (mConnectionInFlight != null && mConnectionInFlight.getBluetoothProfile() != null) {
966 if (profileToUpdate.equals(mConnectionInFlight.getBluetoothProfile()) == false) {
967 Log.d(TAG, "Updating profile " + profileToUpdate
968 + " different from connection in flight "
969 + mConnectionInFlight.getBluetoothProfile());
970 }
971 }
972
973 if (mConnectionInFlight != null && mConnectionInFlight.getBluetoothDevice() != null) {
974 if (deviceThatConnected.equals(mConnectionInFlight.getBluetoothDevice()) == false) {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700975 Log.d(TAG, "Updating device: " + deviceThatConnected.getName()
976 + " different from connection in flight: "
977 + mConnectionInFlight.getBluetoothDevice().getName());
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800978
979 }
980 }
981 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800982 BluetoothDevicesInfo devInfo = null;
983 devInfo = mProfileToConnectableDevicesMap.get(profileToUpdate);
984 if (devInfo == null) {
985 Log.e(TAG, "Unexpected: devInfo null for profile: " + profileToUpdate);
986 return false;
987 }
988
989 boolean retry = canRetryConnection(profileToUpdate);
990 // Update the status and also if a retry attempt can be made if the
991 // connection timed out in the previous attempt.
992 if (DBG) {
993 Log.d(TAG, "Retry? : " + retry);
994 }
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800995 devInfo.updateConnectionStatusLocked(deviceThatConnected, didConnect, retry);
996 // Write to persistent memory to have the latest snapshot available
Ram Periathiruvadiacb60242017-04-13 16:19:09 -0700997 writeDeviceInfoToSettings(params);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800998 return true;
999 }
1000
1001 /**
1002 * Returns if we can retry connection attempt on the given profile for the device that is
1003 * currently in the head of the queue.
1004 *
1005 * @param profile - Profile to check
1006 */
1007 private synchronized boolean canRetryConnection(Integer profile) {
1008 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
1009 if (devInfo == null) {
1010 Log.e(TAG, "Unexpected: No device Queue for this profile: " + profile);
1011 return false;
1012 }
1013 if (devInfo.getRetryCountLocked() < MAX_CONNECT_RETRIES) {
1014 return true;
1015 } else {
1016 return false;
1017 }
1018 }
1019
1020 /**
1021 * Helper method to see if there are any connect-able devices on any of the
1022 * profiles.
1023 *
1024 * @return true - if {@link #mProfileToConnectableDevicesMap} does not have any devices for any
1025 * profiles.
1026 * false - if {@link #mProfileToConnectableDevicesMap} has a device for at least one profile.
1027 */
1028 private synchronized boolean isDeviceMapEmpty() {
1029 boolean empty = true;
1030 for (Integer profile : mProfilesToConnect) {
1031 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
1032 if (devInfo != null) {
1033 if (devInfo.getNumberOfPairedDevicesLocked() != 0) {
1034 if (DBG) {
1035 Log.d(TAG, "Device map not empty. Profile: " + profile + " has "
1036 + devInfo.getNumberOfPairedDevicesLocked() + " paired devices");
1037 }
1038 empty = false;
1039 break;
1040 }
1041 }
1042 }
1043 return empty;
1044 }
1045
1046 /**
1047 * Reset the Device Available to Connect information for all profiles to Available.
1048 * If in a previous connection attempt, we failed to connect on all devices for a profile,
1049 * we would update deviceAvailableToConnect for that profile to false. That information
1050 * is used to deduce if we should move to the next profile. If marked false, we will not
1051 * try to connect on that profile anymore as part of that connection attempt.
1052 * However, when we get another connection trigger from the vehicle, we need to reset the
1053 * deviceAvailableToConnect information so we can start the connection attempts all over
1054 * again.
1055 */
1056 private synchronized void resetDeviceAvailableToConnect() {
1057 for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) {
1058 devInfo.setDeviceAvailableToConnectLocked(true);
1059 }
1060 }
1061
1062 /**
1063 * Utility function - Prints the Profile: list of devices information to log
1064 * Caller should wrap a DBG around this, since this is for debugging purpose.
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001065 *
1066 * @param writer - PrintWriter
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001067 */
1068 private synchronized void printDeviceMap(PrintWriter writer) {
1069 if (mProfileToConnectableDevicesMap == null) {
1070 return;
1071 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001072 for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001073 writer.print("Profile: " + devInfo.getProfileLocked() + "\t");
1074 writer.print("Active Connections: " + devInfo.getNumberOfActiveConnectionsLocked());
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001075 writer.println();
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001076 List<BluetoothDevicesInfo.DeviceInfo> deviceInfoList = devInfo.getDeviceInfoList();
1077 if (deviceInfoList != null) {
1078 for (BluetoothDevicesInfo.DeviceInfo devicesInfo : deviceInfoList) {
1079 if (devicesInfo.getBluetoothDevice() != null) {
1080 writer.print(devicesInfo.getBluetoothDevice().getName() + ":");
1081 writer.print(devicesInfo.getConnectionState() + "\t");
1082 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001083 }
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001084 writer.println();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001085 }
1086 }
1087 }
1088
1089 /**
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001090 * Write the device list for all bluetooth profiles that connected.
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001091 *
1092 * @return true if the write was successful, false otherwise
1093 */
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001094 private synchronized boolean writeDeviceInfoToSettings() {
1095 ConnectionParams params;
1096 boolean writeResult;
1097 for (Integer profile : mProfilesToConnect) {
1098 params = new ConnectionParams(profile);
1099 writeResult = writeDeviceInfoToSettings(params);
1100 if (!writeResult) {
1101 Log.e(TAG, "Error writing Device Info for profile:" + profile);
1102 return writeResult;
1103 }
1104 }
1105 return true;
1106 }
1107
1108 /**
1109 * Write information about which devices connected on which profile to Settings.Secure.
1110 * Essentially the list of devices that a profile can connect on the next auto-connect
1111 * attempt.
1112 *
1113 * @param params - ConnectionParams indicating which bluetooth profile to write this
1114 * information
1115 * for.
1116 * @return true if the write was successful, false otherwise
1117 */
1118 public synchronized boolean writeDeviceInfoToSettings(ConnectionParams params) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001119 boolean writeSuccess = true;
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001120 Integer profileToUpdate = params.getBluetoothProfile();
1121
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001122 if (mProfileToConnectableDevicesMap == null) {
1123 writeSuccess = false;
1124 } else {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001125 List<String> deviceNames = new ArrayList<>();
1126 BluetoothDevicesInfo devicesInfo = mProfileToConnectableDevicesMap.get(profileToUpdate);
1127 StringBuilder sb = new StringBuilder();
1128 String delimiter = ""; // start off with no delimiter.
1129
1130 // Iterate through the List<BluetoothDevice> and build a String that is
1131 // names of all devices to be connected for this profile joined together and
1132 // delimited by a delimiter (its a ',' here)
1133 if (devicesInfo != null && devicesInfo.getDeviceList() != null) {
1134 for (BluetoothDevice device : devicesInfo.getDeviceList()) {
1135 sb.append(delimiter);
1136 sb.append(device.getAddress());
1137 delimiter = SETTINGS_DELIMITER;
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001138 }
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001139
1140 }
1141 // joinedDeviceNames has something like "22:22:33:44:55:AB,22:23:xx:xx:xx:xx"
1142 // mac addresses of connectable devices separated by a delimiter
1143 String joinedDeviceNames = sb.toString();
1144 Log.d(TAG, "Profile: " + profileToUpdate + " Writing: " + joinedDeviceNames);
1145
1146 long userId = ActivityManager.getCurrentUser();
1147 switch (profileToUpdate) {
1148 case BluetoothProfile.A2DP_SINK:
1149 Settings.Secure.putStringForUser(mContext.getContentResolver(),
1150 KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICES,
1151 joinedDeviceNames, (int) userId);
1152 break;
1153
1154 case BluetoothProfile.HEADSET_CLIENT:
1155 Settings.Secure.putStringForUser(mContext.getContentResolver(),
1156 KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICES,
1157 joinedDeviceNames, (int) userId);
1158 break;
1159
1160 case BluetoothProfile.PBAP_CLIENT:
1161 // use the phone
1162 break;
1163
1164 case BluetoothProfile.MAP_CLIENT:
1165 Settings.Secure.putStringForUser(mContext.getContentResolver(),
1166 KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICES,
1167 joinedDeviceNames, (int) userId);
1168 break;
1169
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001170 }
1171 }
1172 return writeSuccess;
1173 }
1174
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001175 /**
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001176 * Read the device information from Settings.Secure and populate the
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001177 * {@link #mProfileToConnectableDevicesMap}
1178 *
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001179 * Device MAC addresses are written to Settings.Secure delimited by a ','.
1180 * Ex: android.car.BLUETOOTH_AUTOCONNECT_PHONE_DEVICES: xx:xx:xx:xx:xx:xx,yy:yy:yy:yy:yy
1181 * denotes that two devices with addresses xx:xx:xx:xx:xx:xx & yy:yy:yy:yy:yy:yy were connected
1182 * as phones (in HFP and PBAP profiles) the last time this user was logged in.
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001183 *
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001184 * @return - true if the read was successful, false if 1. BT Adapter not enabled 2. No prior
1185 * bonded devices 3. No information stored in Settings for this user.
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001186 */
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001187 public synchronized boolean readAndRebuildDeviceMapFromSettings() {
1188 List<String> deviceList;
1189 String devices = null;
1190 // Create and initialize mProfileToConnectableDevicesMap if needed.
1191 initDeviceMap();
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -08001192 if (mBluetoothAdapter != null) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001193 if (DBG) {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001194 Log.d(TAG,
1195 "Number of Bonded devices:" + mBluetoothAdapter.getBondedDevices().size());
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001196 }
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -08001197 if (mBluetoothAdapter.getBondedDevices().isEmpty()) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001198 if (DBG) {
1199 Log.d(TAG, "No Bonded Devices available. Quit rebuilding");
1200 }
1201 return false;
1202 }
1203 }
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001204 // Read from Settings.Secure for the current user. There are 3 keys 1 each for Phone
1205 // (HFP & PBAP), 1 for Music (A2DP) and 1 for Messaging device (MAP)
1206 long userId = ActivityManager.getCurrentUser();
1207 for (Integer profile : mProfilesToConnect) {
1208 switch (profile) {
1209 case BluetoothProfile.A2DP_SINK:
1210 devices = Settings.Secure.getStringForUser(mContext.getContentResolver(),
1211 KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICES, (int) userId);
1212 break;
1213 case BluetoothProfile.PBAP_CLIENT:
1214 // fall through
1215 case BluetoothProfile.HEADSET_CLIENT:
1216 devices = Settings.Secure.getStringForUser(mContext.getContentResolver(),
1217 KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICES, (int) userId);
1218 break;
1219 case BluetoothProfile.MAP_CLIENT:
1220 devices = Settings.Secure.getStringForUser(mContext.getContentResolver(),
1221 KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICES, (int) userId);
1222 break;
1223 default:
1224 Log.e(TAG, "Unexpected profile");
1225 break;
1226 }
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001227
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001228 if (devices == null) {
1229 if (DBG) {
1230 Log.d(TAG, "No device information stored in Settings");
1231 }
1232 return false;
1233 }
1234 if (DBG) {
Ram Periathiruvadia048c0a2017-05-09 07:35:03 -07001235 Log.d(TAG, "Devices in Settings: " + devices);
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001236 }
1237 // Get a list of Device Mac Addresses from the value
1238 deviceList = Arrays.asList(devices.split(SETTINGS_DELIMITER));
1239 if (deviceList == null) {
1240 return false;
1241 }
1242 BluetoothDevicesInfo devicesInfo = mProfileToConnectableDevicesMap.get(profile);
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001243 // Do we have a bonded device with this name? If so, get it and populate the device
1244 // map.
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001245 for (String address : deviceList) {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001246 BluetoothDevice deviceToAdd = getBondedDeviceWithGivenName(address);
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001247 if (deviceToAdd != null) {
1248 devicesInfo.addDeviceLocked(deviceToAdd);
1249 } else {
1250 if (DBG) {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001251 Log.d(TAG, "No device with name " + address + " found in bonded devices");
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001252 }
1253 }
1254 }
1255 mProfileToConnectableDevicesMap.put(profile, devicesInfo);
1256 }
Pavel Maltsevc9e86e82017-03-22 11:37:21 -07001257 return true;
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001258 }
1259
1260 /**
1261 * Given the device name, find the corresponding {@link BluetoothDevice} from the list of
1262 * Bonded devices.
1263 *
1264 * @param name Bluetooth Device name
1265 */
1266 private BluetoothDevice getBondedDeviceWithGivenName(String name) {
1267 if (mBluetoothAdapter == null) {
1268 if (DBG) {
1269 Log.d(TAG, "Bluetooth Adapter Null");
1270 }
1271 return null;
1272 }
1273 if (name == null) {
1274 Log.w(TAG, "getBondedDeviceWithGivenName() Passing in a null name");
1275 return null;
1276 }
1277 if (DBG) {
1278 Log.d(TAG, "Looking for bonded device: " + name);
1279 }
1280 BluetoothDevice btDevice = null;
1281 Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
1282 for (BluetoothDevice bd : bondedDevices) {
Ram Periathiruvadiacb60242017-04-13 16:19:09 -07001283 if (name.equals(bd.getAddress())) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001284 btDevice = bd;
1285 break;
1286 }
1287 }
1288 return btDevice;
1289 }
1290
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001291
1292 public void dump(PrintWriter writer) {
1293 writer.println("*BluetoothDeviceConnectionPolicy*");
1294 printDeviceMap(writer);
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001295 mBluetoothAutoConnectStateMachine.dump(writer);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001296 }
1297}