blob: b00002304b172d2b9e0eee91f230972c3d724ac1 [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
19import android.bluetooth.BluetoothA2dpSink;
20import android.bluetooth.BluetoothAdapter;
21import android.bluetooth.BluetoothDevice;
22import android.bluetooth.BluetoothHeadsetClient;
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080023import android.bluetooth.BluetoothMapClient;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080024import android.bluetooth.BluetoothPbapClient;
25import android.bluetooth.BluetoothProfile;
26import android.car.hardware.CarPropertyValue;
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -080027import android.car.hardware.CarSensorEvent;
28import android.car.hardware.CarSensorManager;
29import android.car.hardware.ICarSensorEventListener;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080030import android.car.hardware.cabin.CarCabinManager;
31import android.car.hardware.property.CarPropertyEvent;
32import android.car.hardware.property.ICarPropertyEventListener;
33import android.content.BroadcastReceiver;
34import android.content.Context;
35import android.content.Intent;
36import android.content.IntentFilter;
37import android.os.RemoteException;
38import android.util.AtomicFile;
39import android.util.Log;
40
41import java.io.File;
42import java.io.FileInputStream;
43import java.io.FileOutputStream;
44import java.io.IOException;
45import java.io.ObjectInputStream;
46import java.io.ObjectOutputStream;
47import java.io.PrintWriter;
48import java.util.ArrayList;
49import java.util.Arrays;
50import java.util.HashMap;
51import java.util.List;
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080052import java.util.Map;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080053import java.util.Set;
54
55/**
56 * A Bluetooth Device Connection policy that is specific to the use cases of a Car. A car's
57 * bluetooth capabilities in terms of the profiles it supports and its use cases are unique. Hence
58 * the CarService manages the policy that drives when and what to connect to.
59 *
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080060 * When to connect:
61 * The policy can be configured to listen to various vehicle events that are appropriate to trigger
62 * a connection attempt. Signals like door unlock/open, ignition state changes indicate user entry
63 * and there by attempt to connect to their devices. This removes the need for the user to manually
64 * connect his device everytime they get in a car.
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080065 *
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080066 * Which device to connect:
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080067 * The policy also keeps track of the {Profile : DevicesThatCanConnectOnTheProfile} and when it is
68 * time to connect, picks the device that is appropriate and available.
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080069 * For every profile, the policy attempts to connect to the last connected device first. The policy
70 * maintains a list of connect-able devices for every profile, in the order of how recently they
71 * connected. The device that successfully connects on a profile is moved to the top of the list
72 * of devices for that profile, so the next time a connection attempt is made, the policy starts
73 * with the last connected device first.
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080074 */
75
76public class BluetoothDeviceConnectionPolicy {
77 private static final String TAG = "BTDevConnectionPolicy";
78 private static final boolean DBG = false;
79 private Context mContext;
80
Pavel Maltsevc9e86e82017-03-22 11:37:21 -070081 private boolean mInitialized = false;
82
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080083 // The main datastructure that holds on to the {profile:list of known and connectible devices}
84 private HashMap<Integer, BluetoothDevicesInfo> mProfileToConnectableDevicesMap;
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080085 // mProfileToConnectableDevicesInfo - holds information to serialize and write
86 // to file, that can be used to rebuild the mProfileToConnectableDevicesMap on a reboot.
87 private HashMap<Integer, List<String>> mProfileToConnectableDevicesInfo;
Pavel Maltsevc9e86e82017-03-22 11:37:21 -070088 private BluetoothAutoConnectStateMachine mBluetoothAutoConnectStateMachine;
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080089 private final BluetoothAdapter mBluetoothAdapter;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080090 private BroadcastReceiver mReceiver;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080091
92 // Events that are listened to for triggering an auto-connect:
93 // Cabin events like Door unlock coming from the Cabin Service.
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -080094 private final CarCabinService mCarCabinService;
95 private final CarPropertyListener mCabinEventListener;
96 // Sensor events like Ignition switch ON from the Car Sensor Service
97 private final CarSensorService mCarSensorService;
98 private final CarSensorEventListener mCarSensorEventListener;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080099
100 // Profile Proxies.
101 private BluetoothHeadsetClient mBluetoothHeadsetClient;
102 private BluetoothA2dpSink mBluetoothA2dpSink;
103 private BluetoothPbapClient mBluetoothPbapClient;
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800104 private BluetoothMapClient mBluetoothMapClient;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800105
106 // The Bluetooth profiles that the CarService will try to autoconnect on.
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700107 private final List<Integer> mProfilesToConnect;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800108 private static final int MAX_CONNECT_RETRIES = 1;
109
110 // File to write connectable devices for a profile information
111 private static final String DEVICE_INFO_FILE = "BluetoothDevicesInfo.map";
112 private static final int PROFILE_NOT_AVAILABLE = -1;
113
114 // Device & Profile currently being connected on
115 private ConnectionParams mConnectionInFlight;
116
117 public static BluetoothDeviceConnectionPolicy create(Context context,
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800118 CarCabinService carCabinService, CarSensorService carSensorService) {
119 return new BluetoothDeviceConnectionPolicy(context, carCabinService, carSensorService);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800120 }
121
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800122 private BluetoothDeviceConnectionPolicy(Context context, CarCabinService carCabinService,
123 CarSensorService carSensorService) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800124 mContext = context;
125 mCarCabinService = carCabinService;
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800126 mCarSensorService = carSensorService;
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700127 mProfilesToConnect = Arrays.asList(
128 BluetoothProfile.HEADSET_CLIENT,
129 BluetoothProfile.PBAP_CLIENT,
130 BluetoothProfile.A2DP_SINK,
131 BluetoothProfile.MAP_CLIENT);
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800132 mCabinEventListener = new CarPropertyListener();
133 mCarSensorEventListener = new CarSensorEventListener();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800134 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800135 if (mBluetoothAdapter == null) {
136 Log.w(TAG, "No Bluetooth Adapter Available");
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800137 }
138 }
139
140 /**
141 * ConnectionParams - parameters/objects relevant to the bluetooth connection calls.
142 * This encapsulates the information that is passed around across different methods in the
143 * policy. Contains the bluetooth device {@link BluetoothDevice} and the list of profiles that
144 * we want that device to connect on.
145 * Used as the currency that methods use to talk to each other in the policy.
146 */
147 public static class ConnectionParams {
148 private BluetoothDevice mBluetoothDevice;
149 private Integer mBluetoothProfile;
150
151 public ConnectionParams() {
152 // default constructor
153 }
154
155 public ConnectionParams(Integer profile) {
156 mBluetoothProfile = profile;
157 }
158
159 public ConnectionParams(BluetoothDevice device, Integer profile) {
160 mBluetoothProfile = profile;
161 mBluetoothDevice = device;
162 }
163
164 // getters & Setters
165 public void setBluetoothDevice(BluetoothDevice device) {
166 mBluetoothDevice = device;
167 }
168
169 public void setBluetoothProfile(Integer profile) {
170 mBluetoothProfile = profile;
171 }
172
173 public BluetoothDevice getBluetoothDevice() {
174 return mBluetoothDevice;
175 }
176
177 public Integer getBluetoothProfile() {
178 return mBluetoothProfile;
179 }
180 }
181
182 /**
183 * BluetoothBroadcastReceiver receives the bluetooth related intents that are relevant to
184 * connection
185 * and bonding state changes. Reports the information to the {@link
186 * BluetoothDeviceConnectionPolicy}
187 * for it update its status.
188 */
189 public class BluetoothBroadcastReceiver extends BroadcastReceiver {
190 @Override
191 public void onReceive(Context context, Intent intent) {
192 String action = intent.getAction();
193 if (DBG) {
194 Log.d(TAG, "Received Intent " + action);
195 }
196 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
197 ConnectionParams connectParams;
198 if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
199 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
200 BluetoothDevice.ERROR);
201 updateBondState(device, bondState);
202
203 } else if (BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
204 connectParams = new ConnectionParams(device, BluetoothProfile.A2DP_SINK);
205 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
206 BluetoothProfile.STATE_DISCONNECTED);
207 notifyConnectionStatus(connectParams, currState);
208
209 } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
210 connectParams = new ConnectionParams(device, BluetoothProfile.HEADSET_CLIENT);
211 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
212 BluetoothProfile.STATE_DISCONNECTED);
213 notifyConnectionStatus(connectParams, currState);
214
215 } else if (BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
216 connectParams = new ConnectionParams(device, BluetoothProfile.PBAP_CLIENT);
217 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
218 BluetoothProfile.STATE_DISCONNECTED);
219 notifyConnectionStatus(connectParams, currState);
220
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800221 } else if (BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
222 connectParams = new ConnectionParams(device, BluetoothProfile.MAP_CLIENT);
223 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
224 BluetoothProfile.STATE_DISCONNECTED);
225 notifyConnectionStatus(connectParams, currState);
226
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800227 } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
228 int currState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
229 -1);
230 if (DBG) {
231 Log.d(TAG, "Bluetooth Adapter State: " + currState);
232 }
233 if (currState == BluetoothAdapter.STATE_ON) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800234 // Initialize and populate (from file if available) the
235 // mProfileToConnectableDevicesMap
236 rebuildDeviceMapFromDeviceInfoLocked();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800237 initiateConnection();
238 } else if (currState == BluetoothAdapter.STATE_OFF) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800239 // Write currently connected device snapshot to file.
240 writeDeviceInfoToFile();
241 resetBluetoothDevicesConnectionInfo();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800242 }
243 }
244 }
245 }
246
247 /**
248 * Setup the Bluetooth profile service connections and Vehicle Event listeners.
249 * and start the state machine -{@link BluetoothAutoConnectStateMachine}
250 */
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700251 public synchronized void init() {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800252 if (DBG) {
253 Log.d(TAG, "init()");
254 }
255 initDeviceMap();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800256 // Register for various intents from the Bluetooth service.
257 mReceiver = new BluetoothBroadcastReceiver();
258 // Create a new ConnectionParams object to keep track of device & profile that are being
259 // connected to.
260 mConnectionInFlight = new ConnectionParams();
261 setupIntentFilter();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800262 // Make connections to Profile Services.
263 setupProfileProxy();
264 mBluetoothAutoConnectStateMachine = BluetoothAutoConnectStateMachine.make(this);
265 // Listen to various events coming from the vehicle.
266 setupEventListeners();
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700267
268 mInitialized = true;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800269 }
270
271 /**
272 * Setting up the intent filter to the connection and bonding state changes we are interested
273 * in.
274 * This includes knowing when the
275 * 1. Bluetooth Adapter turned on/off
276 * 2. Bonding State of a device changes
277 * 3. A specific profile's connection state changes.
278 */
279 private void setupIntentFilter() {
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700280 IntentFilter profileFilter = new IntentFilter();
281 profileFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
282 profileFilter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
283 profileFilter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
284 profileFilter.addAction(BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED);
285 profileFilter.addAction(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED);
286 profileFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
287 mContext.registerReceiver(mReceiver, profileFilter);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800288 if (DBG) {
289 Log.d(TAG, "Intent Receiver Registered");
290 }
291 }
292
293 /**
294 * Initialize the {@link #mProfileToConnectableDevicesMap}.
295 * {@link #mProfileToConnectableDevicesMap} stores the profile:DeviceList information. This
296 * method retrieves it from persistent memory.
297 */
298 private synchronized void initDeviceMap() {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800299 boolean result = readDeviceInfoFromFile();
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700300 if (!result && mProfileToConnectableDevicesMap == null) {
301 mProfileToConnectableDevicesMap = new HashMap<>();
302 for (Integer profile : mProfilesToConnect) {
303 mProfileToConnectableDevicesMap.put(profile, new BluetoothDevicesInfo(profile));
304 }
305 if (DBG) {
306 Log.d(TAG, "new Device Map created");
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800307 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800308 }
309 }
310
311 /**
312 * Setup connections to the profile proxy object to talk to the Bluetooth profile services
313 */
314 private void setupProfileProxy() {
315 if (DBG) {
316 Log.d(TAG, "setupProfileProxy()");
317 }
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800318 if (mBluetoothAdapter == null) {
319 return;
320 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800321 for (Integer profile : mProfilesToConnect) {
322 mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, profile);
323 }
324 }
325
326 /**
327 * Setting up Listeners to the various events we are interested in listening to for initiating
328 * Bluetooth connection attempts.
329 */
330 private void setupEventListeners() {
331 // Setting up a listener for events from CarCabinService
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800332 // For now, we listen to door unlock signal coming from {@link CarCabinService},
333 // and Ignition state START from {@link CarSensorService}
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800334 mCarCabinService.registerListener(mCabinEventListener);
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800335 mCarSensorService.registerOrUpdateSensorListener(
336 CarSensorManager.SENSOR_TYPE_IGNITION_STATE, 0, mCarSensorEventListener);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800337 }
338
339 /**
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800340 * Handles events coming in from the {@link CarCabinService}
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800341 * The events that can trigger Bluetooth Scanning from CarCabinService is Door Unlock.
342 * Upon receiving the event that is of interest, initiate a connection attempt by calling
343 * the policy {@link BluetoothDeviceConnectionPolicy}
344 */
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800345 private class CarPropertyListener extends ICarPropertyEventListener.Stub {
346 @Override
347 public void onEvent(CarPropertyEvent event) throws RemoteException {
348 if (DBG) {
349 Log.d(TAG, "Cabin change Event : " + event.getEventType());
350 }
351 Boolean locked;
352 CarPropertyValue value = event.getCarPropertyValue();
353 Object o = value.getValue();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800354
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800355 if (value.getPropertyId() == CarCabinManager.ID_DOOR_LOCK) {
356 if (o instanceof Boolean) {
357 locked = (Boolean) o;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800358 if (DBG) {
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800359 Log.d(TAG, "Door Lock: " + locked);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800360 }
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800361 // Attempting a connection only on a door unlock
362 if (!locked) {
363 initiateConnection();
364 }
365 }
366 }
367 }
368 }
369
370 /**
371 * Handles events coming in from the {@link CarSensorService}
372 * The events that can trigger Bluetooth Scanning from CarSensorService is Ignition START.
373 * Upon receiving the event that is of interest, initiate a connection attempt by calling
374 * the policy {@link BluetoothDeviceConnectionPolicy}
375 */
376
377 private class CarSensorEventListener extends ICarSensorEventListener.Stub {
378 @Override
379 public void onSensorChanged(List<CarSensorEvent> events) throws RemoteException {
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700380 if (events != null && !events.isEmpty()) {
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800381 CarSensorEvent event = events.get(0);
382 if (DBG) {
383 Log.d(TAG, "Sensor event Type : " + event.sensorType);
384 }
385 if (event.sensorType == CarSensorManager.SENSOR_TYPE_IGNITION_STATE) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800386 if (DBG) {
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800387 Log.d(TAG, "Sensor value : " + event.intValues[0]);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800388 }
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -0800389 if (event.intValues[0] == CarSensorEvent.IGNITION_STATE_START) {
390 initiateConnection();
391 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800392 }
393 }
394 }
395 }
396
397 /**
398 * Clean up slate. Close the Bluetooth profile service connections and quit the state machine -
399 * {@link BluetoothAutoConnectStateMachine}
400 */
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700401 public synchronized void release() {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800402 if (DBG) {
403 Log.d(TAG, "release()");
404 }
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700405 mInitialized = false;
406
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800407 writeDeviceInfoToFile();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800408 closeEventListeners();
409 // Closing the connections to the Profile Services
410 closeProfileProxy();
411 mConnectionInFlight = null;
412 // quit the state machine
413 mBluetoothAutoConnectStateMachine.doQuit();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800414 }
415
416 /**
417 * Unregister the listeners to the various Vehicle events coming from other parts of the
418 * CarService
419 */
420 private void closeEventListeners() {
421 // b/34723490 - Need to add more events other than the Cabin Event.
422 if (mCabinEventListener != null) {
423 mCarCabinService.unregisterListener(mCabinEventListener);
424 }
425 }
426
427 /**
428 * Close connections to the profile proxy object
429 */
430 private void closeProfileProxy() {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800431 if (mBluetoothAdapter == null) {
432 return;
433 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800434 if (DBG) {
435 Log.d(TAG, "closeProfileProxy()");
436 }
437 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP_SINK, mBluetoothA2dpSink);
438 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT,
439 mBluetoothHeadsetClient);
440 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PBAP_CLIENT, mBluetoothPbapClient);
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800441 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.MAP_CLIENT, mBluetoothMapClient);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800442 }
443
444 /**
445 * Resets the {@link BluetoothDevicesInfo#mConnectionInfo} of all the profiles to start from
446 * a clean slate. The ConnectionInfo has all the book keeping information regarding the state
447 * of connection attempts - like which device in the device list for the profile is the next
448 * to try connecting etc.
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800449 * This method does not clear the {@link BluetoothDevicesInfo#mDeviceList} like the {@link
450 * #resetProfileToConnectableDevicesMap()} method does.
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800451 */
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800452 private synchronized void resetBluetoothDevicesConnectionInfo() {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800453 if (DBG) {
454 Log.d(TAG, "Resetting ConnectionInfo for all profiles");
455 }
456 for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) {
457 devInfo.resetConnectionInfoLocked();
458 }
459 }
460
461 /**
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800462 * Resets the {@link #mProfileToConnectableDevicesMap} to a clean and empty slate.
463 */
464 public synchronized void resetProfileToConnectableDevicesMap() {
465 if (DBG) {
466 Log.d(TAG, "Resetting the mProfilesToConnectableDevicesMap");
467 }
468 for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) {
469 devInfo.resetDeviceListLocked();
470 }
471 }
472
473 /**
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800474 * Returns the list of profiles that the Autoconnection policy attempts to connect on
475 *
476 * @return profile list.
477 */
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700478 public synchronized List<Integer> getProfilesToConnect() {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800479 return mProfilesToConnect;
480 }
481
482 /**
483 * Add a new Profile to the list of To Be Connected profiles.
484 *
485 * @param profile - ProfileInfo of the new profile to be added.
486 */
487 public synchronized void addProfile(Integer profile) {
488 mProfilesToConnect.add(profile);
489 }
490
491 /**
492 * Add or remove a device based on the bonding state change.
493 *
494 * @param device - device to add/remove
495 * @param bondState - current bonding state
496 */
497 private void updateBondState(BluetoothDevice device, int bondState) {
498 if (device == null) {
499 Log.e(TAG, "updateBondState: device Null");
500 return;
501 }
502 if (DBG) {
503 Log.d(TAG, "BondState :" + bondState + " Device: " + device.getName());
504 }
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800505 // Bonded devices are added to a profile's device list after the device CONNECTS on the
506 // profile. When unpaired, we remove the device from all of the profiles' device list.
507 if (bondState == BluetoothDevice.BOND_NONE) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800508 for (Integer profile : mProfilesToConnect) {
509 removeDeviceFromProfile(device, profile);
510 }
511 }
512
513 }
514
515 /**
516 * Add a new device to the list of devices connect-able on the given profile
517 *
518 * @param device - Bluetooth device to be added
519 * @param profile - profile to add the device to.
520 */
521 private synchronized void addDeviceToProfile(BluetoothDevice device, Integer profile) {
522 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
523 if (devInfo == null) {
524 if (DBG) {
525 Log.d(TAG, "Creating devInfo for profile: " + profile);
526 }
527 devInfo = new BluetoothDevicesInfo(profile);
528 mProfileToConnectableDevicesMap.put(profile, devInfo);
529 }
530 devInfo.addDeviceLocked(device);
531 }
532
533 /**
534 * Remove the device from the list of devices connect-able on the gievn profile.
535 *
536 * @param device - Bluetooth device to be removed
537 * @param profile - profile to remove the device from
538 */
539 private synchronized void removeDeviceFromProfile(BluetoothDevice device, Integer profile) {
540 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
541 if (devInfo != null) {
542 devInfo.removeDeviceLocked(device);
543 }
544 }
545
546 /**
547 * Initiate a bluetooth connection.
548 */
549 private void initiateConnection() {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800550 // Make sure the bluetooth adapter is available & enabled.
551 if (mBluetoothAdapter == null) {
552 Log.w(TAG, "Bluetooth Adapter null");
553 return;
554 }
555
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800556 if (mBluetoothAdapter.isEnabled()) {
557 if (isDeviceMapEmpty()) {
558 if (DBG) {
559 Log.d(TAG, "Device Map is empty. Querying bonded devices");
560 }
561 if (populateDeviceMapFromBondedDevices() == false) {
562 if (DBG) {
563 Log.d(TAG, "No bonded devices");
564 }
565 return;
566 }
567 }
568 resetDeviceAvailableToConnect();
569 if (DBG) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800570 Log.d(TAG, "initiateConnection() Reset Device Availability");
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800571 }
572 mBluetoothAutoConnectStateMachine.sendMessage(BluetoothAutoConnectStateMachine.CONNECT);
573 } else {
574 if (DBG) {
575 Log.d(TAG, "Bluetooth Adapter not enabled.");
576 }
577 }
578 }
579
580 /**
581 * If, for some reason, the {@link #mProfileToConnectableDevicesMap} is empty, query the
582 * Bluetooth stack
583 * for the list of bonded devices and use it to populate the {@link
584 * #mProfileToConnectableDevicesMap}.
585 *
586 * @return true if devices were added
587 * false if the bonded device list is also empty.
588 */
589 private synchronized boolean populateDeviceMapFromBondedDevices() {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800590 if (mBluetoothAdapter == null) {
591 return false;
592 }
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700593 Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
594 if (bondedDevices == null || bondedDevices.isEmpty()) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800595 if (DBG) {
596 Log.d(TAG, "populateDeviceMapFromBondedDevices() - No bonded devices");
597 }
598 return false;
599 }
600
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700601 for (BluetoothDevice bd : bondedDevices) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800602 for (Integer profile : mProfilesToConnect) {
603 if (bd != null) {
604 if (DBG) {
605 Log.d(TAG, "Adding device: " + bd.getName() + " profile: " + profile);
606 }
607 mProfileToConnectableDevicesMap.get(profile).addDeviceLocked(bd);
608 }
609 }
610 }
611 return true;
612 }
613
614 /**
615 * Find an unconnected profile and find a device to connect on it.
616 * Finds the appropriate device for the profile from the information available in
617 * {@link #mProfileToConnectableDevicesMap}
618 *
619 * @return true - if we found a device to connect on for any of the {@link #mProfilesToConnect}
620 * false - if we cannot find a device to connect to or if we are not ready to connect yet.
621 */
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800622 public synchronized boolean findDeviceToConnect() {
Pavel Maltsevc9e86e82017-03-22 11:37:21 -0700623 if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()
624 || mProfileToConnectableDevicesMap == null || !mInitialized) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800625 return false;
626 }
627 boolean deviceFound = false;
628 // Get the first unconnected profile that we can try to make a connection
629 Integer nextProfile = getNextProfileToConnectLocked();
630 // Keep going through the profiles until we find a device that we can connect to
631 while (nextProfile != PROFILE_NOT_AVAILABLE) {
632 if (DBG) {
633 Log.d(TAG, "connectToProfile(): " + nextProfile);
634 }
635 // find a device that is next in line for a connection attempt for that profile
636 deviceFound = tryNextDeviceInQueueLocked(nextProfile);
637 // If we found a device to connect, break out of the loop
638 if (deviceFound) {
639 if (DBG) {
640 Log.d(TAG, "Found device to connect to");
641 }
642 BluetoothDeviceConnectionPolicy.ConnectionParams btParams =
643 new BluetoothDeviceConnectionPolicy.ConnectionParams(
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800644 mConnectionInFlight.getBluetoothDevice(),
645 mConnectionInFlight.getBluetoothProfile());
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800646 // set up a time out
647 mBluetoothAutoConnectStateMachine.sendMessageDelayed(
648 BluetoothAutoConnectStateMachine.CONNECT_TIMEOUT, btParams,
649 BluetoothAutoConnectStateMachine.CONNECTION_TIMEOUT_MS);
650 break;
651 } else {
652 // result will be false, if there are no more devices to connect
653 // or if the ProfileProxy objects are null (ServiceConnection
654 // not yet established for this profile)
655 if (DBG) {
656 Log.d(TAG, "No more device to connect on Profile: " + nextProfile);
657 }
658 nextProfile = getNextProfileToConnectLocked();
659 }
660 }
661 return deviceFound;
662 }
663
664 /**
665 * Get the first unconnected profile.
666 *
667 * @return profile to connect.
668 * Special return value 0 if
669 * 1. all profiles have been connected on.
670 * 2. no profile connected but no nearby known device that can be connected to
671 */
672 private Integer getNextProfileToConnectLocked() {
673 for (Integer profile : mProfilesToConnect) {
674 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
675 if (devInfo != null) {
676 if (devInfo.isConnectedLocked() == false
677 && devInfo.isDeviceAvailableToConnectLocked() == true) {
678 return profile;
679 }
680 } else {
681 Log.e(TAG, "Unexpected: devInfo null for profile: " + profile);
682 }
683 }
684 // Reaching here denotes all profiles are connected or No devices available for any profile
685 return PROFILE_NOT_AVAILABLE;
686 }
687
688 /**
689 * Try to connect to the next device in the device list for the given profile.
690 *
691 * @param profile - profile to connect on
692 * @return - true if we found a device to connect on for this profile
693 * false - if we cannot find a device to connect to.
694 */
695
696 private boolean tryNextDeviceInQueueLocked(Integer profile) {
697 // Get the Device Information for the given profile and find the next device to connect on
698 boolean deviceAvailable = true;
699 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
700 if (devInfo == null) {
701 Log.e(TAG, "Unexpected: No device Queue for this profile: " + profile);
702 return false;
703 }
704 BluetoothDevice devToConnect = devInfo.getNextDeviceInQueueLocked();
705 if (devToConnect != null) {
706 switch (profile) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800707 case BluetoothProfile.A2DP_SINK:
708 if (mBluetoothA2dpSink != null) {
709 if (DBG) {
710 Log.d(TAG,
711 "Connecting device " + devToConnect.getName() + " on A2DPSink");
712 }
713 mBluetoothA2dpSink.connect(devToConnect);
714 } else {
715 if (DBG) {
716 Log.d(TAG, "Unexpected: BluetoothA2dpSink Profile proxy null");
717 }
718 deviceAvailable = false;
719 }
720 break;
721
722 case BluetoothProfile.HEADSET_CLIENT:
723 if (mBluetoothHeadsetClient != null) {
724 if (DBG) {
725 Log.d(TAG, "Connecting device " + devToConnect.getName()
726 + " on HeadSetClient");
727 }
728 mBluetoothHeadsetClient.connect(devToConnect);
729 } else {
730 if (DBG) {
731 Log.d(TAG, "Unexpected: BluetoothHeadsetClient Profile proxy null");
732 }
733 deviceAvailable = false;
734 }
735 break;
736
737 case BluetoothProfile.PBAP_CLIENT:
738 if (mBluetoothPbapClient != null) {
739 if (DBG) {
740 Log.d(TAG, "Connecting device " + devToConnect.getName()
741 + " on PbapClient");
742 }
743 mBluetoothPbapClient.connect(devToConnect);
744 } else {
745 if (DBG) {
746 Log.d(TAG, "Unexpected: BluetoothPbapClient Profile proxy null");
747 }
748 deviceAvailable = false;
749 }
750 break;
751
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800752 case BluetoothProfile.MAP_CLIENT:
753 if (mBluetoothMapClient != null) {
754 if (DBG) {
755 Log.d(TAG, "Connecting device " + devToConnect.getName()
756 + " on MAPClient");
757 }
758 mBluetoothMapClient.connect(devToConnect);
759 } else {
760 if (DBG) {
761 Log.d(TAG, "Unexpected: BluetoothMAPClient Profile proxy null");
762 }
763 deviceAvailable = false;
764 }
765 break;
766
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800767 default:
768 if (DBG) {
769 Log.d(TAG, "Unsupported Bluetooth profile being tried for connection: "
770 + profile);
771 }
772 break;
773 }
774 // Increment the retry count & cache what is being connected to
775 if (deviceAvailable) {
776 // This method is already called from a synchronized context.
777 mConnectionInFlight.setBluetoothDevice(devToConnect);
778 mConnectionInFlight.setBluetoothProfile(profile);
779 devInfo.incrementRetryCountLocked();
780 if (DBG) {
781 Log.d(TAG, "Increment Retry to: " + devInfo.getRetryCountLocked());
782 }
783 } else {
784 if (DBG) {
785 Log.d(TAG, "Not incrementing retry.");
786 }
787 }
788 } else {
789 if (DBG) {
790 Log.d(TAG, "No paired nearby device to connect to for profile: " + profile);
791 }
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800792 // reset the mConnectionInFlight
793 mConnectionInFlight.setBluetoothProfile(0);
794 mConnectionInFlight.setBluetoothDevice(null);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800795 devInfo.setDeviceAvailableToConnectLocked(false);
796 deviceAvailable = false;
797 }
798 return deviceAvailable;
799 }
800
801 /**
802 * Update the device connection status for a profile and also notify the state machine.
803 * This gets called from {@link BluetoothBroadcastReceiver} when it receives a Profile's
804 * CONNECTION_STATE_CHANGED intent.
805 *
806 * @param params - {@link ConnectionParams} device and profile list info
807 * @param currentState - connection result to update
808 */
809 private void notifyConnectionStatus(ConnectionParams params, int currentState) {
810 // Update the profile's BluetoothDevicesInfo.
811 boolean isConnected;
812 switch (currentState) {
813 case BluetoothProfile.STATE_DISCONNECTED: {
814 isConnected = false;
815 break;
816 }
817
818 case BluetoothProfile.STATE_CONNECTED: {
819 isConnected = true;
820 break;
821 }
822
823 default: {
824 if (DBG) {
825 Log.d(TAG, "notifyConnectionStatus() Ignoring state: " + currentState);
826 }
827 return;
828 }
829
830 }
831
832 boolean updateSuccessful = updateDeviceConnectionStatus(params, isConnected);
833 if (updateSuccessful) {
834 if (isConnected) {
835 mBluetoothAutoConnectStateMachine.sendMessage(
836 BluetoothAutoConnectStateMachine.DEVICE_CONNECTED,
837 params);
838 } else {
839 mBluetoothAutoConnectStateMachine.sendMessage(
840 BluetoothAutoConnectStateMachine.DEVICE_DISCONNECTED,
841 params);
842 }
843 }
844 }
845
846 /**
847 * Update the profile's {@link BluetoothDevicesInfo} with the result of the connection
848 * attempt. This gets called from the {@link BluetoothAutoConnectStateMachine} when the
849 * connection attempt times out or from {@link BluetoothBroadcastReceiver} when it receives
850 * a Profile's CONNECTION_STATE_CHANGED intent.
851 *
852 * @param params - {@link ConnectionParams} device and profile list info
853 * @param didConnect - connection result to update
854 */
855 public synchronized boolean updateDeviceConnectionStatus(ConnectionParams params,
856 boolean didConnect) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800857 if (params == null || params.getBluetoothDevice() == null) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800858 Log.e(TAG, "updateDeviceConnectionStatus: null params");
859 return false;
860 }
861 // Get the profile to update
862 Integer profileToUpdate = params.getBluetoothProfile();
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800863 BluetoothDevice deviceThatConnected = params.getBluetoothDevice();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800864 if (DBG) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800865 Log.d(TAG, "Profile: " + profileToUpdate + " Connected: " + didConnect + " on "
866 + deviceThatConnected.getName());
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800867 }
868
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800869 // If the connection update is on a different profile or device (a very rare possibility),
870 // it is handled automatically. Just logging it here.
871 if (DBG) {
872 if (mConnectionInFlight != null && mConnectionInFlight.getBluetoothProfile() != null) {
873 if (profileToUpdate.equals(mConnectionInFlight.getBluetoothProfile()) == false) {
874 Log.d(TAG, "Updating profile " + profileToUpdate
875 + " different from connection in flight "
876 + mConnectionInFlight.getBluetoothProfile());
877 }
878 }
879
880 if (mConnectionInFlight != null && mConnectionInFlight.getBluetoothDevice() != null) {
881 if (deviceThatConnected.equals(mConnectionInFlight.getBluetoothDevice()) == false) {
882 Log.d(TAG, "Connected device " + deviceThatConnected.getName()
883 + " different from connection in flight");
884
885 }
886 }
887 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800888 BluetoothDevicesInfo devInfo = null;
889 devInfo = mProfileToConnectableDevicesMap.get(profileToUpdate);
890 if (devInfo == null) {
891 Log.e(TAG, "Unexpected: devInfo null for profile: " + profileToUpdate);
892 return false;
893 }
894
895 boolean retry = canRetryConnection(profileToUpdate);
896 // Update the status and also if a retry attempt can be made if the
897 // connection timed out in the previous attempt.
898 if (DBG) {
899 Log.d(TAG, "Retry? : " + retry);
900 }
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800901 devInfo.updateConnectionStatusLocked(deviceThatConnected, didConnect, retry);
902 // Write to persistent memory to have the latest snapshot available
903 writeDeviceInfoToFile();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800904 return true;
905 }
906
907 /**
908 * Returns if we can retry connection attempt on the given profile for the device that is
909 * currently in the head of the queue.
910 *
911 * @param profile - Profile to check
912 */
913 private synchronized boolean canRetryConnection(Integer profile) {
914 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
915 if (devInfo == null) {
916 Log.e(TAG, "Unexpected: No device Queue for this profile: " + profile);
917 return false;
918 }
919 if (devInfo.getRetryCountLocked() < MAX_CONNECT_RETRIES) {
920 return true;
921 } else {
922 return false;
923 }
924 }
925
926 /**
927 * Helper method to see if there are any connect-able devices on any of the
928 * profiles.
929 *
930 * @return true - if {@link #mProfileToConnectableDevicesMap} does not have any devices for any
931 * profiles.
932 * false - if {@link #mProfileToConnectableDevicesMap} has a device for at least one profile.
933 */
934 private synchronized boolean isDeviceMapEmpty() {
935 boolean empty = true;
936 for (Integer profile : mProfilesToConnect) {
937 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
938 if (devInfo != null) {
939 if (devInfo.getNumberOfPairedDevicesLocked() != 0) {
940 if (DBG) {
941 Log.d(TAG, "Device map not empty. Profile: " + profile + " has "
942 + devInfo.getNumberOfPairedDevicesLocked() + " paired devices");
943 }
944 empty = false;
945 break;
946 }
947 }
948 }
949 return empty;
950 }
951
952 /**
953 * Reset the Device Available to Connect information for all profiles to Available.
954 * If in a previous connection attempt, we failed to connect on all devices for a profile,
955 * we would update deviceAvailableToConnect for that profile to false. That information
956 * is used to deduce if we should move to the next profile. If marked false, we will not
957 * try to connect on that profile anymore as part of that connection attempt.
958 * However, when we get another connection trigger from the vehicle, we need to reset the
959 * deviceAvailableToConnect information so we can start the connection attempts all over
960 * again.
961 */
962 private synchronized void resetDeviceAvailableToConnect() {
963 for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) {
964 devInfo.setDeviceAvailableToConnectLocked(true);
965 }
966 }
967
968 /**
969 * Utility function - Prints the Profile: list of devices information to log
970 * Caller should wrap a DBG around this, since this is for debugging purpose.
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800971 *
972 * @param writer - PrintWriter
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800973 */
974 private synchronized void printDeviceMap(PrintWriter writer) {
975 if (mProfileToConnectableDevicesMap == null) {
976 return;
977 }
978 writer.println("Bluetooth Profile -> Connectable Device List ");
979 for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) {
980 writer.println("Profile: " + devInfo.getProfileLocked() + "\t");
981 writer.print("Connected: " + devInfo.isConnectedLocked() +
982 "\t Device Available: " + devInfo.isDeviceAvailableToConnectLocked());
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800983 writer.println();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800984 writer.println("Device List:");
985 List<BluetoothDevice> deviceList = devInfo.getDeviceList();
986 if (deviceList != null) {
987 for (BluetoothDevice device : deviceList) {
988 writer.print(device.getName() + "\t");
989 }
990 }
991 }
992 }
993
994 /**
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800995 * Utility function - could be called from a adb shell dump command to dump the
996 * {@link #mProfileToConnectableDevicesInfo}
997 *
998 * @param writer - PrintWriter
999 */
1000 public synchronized void printDeviceInfo(PrintWriter writer) {
1001 if (mProfileToConnectableDevicesInfo == null) {
1002 Log.d(TAG, "mProfileToConnectableDevicesInfo null");
1003 return;
1004 }
1005 writer.println("Bluetooth Profile -> device Info");
1006 for (Map.Entry<Integer, List<String>> entry : mProfileToConnectableDevicesInfo.entrySet()) {
1007 writer.println("Profile: " + entry.getKey());
1008 for (String devname : entry.getValue()) {
1009 writer.print(devname + "\t");
1010 }
1011 writer.println();
1012 }
1013 }
1014
1015 /**
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001016 * Write information about which devices connected on which profile to persistent memory.
1017 * Essentially the list of devices that a profile can connect on the next auto-connect
1018 * attempt.
1019 *
1020 * @return true if the write was successful, false otherwise
1021 */
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001022 public synchronized boolean writeDeviceInfoToFile() {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001023 boolean writeSuccess = true;
1024 if (mProfileToConnectableDevicesMap == null) {
1025 writeSuccess = false;
1026 } else {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001027 extractDeviceInfoFromDeviceMapLocked();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001028 try {
1029 AtomicFile oFile = new AtomicFile(
1030 new File(mContext.getFilesDir(), DEVICE_INFO_FILE));
1031 FileOutputStream outFile = oFile.startWrite();
1032 ObjectOutputStream ostream = new ObjectOutputStream(outFile);
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001033 ostream.writeObject(mProfileToConnectableDevicesInfo);
1034 ostream.flush();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001035 ostream.close();
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001036 oFile.finishWrite(outFile);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001037 outFile.close();
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001038 if (DBG) {
1039 Log.d(TAG, "Writing successful");
1040 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001041 } catch (IOException e) {
1042 Log.e(TAG, e.getMessage());
1043 writeSuccess = false;
1044 }
1045 }
1046 return writeSuccess;
1047 }
1048
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001049
1050 /**
1051 * Extracts {@link #mProfileToConnectableDevicesInfo} from
1052 * {@link #mProfileToConnectableDevicesMap}
1053 * {@link #mProfileToConnectableDevicesInfo} is a map of Profiles to List of names of
1054 * Connectable
1055 * devices that gets written to a file.
1056 */
1057 private void extractDeviceInfoFromDeviceMapLocked() {
1058 mProfileToConnectableDevicesInfo = new HashMap<Integer, List<String>>();
1059
1060 for (Map.Entry<Integer, BluetoothDevicesInfo> entry : mProfileToConnectableDevicesMap
1061 .entrySet()) {
1062 // for every entry, extract the Profile and the list of device names
1063 Integer profile = entry.getKey();
1064 if (DBG) {
1065 Log.d(TAG, "Extracting for profile " + profile);
1066 }
1067 List<String> deviceNames = new ArrayList<>();
1068 BluetoothDevicesInfo devicesInfo = entry.getValue();
1069 // Iterate through the List<BluetoothDevice> and build List<DeviceNames>
1070 if (devicesInfo != null && devicesInfo.getDeviceList() != null) {
1071 for (BluetoothDevice device : devicesInfo.getDeviceList()) {
1072 deviceNames.add(device.getName());
1073 if (DBG) {
1074 Log.d(TAG, "Device: " + device.getName());
1075 }
1076 }
1077 }
1078 mProfileToConnectableDevicesInfo.put(profile, deviceNames);
1079 }
1080 }
1081
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001082 /**
1083 * Read the device information from file and populate the
1084 * {@link #mProfileToConnectableDevicesMap}
1085 *
1086 * @return - true if the read was successful, false if not.
1087 */
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001088 public synchronized boolean readDeviceInfoFromFile() {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001089 boolean readSuccess = true;
1090 try {
1091 AtomicFile iFile = new AtomicFile(new File(mContext.getFilesDir(), DEVICE_INFO_FILE));
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001092 if (DBG) {
1093 Log.d(TAG, "Reading from file");
1094 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001095 FileInputStream inFile = iFile.openRead();
1096 ObjectInputStream istream = new ObjectInputStream(inFile);
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001097 mProfileToConnectableDevicesInfo =
1098 (HashMap<Integer, List<String>>) istream.readObject();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001099 istream.close();
1100 inFile.close();
1101 } catch (IOException | ClassNotFoundException e) {
1102 Log.e(TAG, e.getMessage());
1103 if (DBG) {
1104 Log.d(TAG, "No previously connected device information available");
1105 }
1106 readSuccess = false;
1107 }
Pavel Maltsevc9e86e82017-03-22 11:37:21 -07001108
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001109 if (readSuccess) {
1110 readSuccess = rebuildDeviceMapFromDeviceInfoLocked();
1111 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001112 return readSuccess;
1113 }
1114
1115 /**
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001116 * Rebuild the {@link #mProfileToConnectableDevicesMap} from the {@link
1117 * #mProfileToConnectableDevicesInfo}
1118 *
1119 * @return true if the reconstruction was successful, false if not.
1120 */
1121 private boolean rebuildDeviceMapFromDeviceInfoLocked() {
1122 if (DBG) {
1123 Log.d(TAG, "Rebuilding device map");
1124 }
1125
1126 if (mProfileToConnectableDevicesInfo == null) {
1127 Log.w(TAG, "No Device Info to rebuild the Device Map");
1128 return false;
1129 }
1130
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -08001131 if (mBluetoothAdapter != null) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001132 if (DBG) {
1133 Log.d(TAG, "Bonded devices size:" + mBluetoothAdapter.getBondedDevices().size());
1134 }
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -08001135 if (mBluetoothAdapter.getBondedDevices().isEmpty()) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001136 if (DBG) {
1137 Log.d(TAG, "No Bonded Devices available. Quit rebuilding");
1138 }
1139 return false;
1140 }
1141 }
1142
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001143 // Iterate through the Map's entries and build the {@link #mProfileToConnectableDevicesMap}
Ram Periathiruvadi49d5a5a2017-02-17 18:50:09 -08001144 if (mProfileToConnectableDevicesMap == null) {
Pavel Maltsevc9e86e82017-03-22 11:37:21 -07001145 mProfileToConnectableDevicesMap = new HashMap<>();
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001146 }
1147
1148 for (Map.Entry<Integer, List<String>> entry : mProfileToConnectableDevicesInfo.entrySet()) {
1149 Integer profile = entry.getKey();
1150 List<String> deviceList = entry.getValue();
1151 // Build the BluetoothDevicesInfo for this profile.
1152 BluetoothDevicesInfo devicesInfo = new BluetoothDevicesInfo(profile);
1153 // Do we have a bonded device with this name? If so, get it and populate the device
1154 // map.
1155 for (String name : deviceList) {
1156 BluetoothDevice deviceToAdd = getBondedDeviceWithGivenName(name);
1157 if (deviceToAdd != null) {
1158 devicesInfo.addDeviceLocked(deviceToAdd);
1159 } else {
1160 if (DBG) {
1161 Log.d(TAG, "No device with name " + name + " found in bonded devices");
1162 }
1163 }
1164 }
1165 mProfileToConnectableDevicesMap.put(profile, devicesInfo);
1166 }
1167
Pavel Maltsevc9e86e82017-03-22 11:37:21 -07001168 return true;
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001169 }
1170
1171 /**
1172 * Given the device name, find the corresponding {@link BluetoothDevice} from the list of
1173 * Bonded devices.
1174 *
1175 * @param name Bluetooth Device name
1176 */
1177 private BluetoothDevice getBondedDeviceWithGivenName(String name) {
1178 if (mBluetoothAdapter == null) {
1179 if (DBG) {
1180 Log.d(TAG, "Bluetooth Adapter Null");
1181 }
1182 return null;
1183 }
1184 if (name == null) {
1185 Log.w(TAG, "getBondedDeviceWithGivenName() Passing in a null name");
1186 return null;
1187 }
1188 if (DBG) {
1189 Log.d(TAG, "Looking for bonded device: " + name);
1190 }
1191 BluetoothDevice btDevice = null;
1192 Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
1193 for (BluetoothDevice bd : bondedDevices) {
1194 if (name.equals(bd.getName())) {
1195 btDevice = bd;
1196 break;
1197 }
1198 }
1199 return btDevice;
1200 }
1201
1202 /**
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001203 * All the BluetoothProfile.ServiceListeners to get the Profile Proxy objects
1204 */
1205 private BluetoothProfile.ServiceListener mProfileListener =
1206 new BluetoothProfile.ServiceListener() {
1207 public void onServiceConnected(int profile, BluetoothProfile proxy) {
1208 if (DBG) {
1209 Log.d(TAG, "OnServiceConnected profile: " + profile);
1210 }
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001211 switch (profile) {
1212 case BluetoothProfile.A2DP_SINK:
1213 mBluetoothA2dpSink = (BluetoothA2dpSink) proxy;
1214 break;
1215
1216 case BluetoothProfile.HEADSET_CLIENT:
1217 mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
1218 break;
1219
1220 case BluetoothProfile.PBAP_CLIENT:
1221 mBluetoothPbapClient = (BluetoothPbapClient) proxy;
1222 break;
1223
1224 case BluetoothProfile.MAP_CLIENT:
1225 mBluetoothMapClient = (BluetoothMapClient) proxy;
1226 break;
1227
1228 default:
1229 if (DBG) {
1230 Log.d(TAG, "Unhandled profile");
1231 }
1232 break;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001233 }
1234
1235 }
1236
1237 public void onServiceDisconnected(int profile) {
1238 if (DBG) {
1239 Log.d(TAG, "onServiceDisconnected profile: " + profile);
1240 }
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001241 switch (profile) {
1242 case BluetoothProfile.A2DP_SINK:
1243 mBluetoothA2dpSink = null;
1244 break;
1245
1246 case BluetoothProfile.HEADSET_CLIENT:
1247 mBluetoothHeadsetClient = null;
1248 break;
1249
1250 case BluetoothProfile.PBAP_CLIENT:
1251 mBluetoothPbapClient = null;
1252 break;
1253
1254 case BluetoothProfile.MAP_CLIENT:
1255 mBluetoothMapClient = null;
1256 break;
1257
1258 default:
1259 if (DBG) {
1260 Log.d(TAG, "Unhandled profile");
1261 }
1262 break;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001263 }
1264 }
1265 };
1266
1267 public void dump(PrintWriter writer) {
1268 writer.println("*BluetoothDeviceConnectionPolicy*");
1269 printDeviceMap(writer);
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001270 mBluetoothAutoConnectStateMachine.dump(writer);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001271 }
1272}