blob: 520b8b4914957c56d7d842f6025e6a4e793799b7 [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;
27import android.car.hardware.cabin.CarCabinManager;
28import android.car.hardware.property.CarPropertyEvent;
29import android.car.hardware.property.ICarPropertyEventListener;
30import android.content.BroadcastReceiver;
31import android.content.Context;
32import android.content.Intent;
33import android.content.IntentFilter;
34import android.os.RemoteException;
35import android.util.AtomicFile;
36import android.util.Log;
37
38import java.io.File;
39import java.io.FileInputStream;
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080040import java.io.FileNotFoundException;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080041import java.io.FileOutputStream;
42import java.io.IOException;
43import java.io.ObjectInputStream;
44import java.io.ObjectOutputStream;
45import java.io.PrintWriter;
46import java.util.ArrayList;
47import java.util.Arrays;
48import java.util.HashMap;
49import java.util.List;
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080050import java.util.Map;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080051import java.util.Set;
52
53/**
54 * A Bluetooth Device Connection policy that is specific to the use cases of a Car. A car's
55 * bluetooth capabilities in terms of the profiles it supports and its use cases are unique. Hence
56 * the CarService manages the policy that drives when and what to connect to.
57 *
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080058 * When to connect:
59 * The policy can be configured to listen to various vehicle events that are appropriate to trigger
60 * a connection attempt. Signals like door unlock/open, ignition state changes indicate user entry
61 * and there by attempt to connect to their devices. This removes the need for the user to manually
62 * connect his device everytime they get in a car.
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080063 *
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080064 * Which device to connect:
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080065 * The policy also keeps track of the {Profile : DevicesThatCanConnectOnTheProfile} and when it is
66 * time to connect, picks the device that is appropriate and available.
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080067 * For every profile, the policy attempts to connect to the last connected device first. The policy
68 * maintains a list of connect-able devices for every profile, in the order of how recently they
69 * connected. The device that successfully connects on a profile is moved to the top of the list
70 * of devices for that profile, so the next time a connection attempt is made, the policy starts
71 * with the last connected device first.
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080072 *
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080073 */
74
75public class BluetoothDeviceConnectionPolicy {
76 private static final String TAG = "BTDevConnectionPolicy";
77 private static final boolean DBG = false;
78 private Context mContext;
79
80 // The main datastructure that holds on to the {profile:list of known and connectible devices}
81 private HashMap<Integer, BluetoothDevicesInfo> mProfileToConnectableDevicesMap;
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080082 // mProfileToConnectableDevicesInfo - holds information to serialize and write
83 // to file, that can be used to rebuild the mProfileToConnectableDevicesMap on a reboot.
84 private HashMap<Integer, List<String>> mProfileToConnectableDevicesInfo;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080085 BluetoothAutoConnectStateMachine mBluetoothAutoConnectStateMachine;
Ram Periathiruvadiee28c002017-02-07 21:35:01 -080086 private final BluetoothAdapter mBluetoothAdapter;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -080087 private Set<BluetoothDevice> mBondedDevices;
88 private BroadcastReceiver mReceiver;
89 private IntentFilter mProfileFilter;
90
91 // Events that are listened to for triggering an auto-connect:
92 // Cabin events like Door unlock coming from the Cabin Service.
93 private CarCabinService mCarCabinService;
94 private ICarPropertyEventListener mCabinEventListener;
95
96 // Profile Proxies.
97 private BluetoothHeadsetClient mBluetoothHeadsetClient;
98 private BluetoothA2dpSink mBluetoothA2dpSink;
99 private BluetoothPbapClient mBluetoothPbapClient;
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800100 private BluetoothMapClient mBluetoothMapClient;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800101
102 // The Bluetooth profiles that the CarService will try to autoconnect on.
103 private List<Integer> mProfilesToConnect;
104 private static final int MAX_CONNECT_RETRIES = 1;
105
106 // File to write connectable devices for a profile information
107 private static final String DEVICE_INFO_FILE = "BluetoothDevicesInfo.map";
108 private static final int PROFILE_NOT_AVAILABLE = -1;
109
110 // Device & Profile currently being connected on
111 private ConnectionParams mConnectionInFlight;
112
113 public static BluetoothDeviceConnectionPolicy create(Context context,
114 CarCabinService carCabinService) {
115 return new BluetoothDeviceConnectionPolicy(context, carCabinService);
116 }
117
118 private BluetoothDeviceConnectionPolicy(Context context, CarCabinService carCabinService) {
119 mContext = context;
120 mCarCabinService = carCabinService;
121 mProfilesToConnect = Arrays.asList(new Integer[]
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800122 {BluetoothProfile.HEADSET_CLIENT,
123 BluetoothProfile.PBAP_CLIENT, BluetoothProfile.A2DP_SINK,
124 BluetoothProfile.MAP_CLIENT,});
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800125 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800126 if (mBluetoothAdapter == null) {
127 Log.w(TAG, "No Bluetooth Adapter Available");
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800128 }
129 }
130
131 /**
132 * ConnectionParams - parameters/objects relevant to the bluetooth connection calls.
133 * This encapsulates the information that is passed around across different methods in the
134 * policy. Contains the bluetooth device {@link BluetoothDevice} and the list of profiles that
135 * we want that device to connect on.
136 * Used as the currency that methods use to talk to each other in the policy.
137 */
138 public static class ConnectionParams {
139 private BluetoothDevice mBluetoothDevice;
140 private Integer mBluetoothProfile;
141
142 public ConnectionParams() {
143 // default constructor
144 }
145
146 public ConnectionParams(Integer profile) {
147 mBluetoothProfile = profile;
148 }
149
150 public ConnectionParams(BluetoothDevice device, Integer profile) {
151 mBluetoothProfile = profile;
152 mBluetoothDevice = device;
153 }
154
155 // getters & Setters
156 public void setBluetoothDevice(BluetoothDevice device) {
157 mBluetoothDevice = device;
158 }
159
160 public void setBluetoothProfile(Integer profile) {
161 mBluetoothProfile = profile;
162 }
163
164 public BluetoothDevice getBluetoothDevice() {
165 return mBluetoothDevice;
166 }
167
168 public Integer getBluetoothProfile() {
169 return mBluetoothProfile;
170 }
171 }
172
173 /**
174 * BluetoothBroadcastReceiver receives the bluetooth related intents that are relevant to
175 * connection
176 * and bonding state changes. Reports the information to the {@link
177 * BluetoothDeviceConnectionPolicy}
178 * for it update its status.
179 */
180 public class BluetoothBroadcastReceiver extends BroadcastReceiver {
181 @Override
182 public void onReceive(Context context, Intent intent) {
183 String action = intent.getAction();
184 if (DBG) {
185 Log.d(TAG, "Received Intent " + action);
186 }
187 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
188 ConnectionParams connectParams;
189 if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
190 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
191 BluetoothDevice.ERROR);
192 updateBondState(device, bondState);
193
194 } else if (BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
195 connectParams = new ConnectionParams(device, BluetoothProfile.A2DP_SINK);
196 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
197 BluetoothProfile.STATE_DISCONNECTED);
198 notifyConnectionStatus(connectParams, currState);
199
200 } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
201 connectParams = new ConnectionParams(device, BluetoothProfile.HEADSET_CLIENT);
202 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
203 BluetoothProfile.STATE_DISCONNECTED);
204 notifyConnectionStatus(connectParams, currState);
205
206 } else if (BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
207 connectParams = new ConnectionParams(device, BluetoothProfile.PBAP_CLIENT);
208 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
209 BluetoothProfile.STATE_DISCONNECTED);
210 notifyConnectionStatus(connectParams, currState);
211
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800212 } else if (BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
213 connectParams = new ConnectionParams(device, BluetoothProfile.MAP_CLIENT);
214 int currState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
215 BluetoothProfile.STATE_DISCONNECTED);
216 notifyConnectionStatus(connectParams, currState);
217
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800218 } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
219 int currState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
220 -1);
221 if (DBG) {
222 Log.d(TAG, "Bluetooth Adapter State: " + currState);
223 }
224 if (currState == BluetoothAdapter.STATE_ON) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800225 // Initialize and populate (from file if available) the
226 // mProfileToConnectableDevicesMap
227 rebuildDeviceMapFromDeviceInfoLocked();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800228 initiateConnection();
229 } else if (currState == BluetoothAdapter.STATE_OFF) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800230 // Write currently connected device snapshot to file.
231 writeDeviceInfoToFile();
232 resetBluetoothDevicesConnectionInfo();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800233 }
234 }
235 }
236 }
237
238 /**
239 * Setup the Bluetooth profile service connections and Vehicle Event listeners.
240 * and start the state machine -{@link BluetoothAutoConnectStateMachine}
241 */
242 public void init() {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800243 if (DBG) {
244 Log.d(TAG, "init()");
245 }
246 initDeviceMap();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800247 // Register for various intents from the Bluetooth service.
248 mReceiver = new BluetoothBroadcastReceiver();
249 // Create a new ConnectionParams object to keep track of device & profile that are being
250 // connected to.
251 mConnectionInFlight = new ConnectionParams();
252 setupIntentFilter();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800253 // Make connections to Profile Services.
254 setupProfileProxy();
255 mBluetoothAutoConnectStateMachine = BluetoothAutoConnectStateMachine.make(this);
256 // Listen to various events coming from the vehicle.
257 setupEventListeners();
258 }
259
260 /**
261 * Setting up the intent filter to the connection and bonding state changes we are interested
262 * in.
263 * This includes knowing when the
264 * 1. Bluetooth Adapter turned on/off
265 * 2. Bonding State of a device changes
266 * 3. A specific profile's connection state changes.
267 */
268 private void setupIntentFilter() {
269 mProfileFilter = new IntentFilter();
270 mProfileFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
271 mProfileFilter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
272 mProfileFilter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
273 mProfileFilter.addAction(BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED);
274 mProfileFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
275 mContext.registerReceiver(mReceiver, mProfileFilter);
276 if (DBG) {
277 Log.d(TAG, "Intent Receiver Registered");
278 }
279 }
280
281 /**
282 * Initialize the {@link #mProfileToConnectableDevicesMap}.
283 * {@link #mProfileToConnectableDevicesMap} stores the profile:DeviceList information. This
284 * method retrieves it from persistent memory.
285 */
286 private synchronized void initDeviceMap() {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800287 boolean result = readDeviceInfoFromFile();
288 if (result == false || mProfileToConnectableDevicesMap == null) {
289 if (mProfileToConnectableDevicesMap == null) {
290 mProfileToConnectableDevicesMap = new HashMap<Integer, BluetoothDevicesInfo>();
291 for (Integer profile : mProfilesToConnect) {
292 mProfileToConnectableDevicesMap.put(profile, new BluetoothDevicesInfo(profile));
293 }
294 if (DBG) {
295 Log.d(TAG, "new Device Map created");
296 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800297 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800298 }
299 }
300
301 /**
302 * Setup connections to the profile proxy object to talk to the Bluetooth profile services
303 */
304 private void setupProfileProxy() {
305 if (DBG) {
306 Log.d(TAG, "setupProfileProxy()");
307 }
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800308 if (mBluetoothAdapter == null) {
309 return;
310 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800311 for (Integer profile : mProfilesToConnect) {
312 mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, profile);
313 }
314 }
315
316 /**
317 * Setting up Listeners to the various events we are interested in listening to for initiating
318 * Bluetooth connection attempts.
319 */
320 private void setupEventListeners() {
321 // Setting up a listener for events from CarCabinService
322 // For now, we only listen to door unlock signal coming from {@link CarCabinService},
323 // but more events will be added here soon - b/34723490
324 mCabinEventListener = new ICarPropertyEventListener.Stub() {
325 @Override
326 public void onEvent(CarPropertyEvent event) throws RemoteException {
327 handleCabinChangeEvent(event);
328 }
329 };
330 mCarCabinService.registerListener(mCabinEventListener);
331 }
332
333 /**
334 * handleCabinChangeEvent handles events coming in from the {@link CarCabinService}
335 * The events that can trigger Bluetooth Scanning from CarCabinService is Door Unlock.
336 * Upon receiving the event that is of interest, initiate a connection attempt by calling
337 * the policy {@link BluetoothDeviceConnectionPolicy}
338 */
339 private void handleCabinChangeEvent(CarPropertyEvent event) {
340 if (DBG) {
341 Log.d(TAG, "Cabin change Event : " + event.getEventType());
342 }
343 Boolean locked;
344 CarPropertyValue value = event.getCarPropertyValue();
345 Object o = value.getValue();
346
347 if (value.getPropertyId() == CarCabinManager.ID_DOOR_LOCK) {
348 if (o instanceof Boolean) {
349 locked = (Boolean) o;
350 // Attempting a connection only on a door unlock
351 if (locked) {
352 if (DBG) {
353 Log.d(TAG, "Door locked");
354 }
355 } else {
356 if (DBG) {
357 Log.d(TAG, "Door Unlocked");
358 }
359 initiateConnection();
360 }
361 }
362 }
363 }
364
365 /**
366 * Clean up slate. Close the Bluetooth profile service connections and quit the state machine -
367 * {@link BluetoothAutoConnectStateMachine}
368 */
369 public void release() {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800370 if (DBG) {
371 Log.d(TAG, "release()");
372 }
373 writeDeviceInfoToFile();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800374 closeEventListeners();
375 // Closing the connections to the Profile Services
376 closeProfileProxy();
377 mConnectionInFlight = null;
378 // quit the state machine
379 mBluetoothAutoConnectStateMachine.doQuit();
380
381 }
382
383 /**
384 * Unregister the listeners to the various Vehicle events coming from other parts of the
385 * CarService
386 */
387 private void closeEventListeners() {
388 // b/34723490 - Need to add more events other than the Cabin Event.
389 if (mCabinEventListener != null) {
390 mCarCabinService.unregisterListener(mCabinEventListener);
391 }
392 }
393
394 /**
395 * Close connections to the profile proxy object
396 */
397 private void closeProfileProxy() {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800398 if (mBluetoothAdapter == null) {
399 return;
400 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800401 if (DBG) {
402 Log.d(TAG, "closeProfileProxy()");
403 }
404 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP_SINK, mBluetoothA2dpSink);
405 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT,
406 mBluetoothHeadsetClient);
407 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PBAP_CLIENT, mBluetoothPbapClient);
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800408 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.MAP_CLIENT, mBluetoothMapClient);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800409 }
410
411 /**
412 * Resets the {@link BluetoothDevicesInfo#mConnectionInfo} of all the profiles to start from
413 * a clean slate. The ConnectionInfo has all the book keeping information regarding the state
414 * of connection attempts - like which device in the device list for the profile is the next
415 * to try connecting etc.
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800416 * This method does not clear the {@link BluetoothDevicesInfo#mDeviceList} like the {@link
417 * #resetProfileToConnectableDevicesMap()} method does.
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800418 */
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800419 private synchronized void resetBluetoothDevicesConnectionInfo() {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800420 if (DBG) {
421 Log.d(TAG, "Resetting ConnectionInfo for all profiles");
422 }
423 for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) {
424 devInfo.resetConnectionInfoLocked();
425 }
426 }
427
428 /**
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800429 * Resets the {@link #mProfileToConnectableDevicesMap} to a clean and empty slate.
430 */
431 public synchronized void resetProfileToConnectableDevicesMap() {
432 if (DBG) {
433 Log.d(TAG, "Resetting the mProfilesToConnectableDevicesMap");
434 }
435 for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) {
436 devInfo.resetDeviceListLocked();
437 }
438 }
439
440 /**
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800441 * Returns the list of profiles that the Autoconnection policy attempts to connect on
442 *
443 * @return profile list.
444 */
445 public List<Integer> getProfilesToConnect() {
446 return mProfilesToConnect;
447 }
448
449 /**
450 * Add a new Profile to the list of To Be Connected profiles.
451 *
452 * @param profile - ProfileInfo of the new profile to be added.
453 */
454 public synchronized void addProfile(Integer profile) {
455 mProfilesToConnect.add(profile);
456 }
457
458 /**
459 * Add or remove a device based on the bonding state change.
460 *
461 * @param device - device to add/remove
462 * @param bondState - current bonding state
463 */
464 private void updateBondState(BluetoothDevice device, int bondState) {
465 if (device == null) {
466 Log.e(TAG, "updateBondState: device Null");
467 return;
468 }
469 if (DBG) {
470 Log.d(TAG, "BondState :" + bondState + " Device: " + device.getName());
471 }
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800472 // Bonded devices are added to a profile's device list after the device CONNECTS on the
473 // profile. When unpaired, we remove the device from all of the profiles' device list.
474 if (bondState == BluetoothDevice.BOND_NONE) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800475 for (Integer profile : mProfilesToConnect) {
476 removeDeviceFromProfile(device, profile);
477 }
478 }
479
480 }
481
482 /**
483 * Add a new device to the list of devices connect-able on the given profile
484 *
485 * @param device - Bluetooth device to be added
486 * @param profile - profile to add the device to.
487 */
488 private synchronized void addDeviceToProfile(BluetoothDevice device, Integer profile) {
489 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
490 if (devInfo == null) {
491 if (DBG) {
492 Log.d(TAG, "Creating devInfo for profile: " + profile);
493 }
494 devInfo = new BluetoothDevicesInfo(profile);
495 mProfileToConnectableDevicesMap.put(profile, devInfo);
496 }
497 devInfo.addDeviceLocked(device);
498 }
499
500 /**
501 * Remove the device from the list of devices connect-able on the gievn profile.
502 *
503 * @param device - Bluetooth device to be removed
504 * @param profile - profile to remove the device from
505 */
506 private synchronized void removeDeviceFromProfile(BluetoothDevice device, Integer profile) {
507 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
508 if (devInfo != null) {
509 devInfo.removeDeviceLocked(device);
510 }
511 }
512
513 /**
514 * Initiate a bluetooth connection.
515 */
516 private void initiateConnection() {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800517 // Make sure the bluetooth adapter is available & enabled.
518 if (mBluetoothAdapter == null) {
519 Log.w(TAG, "Bluetooth Adapter null");
520 return;
521 }
522
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800523 if (mBluetoothAdapter.isEnabled()) {
524 if (isDeviceMapEmpty()) {
525 if (DBG) {
526 Log.d(TAG, "Device Map is empty. Querying bonded devices");
527 }
528 if (populateDeviceMapFromBondedDevices() == false) {
529 if (DBG) {
530 Log.d(TAG, "No bonded devices");
531 }
532 return;
533 }
534 }
535 resetDeviceAvailableToConnect();
536 if (DBG) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800537 Log.d(TAG, "initiateConnection() Reset Device Availability");
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800538 }
539 mBluetoothAutoConnectStateMachine.sendMessage(BluetoothAutoConnectStateMachine.CONNECT);
540 } else {
541 if (DBG) {
542 Log.d(TAG, "Bluetooth Adapter not enabled.");
543 }
544 }
545 }
546
547 /**
548 * If, for some reason, the {@link #mProfileToConnectableDevicesMap} is empty, query the
549 * Bluetooth stack
550 * for the list of bonded devices and use it to populate the {@link
551 * #mProfileToConnectableDevicesMap}.
552 *
553 * @return true if devices were added
554 * false if the bonded device list is also empty.
555 */
556 private synchronized boolean populateDeviceMapFromBondedDevices() {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800557 if (mBluetoothAdapter == null) {
558 return false;
559 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800560 mBondedDevices = mBluetoothAdapter.getBondedDevices();
561 if (mBondedDevices.size() == 0) {
562 if (DBG) {
563 Log.d(TAG, "populateDeviceMapFromBondedDevices() - No bonded devices");
564 }
565 return false;
566 }
567
568 for (BluetoothDevice bd : mBondedDevices) {
569 for (Integer profile : mProfilesToConnect) {
570 if (bd != null) {
571 if (DBG) {
572 Log.d(TAG, "Adding device: " + bd.getName() + " profile: " + profile);
573 }
574 mProfileToConnectableDevicesMap.get(profile).addDeviceLocked(bd);
575 }
576 }
577 }
578 return true;
579 }
580
581 /**
582 * Find an unconnected profile and find a device to connect on it.
583 * Finds the appropriate device for the profile from the information available in
584 * {@link #mProfileToConnectableDevicesMap}
585 *
586 * @return true - if we found a device to connect on for any of the {@link #mProfilesToConnect}
587 * false - if we cannot find a device to connect to or if we are not ready to connect yet.
588 */
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800589 public synchronized boolean findDeviceToConnect() {
590 if (mBluetoothAdapter == null || mBluetoothAdapter.isEnabled() == false
591 || mProfileToConnectableDevicesMap == null) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800592 return false;
593 }
594 boolean deviceFound = false;
595 // Get the first unconnected profile that we can try to make a connection
596 Integer nextProfile = getNextProfileToConnectLocked();
597 // Keep going through the profiles until we find a device that we can connect to
598 while (nextProfile != PROFILE_NOT_AVAILABLE) {
599 if (DBG) {
600 Log.d(TAG, "connectToProfile(): " + nextProfile);
601 }
602 // find a device that is next in line for a connection attempt for that profile
603 deviceFound = tryNextDeviceInQueueLocked(nextProfile);
604 // If we found a device to connect, break out of the loop
605 if (deviceFound) {
606 if (DBG) {
607 Log.d(TAG, "Found device to connect to");
608 }
609 BluetoothDeviceConnectionPolicy.ConnectionParams btParams =
610 new BluetoothDeviceConnectionPolicy.ConnectionParams(
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800611 mConnectionInFlight.getBluetoothDevice(),
612 mConnectionInFlight.getBluetoothProfile());
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800613 // set up a time out
614 mBluetoothAutoConnectStateMachine.sendMessageDelayed(
615 BluetoothAutoConnectStateMachine.CONNECT_TIMEOUT, btParams,
616 BluetoothAutoConnectStateMachine.CONNECTION_TIMEOUT_MS);
617 break;
618 } else {
619 // result will be false, if there are no more devices to connect
620 // or if the ProfileProxy objects are null (ServiceConnection
621 // not yet established for this profile)
622 if (DBG) {
623 Log.d(TAG, "No more device to connect on Profile: " + nextProfile);
624 }
625 nextProfile = getNextProfileToConnectLocked();
626 }
627 }
628 return deviceFound;
629 }
630
631 /**
632 * Get the first unconnected profile.
633 *
634 * @return profile to connect.
635 * Special return value 0 if
636 * 1. all profiles have been connected on.
637 * 2. no profile connected but no nearby known device that can be connected to
638 */
639 private Integer getNextProfileToConnectLocked() {
640 for (Integer profile : mProfilesToConnect) {
641 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
642 if (devInfo != null) {
643 if (devInfo.isConnectedLocked() == false
644 && devInfo.isDeviceAvailableToConnectLocked() == true) {
645 return profile;
646 }
647 } else {
648 Log.e(TAG, "Unexpected: devInfo null for profile: " + profile);
649 }
650 }
651 // Reaching here denotes all profiles are connected or No devices available for any profile
652 return PROFILE_NOT_AVAILABLE;
653 }
654
655 /**
656 * Try to connect to the next device in the device list for the given profile.
657 *
658 * @param profile - profile to connect on
659 * @return - true if we found a device to connect on for this profile
660 * false - if we cannot find a device to connect to.
661 */
662
663 private boolean tryNextDeviceInQueueLocked(Integer profile) {
664 // Get the Device Information for the given profile and find the next device to connect on
665 boolean deviceAvailable = true;
666 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
667 if (devInfo == null) {
668 Log.e(TAG, "Unexpected: No device Queue for this profile: " + profile);
669 return false;
670 }
671 BluetoothDevice devToConnect = devInfo.getNextDeviceInQueueLocked();
672 if (devToConnect != null) {
673 switch (profile) {
674 // b/34723437 - Add MAP_CLIENT
675 case BluetoothProfile.A2DP_SINK:
676 if (mBluetoothA2dpSink != null) {
677 if (DBG) {
678 Log.d(TAG,
679 "Connecting device " + devToConnect.getName() + " on A2DPSink");
680 }
681 mBluetoothA2dpSink.connect(devToConnect);
682 } else {
683 if (DBG) {
684 Log.d(TAG, "Unexpected: BluetoothA2dpSink Profile proxy null");
685 }
686 deviceAvailable = false;
687 }
688 break;
689
690 case BluetoothProfile.HEADSET_CLIENT:
691 if (mBluetoothHeadsetClient != null) {
692 if (DBG) {
693 Log.d(TAG, "Connecting device " + devToConnect.getName()
694 + " on HeadSetClient");
695 }
696 mBluetoothHeadsetClient.connect(devToConnect);
697 } else {
698 if (DBG) {
699 Log.d(TAG, "Unexpected: BluetoothHeadsetClient Profile proxy null");
700 }
701 deviceAvailable = false;
702 }
703 break;
704
705 case BluetoothProfile.PBAP_CLIENT:
706 if (mBluetoothPbapClient != null) {
707 if (DBG) {
708 Log.d(TAG, "Connecting device " + devToConnect.getName()
709 + " on PbapClient");
710 }
711 mBluetoothPbapClient.connect(devToConnect);
712 } else {
713 if (DBG) {
714 Log.d(TAG, "Unexpected: BluetoothPbapClient Profile proxy null");
715 }
716 deviceAvailable = false;
717 }
718 break;
719
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800720 case BluetoothProfile.MAP_CLIENT:
721 if (mBluetoothMapClient != null) {
722 if (DBG) {
723 Log.d(TAG, "Connecting device " + devToConnect.getName()
724 + " on MAPClient");
725 }
726 mBluetoothMapClient.connect(devToConnect);
727 } else {
728 if (DBG) {
729 Log.d(TAG, "Unexpected: BluetoothMAPClient Profile proxy null");
730 }
731 deviceAvailable = false;
732 }
733 break;
734
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800735 default:
736 if (DBG) {
737 Log.d(TAG, "Unsupported Bluetooth profile being tried for connection: "
738 + profile);
739 }
740 break;
741 }
742 // Increment the retry count & cache what is being connected to
743 if (deviceAvailable) {
744 // This method is already called from a synchronized context.
745 mConnectionInFlight.setBluetoothDevice(devToConnect);
746 mConnectionInFlight.setBluetoothProfile(profile);
747 devInfo.incrementRetryCountLocked();
748 if (DBG) {
749 Log.d(TAG, "Increment Retry to: " + devInfo.getRetryCountLocked());
750 }
751 } else {
752 if (DBG) {
753 Log.d(TAG, "Not incrementing retry.");
754 }
755 }
756 } else {
757 if (DBG) {
758 Log.d(TAG, "No paired nearby device to connect to for profile: " + profile);
759 }
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800760 // reset the mConnectionInFlight
761 mConnectionInFlight.setBluetoothProfile(0);
762 mConnectionInFlight.setBluetoothDevice(null);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800763 devInfo.setDeviceAvailableToConnectLocked(false);
764 deviceAvailable = false;
765 }
766 return deviceAvailable;
767 }
768
769 /**
770 * Update the device connection status for a profile and also notify the state machine.
771 * This gets called from {@link BluetoothBroadcastReceiver} when it receives a Profile's
772 * CONNECTION_STATE_CHANGED intent.
773 *
774 * @param params - {@link ConnectionParams} device and profile list info
775 * @param currentState - connection result to update
776 */
777 private void notifyConnectionStatus(ConnectionParams params, int currentState) {
778 // Update the profile's BluetoothDevicesInfo.
779 boolean isConnected;
780 switch (currentState) {
781 case BluetoothProfile.STATE_DISCONNECTED: {
782 isConnected = false;
783 break;
784 }
785
786 case BluetoothProfile.STATE_CONNECTED: {
787 isConnected = true;
788 break;
789 }
790
791 default: {
792 if (DBG) {
793 Log.d(TAG, "notifyConnectionStatus() Ignoring state: " + currentState);
794 }
795 return;
796 }
797
798 }
799
800 boolean updateSuccessful = updateDeviceConnectionStatus(params, isConnected);
801 if (updateSuccessful) {
802 if (isConnected) {
803 mBluetoothAutoConnectStateMachine.sendMessage(
804 BluetoothAutoConnectStateMachine.DEVICE_CONNECTED,
805 params);
806 } else {
807 mBluetoothAutoConnectStateMachine.sendMessage(
808 BluetoothAutoConnectStateMachine.DEVICE_DISCONNECTED,
809 params);
810 }
811 }
812 }
813
814 /**
815 * Update the profile's {@link BluetoothDevicesInfo} with the result of the connection
816 * attempt. This gets called from the {@link BluetoothAutoConnectStateMachine} when the
817 * connection attempt times out or from {@link BluetoothBroadcastReceiver} when it receives
818 * a Profile's CONNECTION_STATE_CHANGED intent.
819 *
820 * @param params - {@link ConnectionParams} device and profile list info
821 * @param didConnect - connection result to update
822 */
823 public synchronized boolean updateDeviceConnectionStatus(ConnectionParams params,
824 boolean didConnect) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800825 if (params == null || params.getBluetoothDevice() == null) {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800826 Log.e(TAG, "updateDeviceConnectionStatus: null params");
827 return false;
828 }
829 // Get the profile to update
830 Integer profileToUpdate = params.getBluetoothProfile();
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800831 BluetoothDevice deviceThatConnected = params.getBluetoothDevice();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800832 if (DBG) {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800833 Log.d(TAG, "Profile: " + profileToUpdate + " Connected: " + didConnect + " on "
834 + deviceThatConnected.getName());
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800835 }
836
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800837 // If the connection update is on a different profile or device (a very rare possibility),
838 // it is handled automatically. Just logging it here.
839 if (DBG) {
840 if (mConnectionInFlight != null && mConnectionInFlight.getBluetoothProfile() != null) {
841 if (profileToUpdate.equals(mConnectionInFlight.getBluetoothProfile()) == false) {
842 Log.d(TAG, "Updating profile " + profileToUpdate
843 + " different from connection in flight "
844 + mConnectionInFlight.getBluetoothProfile());
845 }
846 }
847
848 if (mConnectionInFlight != null && mConnectionInFlight.getBluetoothDevice() != null) {
849 if (deviceThatConnected.equals(mConnectionInFlight.getBluetoothDevice()) == false) {
850 Log.d(TAG, "Connected device " + deviceThatConnected.getName()
851 + " different from connection in flight");
852
853 }
854 }
855 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800856 BluetoothDevicesInfo devInfo = null;
857 devInfo = mProfileToConnectableDevicesMap.get(profileToUpdate);
858 if (devInfo == null) {
859 Log.e(TAG, "Unexpected: devInfo null for profile: " + profileToUpdate);
860 return false;
861 }
862
863 boolean retry = canRetryConnection(profileToUpdate);
864 // Update the status and also if a retry attempt can be made if the
865 // connection timed out in the previous attempt.
866 if (DBG) {
867 Log.d(TAG, "Retry? : " + retry);
868 }
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800869 devInfo.updateConnectionStatusLocked(deviceThatConnected, didConnect, retry);
870 // Write to persistent memory to have the latest snapshot available
871 writeDeviceInfoToFile();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800872 return true;
873 }
874
875 /**
876 * Returns if we can retry connection attempt on the given profile for the device that is
877 * currently in the head of the queue.
878 *
879 * @param profile - Profile to check
880 */
881 private synchronized boolean canRetryConnection(Integer profile) {
882 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
883 if (devInfo == null) {
884 Log.e(TAG, "Unexpected: No device Queue for this profile: " + profile);
885 return false;
886 }
887 if (devInfo.getRetryCountLocked() < MAX_CONNECT_RETRIES) {
888 return true;
889 } else {
890 return false;
891 }
892 }
893
894 /**
895 * Helper method to see if there are any connect-able devices on any of the
896 * profiles.
897 *
898 * @return true - if {@link #mProfileToConnectableDevicesMap} does not have any devices for any
899 * profiles.
900 * false - if {@link #mProfileToConnectableDevicesMap} has a device for at least one profile.
901 */
902 private synchronized boolean isDeviceMapEmpty() {
903 boolean empty = true;
904 for (Integer profile : mProfilesToConnect) {
905 BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
906 if (devInfo != null) {
907 if (devInfo.getNumberOfPairedDevicesLocked() != 0) {
908 if (DBG) {
909 Log.d(TAG, "Device map not empty. Profile: " + profile + " has "
910 + devInfo.getNumberOfPairedDevicesLocked() + " paired devices");
911 }
912 empty = false;
913 break;
914 }
915 }
916 }
917 return empty;
918 }
919
920 /**
921 * Reset the Device Available to Connect information for all profiles to Available.
922 * If in a previous connection attempt, we failed to connect on all devices for a profile,
923 * we would update deviceAvailableToConnect for that profile to false. That information
924 * is used to deduce if we should move to the next profile. If marked false, we will not
925 * try to connect on that profile anymore as part of that connection attempt.
926 * However, when we get another connection trigger from the vehicle, we need to reset the
927 * deviceAvailableToConnect information so we can start the connection attempts all over
928 * again.
929 */
930 private synchronized void resetDeviceAvailableToConnect() {
931 for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) {
932 devInfo.setDeviceAvailableToConnectLocked(true);
933 }
934 }
935
936 /**
937 * Utility function - Prints the Profile: list of devices information to log
938 * Caller should wrap a DBG around this, since this is for debugging purpose.
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800939 *
940 * @param writer - PrintWriter
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800941 */
942 private synchronized void printDeviceMap(PrintWriter writer) {
943 if (mProfileToConnectableDevicesMap == null) {
944 return;
945 }
946 writer.println("Bluetooth Profile -> Connectable Device List ");
947 for (BluetoothDevicesInfo devInfo : mProfileToConnectableDevicesMap.values()) {
948 writer.println("Profile: " + devInfo.getProfileLocked() + "\t");
949 writer.print("Connected: " + devInfo.isConnectedLocked() +
950 "\t Device Available: " + devInfo.isDeviceAvailableToConnectLocked());
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800951 writer.println();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800952 writer.println("Device List:");
953 List<BluetoothDevice> deviceList = devInfo.getDeviceList();
954 if (deviceList != null) {
955 for (BluetoothDevice device : deviceList) {
956 writer.print(device.getName() + "\t");
957 }
958 }
959 }
960 }
961
962 /**
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800963 * Utility function - could be called from a adb shell dump command to dump the
964 * {@link #mProfileToConnectableDevicesInfo}
965 *
966 * @param writer - PrintWriter
967 */
968 public synchronized void printDeviceInfo(PrintWriter writer) {
969 if (mProfileToConnectableDevicesInfo == null) {
970 Log.d(TAG, "mProfileToConnectableDevicesInfo null");
971 return;
972 }
973 writer.println("Bluetooth Profile -> device Info");
974 for (Map.Entry<Integer, List<String>> entry : mProfileToConnectableDevicesInfo.entrySet()) {
975 writer.println("Profile: " + entry.getKey());
976 for (String devname : entry.getValue()) {
977 writer.print(devname + "\t");
978 }
979 writer.println();
980 }
981 }
982
983 /**
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800984 * Write information about which devices connected on which profile to persistent memory.
985 * Essentially the list of devices that a profile can connect on the next auto-connect
986 * attempt.
987 *
988 * @return true if the write was successful, false otherwise
989 */
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800990 public synchronized boolean writeDeviceInfoToFile() {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800991 boolean writeSuccess = true;
992 if (mProfileToConnectableDevicesMap == null) {
993 writeSuccess = false;
994 } else {
Ram Periathiruvadiee28c002017-02-07 21:35:01 -0800995 extractDeviceInfoFromDeviceMapLocked();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -0800996 try {
997 AtomicFile oFile = new AtomicFile(
998 new File(mContext.getFilesDir(), DEVICE_INFO_FILE));
999 FileOutputStream outFile = oFile.startWrite();
1000 ObjectOutputStream ostream = new ObjectOutputStream(outFile);
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001001 ostream.writeObject(mProfileToConnectableDevicesInfo);
1002 ostream.flush();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001003 ostream.close();
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001004 oFile.finishWrite(outFile);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001005 outFile.close();
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001006 if (DBG) {
1007 Log.d(TAG, "Writing successful");
1008 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001009 } catch (IOException e) {
1010 Log.e(TAG, e.getMessage());
1011 writeSuccess = false;
1012 }
1013 }
1014 return writeSuccess;
1015 }
1016
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001017
1018 /**
1019 * Extracts {@link #mProfileToConnectableDevicesInfo} from
1020 * {@link #mProfileToConnectableDevicesMap}
1021 * {@link #mProfileToConnectableDevicesInfo} is a map of Profiles to List of names of
1022 * Connectable
1023 * devices that gets written to a file.
1024 */
1025 private void extractDeviceInfoFromDeviceMapLocked() {
1026 mProfileToConnectableDevicesInfo = new HashMap<Integer, List<String>>();
1027
1028 for (Map.Entry<Integer, BluetoothDevicesInfo> entry : mProfileToConnectableDevicesMap
1029 .entrySet()) {
1030 // for every entry, extract the Profile and the list of device names
1031 Integer profile = entry.getKey();
1032 if (DBG) {
1033 Log.d(TAG, "Extracting for profile " + profile);
1034 }
1035 List<String> deviceNames = new ArrayList<>();
1036 BluetoothDevicesInfo devicesInfo = entry.getValue();
1037 // Iterate through the List<BluetoothDevice> and build List<DeviceNames>
1038 if (devicesInfo != null && devicesInfo.getDeviceList() != null) {
1039 for (BluetoothDevice device : devicesInfo.getDeviceList()) {
1040 deviceNames.add(device.getName());
1041 if (DBG) {
1042 Log.d(TAG, "Device: " + device.getName());
1043 }
1044 }
1045 }
1046 mProfileToConnectableDevicesInfo.put(profile, deviceNames);
1047 }
1048 }
1049
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001050 /**
1051 * Read the device information from file and populate the
1052 * {@link #mProfileToConnectableDevicesMap}
1053 *
1054 * @return - true if the read was successful, false if not.
1055 */
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001056 public synchronized boolean readDeviceInfoFromFile() {
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001057 boolean readSuccess = true;
1058 try {
1059 AtomicFile iFile = new AtomicFile(new File(mContext.getFilesDir(), DEVICE_INFO_FILE));
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001060 if (DBG) {
1061 Log.d(TAG, "Reading from file");
1062 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001063 FileInputStream inFile = iFile.openRead();
1064 ObjectInputStream istream = new ObjectInputStream(inFile);
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001065 mProfileToConnectableDevicesInfo =
1066 (HashMap<Integer, List<String>>) istream.readObject();
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001067 istream.close();
1068 inFile.close();
1069 } catch (IOException | ClassNotFoundException e) {
1070 Log.e(TAG, e.getMessage());
1071 if (DBG) {
1072 Log.d(TAG, "No previously connected device information available");
1073 }
1074 readSuccess = false;
1075 }
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001076 if (readSuccess) {
1077 readSuccess = rebuildDeviceMapFromDeviceInfoLocked();
1078 }
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001079 return readSuccess;
1080 }
1081
1082 /**
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001083 * Rebuild the {@link #mProfileToConnectableDevicesMap} from the {@link
1084 * #mProfileToConnectableDevicesInfo}
1085 *
1086 * @return true if the reconstruction was successful, false if not.
1087 */
1088 private boolean rebuildDeviceMapFromDeviceInfoLocked() {
1089 if (DBG) {
1090 Log.d(TAG, "Rebuilding device map");
1091 }
1092
1093 if (mProfileToConnectableDevicesInfo == null) {
1094 Log.w(TAG, "No Device Info to rebuild the Device Map");
1095 return false;
1096 }
1097
1098 if (mBluetoothAdapter != null ) {
1099 if (DBG) {
1100 Log.d(TAG, "Bonded devices size:" + mBluetoothAdapter.getBondedDevices().size());
1101 }
1102 if(mBluetoothAdapter.getBondedDevices().isEmpty()) {
1103 if (DBG) {
1104 Log.d(TAG, "No Bonded Devices available. Quit rebuilding");
1105 }
1106 return false;
1107 }
1108 }
1109
1110 boolean rebuildSuccess = true;
1111 // Iterate through the Map's entries and build the {@link #mProfileToConnectableDevicesMap}
1112 if (mProfileToConnectableDevicesMap == null ) {
1113 mProfileToConnectableDevicesMap = new HashMap<Integer, BluetoothDevicesInfo>();
1114 }
1115
1116 for (Map.Entry<Integer, List<String>> entry : mProfileToConnectableDevicesInfo.entrySet()) {
1117 Integer profile = entry.getKey();
1118 List<String> deviceList = entry.getValue();
1119 // Build the BluetoothDevicesInfo for this profile.
1120 BluetoothDevicesInfo devicesInfo = new BluetoothDevicesInfo(profile);
1121 // Do we have a bonded device with this name? If so, get it and populate the device
1122 // map.
1123 for (String name : deviceList) {
1124 BluetoothDevice deviceToAdd = getBondedDeviceWithGivenName(name);
1125 if (deviceToAdd != null) {
1126 devicesInfo.addDeviceLocked(deviceToAdd);
1127 } else {
1128 if (DBG) {
1129 Log.d(TAG, "No device with name " + name + " found in bonded devices");
1130 }
1131 }
1132 }
1133 mProfileToConnectableDevicesMap.put(profile, devicesInfo);
1134 }
1135
1136 return rebuildSuccess;
1137 }
1138
1139 /**
1140 * Given the device name, find the corresponding {@link BluetoothDevice} from the list of
1141 * Bonded devices.
1142 *
1143 * @param name Bluetooth Device name
1144 */
1145 private BluetoothDevice getBondedDeviceWithGivenName(String name) {
1146 if (mBluetoothAdapter == null) {
1147 if (DBG) {
1148 Log.d(TAG, "Bluetooth Adapter Null");
1149 }
1150 return null;
1151 }
1152 if (name == null) {
1153 Log.w(TAG, "getBondedDeviceWithGivenName() Passing in a null name");
1154 return null;
1155 }
1156 if (DBG) {
1157 Log.d(TAG, "Looking for bonded device: " + name);
1158 }
1159 BluetoothDevice btDevice = null;
1160 Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
1161 for (BluetoothDevice bd : bondedDevices) {
1162 if (name.equals(bd.getName())) {
1163 btDevice = bd;
1164 break;
1165 }
1166 }
1167 return btDevice;
1168 }
1169
1170 /**
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001171 * All the BluetoothProfile.ServiceListeners to get the Profile Proxy objects
1172 */
1173 private BluetoothProfile.ServiceListener mProfileListener =
1174 new BluetoothProfile.ServiceListener() {
1175 public void onServiceConnected(int profile, BluetoothProfile proxy) {
1176 if (DBG) {
1177 Log.d(TAG, "OnServiceConnected profile: " + profile);
1178 }
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001179 switch (profile) {
1180 case BluetoothProfile.A2DP_SINK:
1181 mBluetoothA2dpSink = (BluetoothA2dpSink) proxy;
1182 break;
1183
1184 case BluetoothProfile.HEADSET_CLIENT:
1185 mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
1186 break;
1187
1188 case BluetoothProfile.PBAP_CLIENT:
1189 mBluetoothPbapClient = (BluetoothPbapClient) proxy;
1190 break;
1191
1192 case BluetoothProfile.MAP_CLIENT:
1193 mBluetoothMapClient = (BluetoothMapClient) proxy;
1194 break;
1195
1196 default:
1197 if (DBG) {
1198 Log.d(TAG, "Unhandled profile");
1199 }
1200 break;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001201 }
1202
1203 }
1204
1205 public void onServiceDisconnected(int profile) {
1206 if (DBG) {
1207 Log.d(TAG, "onServiceDisconnected profile: " + profile);
1208 }
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001209 switch (profile) {
1210 case BluetoothProfile.A2DP_SINK:
1211 mBluetoothA2dpSink = null;
1212 break;
1213
1214 case BluetoothProfile.HEADSET_CLIENT:
1215 mBluetoothHeadsetClient = null;
1216 break;
1217
1218 case BluetoothProfile.PBAP_CLIENT:
1219 mBluetoothPbapClient = null;
1220 break;
1221
1222 case BluetoothProfile.MAP_CLIENT:
1223 mBluetoothMapClient = null;
1224 break;
1225
1226 default:
1227 if (DBG) {
1228 Log.d(TAG, "Unhandled profile");
1229 }
1230 break;
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001231 }
1232 }
1233 };
1234
1235 public void dump(PrintWriter writer) {
1236 writer.println("*BluetoothDeviceConnectionPolicy*");
1237 printDeviceMap(writer);
Ram Periathiruvadiee28c002017-02-07 21:35:01 -08001238 mBluetoothAutoConnectStateMachine.dump(writer);
Ram Periathiruvadi7ed84182017-01-20 15:18:08 -08001239 }
1240}