blob: cfd45e5479237a14011af2e21c9e0ff7781190c8 [file] [log] [blame]
Sal Savage703c46f2019-04-15 08:39:25 -07001/*
2 * Copyright (C) 2019 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 static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_A2DP_SINK_DEVICES;
20import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_HFP_CLIENT_DEVICES;
21import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_MAP_CLIENT_DEVICES;
22import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_PAN_DEVICES;
23import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_PBAP_CLIENT_DEVICES;
24
25import android.bluetooth.BluetoothA2dpSink;
26import android.bluetooth.BluetoothAdapter;
27import android.bluetooth.BluetoothDevice;
28import android.bluetooth.BluetoothHeadsetClient;
29import android.bluetooth.BluetoothMapClient;
30import android.bluetooth.BluetoothPan;
31import android.bluetooth.BluetoothPbapClient;
32import android.bluetooth.BluetoothProfile;
33import android.bluetooth.BluetoothUuid;
34import android.car.ICarBluetoothUserService;
35import android.content.BroadcastReceiver;
36import android.content.Context;
37import android.content.Intent;
38import android.content.IntentFilter;
39import android.os.Handler;
40import android.os.Looper;
41import android.os.ParcelUuid;
42import android.os.Parcelable;
43import android.os.RemoteException;
44import android.os.UserHandle;
45import android.provider.Settings;
46import android.util.Log;
47import android.util.SparseArray;
48
Sal Savage13921c32019-11-14 15:30:18 -080049import com.android.internal.annotations.GuardedBy;
50
Sal Savage703c46f2019-04-15 08:39:25 -070051import java.io.PrintWriter;
52import java.util.ArrayList;
53import java.util.Arrays;
54import java.util.List;
55import java.util.Objects;
56import java.util.Set;
57
58/**
59 * BluetoothProfileDeviceManager - Manages a list of devices, sorted by connection attempt priority.
60 * Provides a means for other applications to request connection events and adjust the device
61 * connection priorities. Access to these functions is provided through CarBluetoothManager.
62 */
63public class BluetoothProfileDeviceManager {
64 private static final String TAG = "BluetoothProfileDeviceManager";
65 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
66 private final Context mContext;
67 private final int mUserId;
68
69 private static final String SETTINGS_DELIMITER = ",";
70
71 private static final int AUTO_CONNECT_TIMEOUT_MS = 8000;
72 private static final Object AUTO_CONNECT_TOKEN = new Object();
73
74 private static class BluetoothProfileInfo {
75 final String mSettingsKey;
76 final String mConnectionAction;
77 final ParcelUuid[] mUuids;
78 final int[] mProfileTriggers;
79
80 private BluetoothProfileInfo(String action, String settingsKey, ParcelUuid[] uuids,
81 int[] profileTriggers) {
82 mSettingsKey = settingsKey;
83 mConnectionAction = action;
84 mUuids = uuids;
85 mProfileTriggers = profileTriggers;
86 }
87 }
88
89 private static final SparseArray<BluetoothProfileInfo> sProfileActions = new SparseArray();
90 static {
91 sProfileActions.put(BluetoothProfile.A2DP_SINK,
92 new BluetoothProfileInfo(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED,
93 KEY_BLUETOOTH_A2DP_SINK_DEVICES, new ParcelUuid[] {
Rahul Sabnisb9679f92019-12-03 15:37:51 -080094 BluetoothUuid.A2DP_SOURCE
Sal Savage703c46f2019-04-15 08:39:25 -070095 }, new int[] {}));
96 sProfileActions.put(BluetoothProfile.HEADSET_CLIENT,
97 new BluetoothProfileInfo(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED,
98 KEY_BLUETOOTH_HFP_CLIENT_DEVICES, new ParcelUuid[] {
Rahul Sabnisb9679f92019-12-03 15:37:51 -080099 BluetoothUuid.HFP_AG,
Sal Savage703c46f2019-04-15 08:39:25 -0700100 BluetoothUuid.HSP_AG
101 }, new int[] {BluetoothProfile.MAP_CLIENT, BluetoothProfile.PBAP_CLIENT}));
102 sProfileActions.put(BluetoothProfile.MAP_CLIENT,
103 new BluetoothProfileInfo(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED,
104 KEY_BLUETOOTH_MAP_CLIENT_DEVICES, new ParcelUuid[] {
105 BluetoothUuid.MAS
106 }, new int[] {}));
107 sProfileActions.put(BluetoothProfile.PAN,
108 new BluetoothProfileInfo(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED,
109 KEY_BLUETOOTH_PAN_DEVICES, new ParcelUuid[] {
110 BluetoothUuid.PANU
111 }, new int[] {}));
112 sProfileActions.put(BluetoothProfile.PBAP_CLIENT,
113 new BluetoothProfileInfo(BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED,
114 KEY_BLUETOOTH_PBAP_CLIENT_DEVICES, new ParcelUuid[] {
115 BluetoothUuid.PBAP_PSE
116 }, new int[] {}));
117 }
118
Sal Savage13921c32019-11-14 15:30:18 -0800119 // Fixed per-profile information for the profile this object manages
Sal Savage703c46f2019-04-15 08:39:25 -0700120 private final int mProfileId;
121 private final String mSettingsKey;
122 private final String mProfileConnectionAction;
123 private final ParcelUuid[] mProfileUuids;
124 private final int[] mProfileTriggers;
Sal Savage13921c32019-11-14 15:30:18 -0800125
126 // Central priority list of devices
127 private final Object mPrioritizedDevicesLock = new Object();
128 @GuardedBy("mPrioritizedDevicesLock")
Sal Savage703c46f2019-04-15 08:39:25 -0700129 private ArrayList<BluetoothDevice> mPrioritizedDevices;
130
Sal Savage13921c32019-11-14 15:30:18 -0800131 // Auto connection process state
Sal Savage703c46f2019-04-15 08:39:25 -0700132 private final Object mAutoConnectLock = new Object();
Sal Savage13921c32019-11-14 15:30:18 -0800133 @GuardedBy("mAutoConnectLock")
Sal Savage703c46f2019-04-15 08:39:25 -0700134 private boolean mConnecting = false;
Sal Savage13921c32019-11-14 15:30:18 -0800135 @GuardedBy("mAutoConnectLock")
Sal Savage703c46f2019-04-15 08:39:25 -0700136 private int mAutoConnectPriority;
Sal Savage13921c32019-11-14 15:30:18 -0800137 @GuardedBy("mAutoConnectLock")
Sal Savage703c46f2019-04-15 08:39:25 -0700138 private ArrayList<BluetoothDevice> mAutoConnectingDevices;
139
Sal Savage13921c32019-11-14 15:30:18 -0800140 private final BluetoothAdapter mBluetoothAdapter;
141 private final BluetoothBroadcastReceiver mBluetoothBroadcastReceiver;
142 private final ICarBluetoothUserService mBluetoothUserProxies;
Sal Savage703c46f2019-04-15 08:39:25 -0700143 private final Handler mHandler = new Handler(Looper.getMainLooper());
144
145 /**
146 * A BroadcastReceiver that listens specifically for actions related to the profile we're
147 * tracking and uses them to update the status.
148 */
149 private class BluetoothBroadcastReceiver extends BroadcastReceiver {
150 @Override
151 public void onReceive(Context context, Intent intent) {
152 String action = intent.getAction();
153 if (mProfileConnectionAction.equals(action)) {
154 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
155 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
156 BluetoothProfile.STATE_DISCONNECTED);
157 handleDeviceConnectionStateChange(device, state);
158 } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
159 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
160 int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
161 BluetoothDevice.ERROR);
162 handleDeviceBondStateChange(device, state);
163 } else if (BluetoothDevice.ACTION_UUID.equals(action)) {
164 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
165 Parcelable[] uuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID);
166 handleDeviceUuidEvent(device, uuids);
167 } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
168 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
169 handleAdapterStateChange(state);
170 }
171 }
172 }
173
174 /**
175 * Handles an incoming Profile-Device connection event.
176 *
177 * On <BluetoothProfile>.ACTION_CONNECTION_STATE_CHANGED coming from the BroadcastReceiver:
178 * On connected, if we're auto connecting and this is the current device we're managing, then
179 * see if we can move on to the next device in the list. Otherwise, If the device connected
180 * then add it to our priority list if it's not on their already.
181 *
182 * On disconnected, if the device that disconnected also has had its profile priority set to
183 * PRIORITY_OFF, then remove it from our list.
184 *
185 * @param device - The Bluetooth device the state change is for
186 * @param state - The new profile connection state of the device
187 */
188 private void handleDeviceConnectionStateChange(BluetoothDevice device, int state) {
189 logd("Connection state changed [device: " + device + ", state: "
190 + Utils.getConnectionStateName(state) + "]");
191 if (state == BluetoothProfile.STATE_CONNECTED) {
192 if (isAutoConnecting() && isAutoConnectingDevice(device)) {
193 continueAutoConnecting();
194 } else {
195 if (getProfilePriority(device) >= BluetoothProfile.PRIORITY_ON) {
196 addDevice(device); // No-op if device is in the list.
197 }
198 triggerConnections(device);
199 }
Sal Savage703c46f2019-04-15 08:39:25 -0700200 }
Sal Savage619b04b2019-05-10 10:45:27 -0700201 // NOTE: We wanted check on disconnect if a device is priority off and use that as an
202 // indicator to remove a device from the list, but priority reporting can be flaky and
203 // was leading to us removing devices when we didn't want to.
Sal Savage703c46f2019-04-15 08:39:25 -0700204 }
205
206 /**
207 * Handles an incoming device bond status event.
208 *
209 * On BluetoothDevice.ACTION_BOND_STATE_CHANGED:
210 * - If a device becomes unbonded, remove it from our list if it's there.
211 * - If it's bonded, then add it to our list if the UUID set says it supports us.
212 *
213 * @param device - The Bluetooth device the state change is for
214 * @param state - The new bond state of the device
215 */
216 private void handleDeviceBondStateChange(BluetoothDevice device, int state) {
217 logd("Bond state has changed [device: " + device + ", state: "
218 + Utils.getBondStateName(state) + "]");
219 if (state == BluetoothDevice.BOND_NONE) {
220 // Note: We have seen cases of unbonding events being sent without actually
221 // unbonding the device.
222 removeDevice(device);
223 } else if (state == BluetoothDevice.BOND_BONDED) {
224 addBondedDeviceIfSupported(device);
225 }
226 }
227
228 /**
229 * Handles an incoming device UUID set update event.
230 *
231 * On BluetoothDevice.ACTION_UUID:
232 * If the UUID is one this profile cares about, set the profile priority for the device that
233 * the UUID was found on to PRIORITY_ON if its not PRIORITY_OFF already (meaning inhibited or
234 * disabled by the user through settings).
235 *
236 * @param device - The Bluetooth device the UUID event is for
237 * @param uuids - The incoming set of supported UUIDs for the device
238 */
239 private void handleDeviceUuidEvent(BluetoothDevice device, Parcelable[] uuids) {
240 logd("UUIDs found, device: " + device);
241 if (uuids != null) {
242 ParcelUuid[] uuidsToSend = new ParcelUuid[uuids.length];
243 for (int i = 0; i < uuidsToSend.length; i++) {
244 uuidsToSend[i] = (ParcelUuid) uuids[i];
245 }
246 provisionDeviceIfSupported(device, uuidsToSend);
247 }
248 }
249
250 /**
251 * Handle an adapter state change event.
252 *
253 * On BluetoothAdapter.ACTION_STATE_CHANGED:
254 * If the adapter is going into the OFF state, then cancel any auto connecting, commit our
255 * priority list and go idle.
256 *
257 * @param state - The new state of the Bluetooth adapter
258 */
259 private void handleAdapterStateChange(int state) {
260 logd("Bluetooth Adapter state changed: " + Utils.getAdapterStateName(state));
261 // Crashes of the BT stack mean we're not promised to see all the state changes we
262 // might want to see. In order to be a bit more robust to crashes, we'll treat any
263 // non-ON state as a time to cancel auto-connect. This gives us a better chance of
264 // seeing a cancel state before a crash, as well as makes sure we're "cancelled"
265 // before we see an ON.
266 if (state != BluetoothAdapter.STATE_ON) {
267 cancelAutoConnecting();
268 }
269 // To reduce how many times we're committing the list, we'll only write back on off
270 if (state == BluetoothAdapter.STATE_OFF) {
271 commit();
272 }
273 }
274
275 /**
276 * Creates an instance of BluetoothProfileDeviceManager that will manage devices
277 * for the given profile ID.
278 *
279 * @param context - context of calling code
280 * @param userId - ID of user we want to manage devices for
281 * @param bluetoothUserProxies - Set of per-user bluetooth proxies for calling into the
282 * bluetooth stack as the current user.
283 * @param profileId - BluetoothProfile integer that represents the profile we're managing
284 * @return A new instance of a BluetoothProfileDeviceManager, or null on any error
285 */
286 public static BluetoothProfileDeviceManager create(Context context, int userId,
287 ICarBluetoothUserService bluetoothUserProxies, int profileId) {
288 try {
289 return new BluetoothProfileDeviceManager(context, userId, bluetoothUserProxies,
290 profileId);
291 } catch (NullPointerException | IllegalArgumentException e) {
292 return null;
293 }
294 }
295
296 /**
297 * Creates an instance of BluetoothProfileDeviceManager that will manage devices
298 * for the given profile ID.
299 *
300 * @param context - context of calling code
301 * @param userId - ID of user we want to manage devices for
302 * @param bluetoothUserProxies - Set of per-user bluetooth proxies for calling into the
303 * bluetooth stack as the current user.
304 * @param profileId - BluetoothProfile integer that represents the profile we're managing
305 * @return A new instance of a BluetoothProfileDeviceManager
306 */
307 private BluetoothProfileDeviceManager(Context context, int userId,
308 ICarBluetoothUserService bluetoothUserProxies, int profileId) {
309 mContext = Objects.requireNonNull(context);
310 mUserId = userId;
311 mBluetoothUserProxies = bluetoothUserProxies;
312
313 mPrioritizedDevices = new ArrayList<>();
314 BluetoothProfileInfo bpi = sProfileActions.get(profileId);
315 if (bpi == null) {
316 throw new IllegalArgumentException("Provided profile " + Utils.getProfileName(profileId)
317 + " is unrecognized");
318 }
319 mProfileId = profileId;
320 mSettingsKey = bpi.mSettingsKey;
321 mProfileConnectionAction = bpi.mConnectionAction;
322 mProfileUuids = bpi.mUuids;
323 mProfileTriggers = bpi.mProfileTriggers;
324
Sal Savage13921c32019-11-14 15:30:18 -0800325 mBluetoothBroadcastReceiver = new BluetoothBroadcastReceiver();
Sal Savage703c46f2019-04-15 08:39:25 -0700326 mBluetoothAdapter = Objects.requireNonNull(BluetoothAdapter.getDefaultAdapter());
327 }
328
329 /**
330 * Begin managing devices for this profile. Sets the start state from persistent memory.
331 */
332 public void start() {
333 logd("Starting device management");
334 load();
335 synchronized (mAutoConnectLock) {
336 mConnecting = false;
337 mAutoConnectPriority = -1;
338 mAutoConnectingDevices = null;
339 }
Sal Savage13921c32019-11-14 15:30:18 -0800340
Sal Savage703c46f2019-04-15 08:39:25 -0700341 IntentFilter profileFilter = new IntentFilter();
342 profileFilter.addAction(mProfileConnectionAction);
343 profileFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
344 profileFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
345 profileFilter.addAction(BluetoothDevice.ACTION_UUID);
346 mContext.registerReceiverAsUser(mBluetoothBroadcastReceiver, UserHandle.CURRENT,
347 profileFilter, null, null);
348 }
349
350 /**
351 * Stop managing devices for this profile. Commits the final priority list to persistent memory
352 * and cleans up local resources.
353 */
354 public void stop() {
355 logd("Stopping device management");
356 if (mBluetoothBroadcastReceiver != null) {
357 if (mContext != null) {
358 mContext.unregisterReceiver(mBluetoothBroadcastReceiver);
359 }
Sal Savage703c46f2019-04-15 08:39:25 -0700360 }
361 cancelAutoConnecting();
362 commit();
363 return;
364 }
365
366 /**
367 * Loads the current device priority list from persistent memory in {@link Settings.Secure}.
368 *
369 * This will overwrite the contents of the local priority list. It does not attempt to take the
370 * union of the file and existing set. As such, you likely do not want to load after starting.
371 * Failed attempts to load leave the prioritized device list unchanged.
372 *
373 * @return true on success, false otherwise
374 */
375 private boolean load() {
376 logd("Loading device priority list snapshot using key '" + mSettingsKey + "'");
377
378 // Read from Settings.Secure for our profile, as the current user.
379 String devicesStr = Settings.Secure.getStringForUser(mContext.getContentResolver(),
380 mSettingsKey, mUserId);
381 logd("Found Device String: '" + devicesStr + "'");
382 if (devicesStr == null || "".equals(devicesStr)) {
383 return false;
384 }
385
386 // Split string into list of device MAC addresses
387 List<String> deviceList = Arrays.asList(devicesStr.split(SETTINGS_DELIMITER));
388 if (deviceList == null) {
389 return false;
390 }
391
392 // Turn the strings into full blown Bluetooth devices
393 ArrayList<BluetoothDevice> devices = new ArrayList<>();
394 for (String address : deviceList) {
395 try {
396 BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
397 devices.add(device);
398 } catch (IllegalArgumentException e) {
399 logw("Unable to parse address '" + address + "' to a device");
400 continue;
401 }
402 }
403
Sal Savage13921c32019-11-14 15:30:18 -0800404 synchronized (mPrioritizedDevicesLock) {
Sal Savage703c46f2019-04-15 08:39:25 -0700405 mPrioritizedDevices = devices;
406 }
407
408 logd("Loaded Priority list: " + devices);
409 return true;
410 }
411
412 /**
413 * Commits the current device priority list to persistent memory in {@link Settings.Secure}.
414 *
415 * @return true on success, false otherwise
416 */
417 private boolean commit() {
418 StringBuilder sb = new StringBuilder();
419 String delimiter = "";
Sal Savage13921c32019-11-14 15:30:18 -0800420 synchronized (mPrioritizedDevicesLock) {
Sal Savage703c46f2019-04-15 08:39:25 -0700421 for (BluetoothDevice device : mPrioritizedDevices) {
422 sb.append(delimiter);
423 sb.append(device.getAddress());
424 delimiter = SETTINGS_DELIMITER;
425 }
426 }
427
428 String devicesStr = sb.toString();
429 Settings.Secure.putStringForUser(mContext.getContentResolver(), mSettingsKey, devicesStr,
430 mUserId);
431 logd("Committed key: " + mSettingsKey + ", value: '" + devicesStr + "'");
432 return true;
433 }
434
435 /**
436 * Syncs the current priority list against the list of bonded devices from the adapter so that
437 * we can make sure things haven't changed on us between the last two times we've ran.
438 */
439 private void sync() {
440 logd("Syncing the priority list with the adapter's list of bonded devices");
441 Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
442 for (BluetoothDevice device : bondedDevices) {
443 addDevice(device); // No-op if device is already in the priority list
444 }
445
Sal Savage13921c32019-11-14 15:30:18 -0800446 synchronized (mPrioritizedDevicesLock) {
Sal Savage703c46f2019-04-15 08:39:25 -0700447 ArrayList<BluetoothDevice> devices = getDeviceListSnapshot();
448 for (BluetoothDevice device : devices) {
449 if (!bondedDevices.contains(device)) {
450 removeDevice(device);
451 }
452 }
453 }
454 }
455
456 /**
457 * Makes a clone of the current prioritized device list in a synchronized fashion
458 *
459 * @return A clone of the most up to date prioritized device list
460 */
461 public ArrayList<BluetoothDevice> getDeviceListSnapshot() {
462 ArrayList<BluetoothDevice> devices = new ArrayList<>();
Sal Savage13921c32019-11-14 15:30:18 -0800463 synchronized (mPrioritizedDevicesLock) {
Sal Savage703c46f2019-04-15 08:39:25 -0700464 devices = (ArrayList) mPrioritizedDevices.clone();
465 }
466 return devices;
467 }
468
469 /**
470 * Adds a device to the end of the priority list.
471 *
472 * @param device - The device you wish to add
473 */
Sal Savage13921c32019-11-14 15:30:18 -0800474 public void addDevice(BluetoothDevice device) {
Sal Savage703c46f2019-04-15 08:39:25 -0700475 if (device == null) return;
Sal Savage13921c32019-11-14 15:30:18 -0800476 synchronized (mPrioritizedDevicesLock) {
477 if (mPrioritizedDevices.contains(device)) return;
478 logd("Add device " + device);
479 mPrioritizedDevices.add(device);
480 commit();
481 }
Sal Savage703c46f2019-04-15 08:39:25 -0700482 }
483
484 /**
485 * Removes a device from the priority list.
486 *
487 * @param device - The device you wish to remove
488 */
Sal Savage13921c32019-11-14 15:30:18 -0800489 public void removeDevice(BluetoothDevice device) {
Sal Savage703c46f2019-04-15 08:39:25 -0700490 if (device == null) return;
Sal Savage13921c32019-11-14 15:30:18 -0800491 synchronized (mPrioritizedDevicesLock) {
492 if (!mPrioritizedDevices.contains(device)) return;
493 logd("Remove device " + device);
494 mPrioritizedDevices.remove(device);
495 commit();
496 }
Sal Savage703c46f2019-04-15 08:39:25 -0700497 }
498
499 /**
500 * Get the connection priority of a device.
501 *
502 * @param device - The device you want the priority of
503 * @return The priority of the device, or -1 if the device is not in the list
504 */
Sal Savage13921c32019-11-14 15:30:18 -0800505 public int getDeviceConnectionPriority(BluetoothDevice device) {
Sal Savage703c46f2019-04-15 08:39:25 -0700506 if (device == null) return -1;
507 logd("Get connection priority of " + device);
Sal Savage13921c32019-11-14 15:30:18 -0800508 synchronized (mPrioritizedDevicesLock) {
509 return mPrioritizedDevices.indexOf(device);
510 }
Sal Savage703c46f2019-04-15 08:39:25 -0700511 }
512
513 /**
514 * Set the connection priority of a device.
515 *
516 * If the devide does not exist, it will be added. If the priority is less than zero,
517 * no priority will be set. If the priority exceeds the bounds of the list, no priority will be
518 * set.
519 *
520 * @param device - The device you want to set the priority of
521 * @param priority - The priority you want to the device to have
522 */
Sal Savage13921c32019-11-14 15:30:18 -0800523 public void setDeviceConnectionPriority(BluetoothDevice device, int priority) {
524 synchronized (mPrioritizedDevicesLock) {
525 if (device == null || priority < 0 || priority > mPrioritizedDevices.size()
526 || getDeviceConnectionPriority(device) == priority) return;
527 if (mPrioritizedDevices.contains(device)) {
528 mPrioritizedDevices.remove(device);
529 if (priority > mPrioritizedDevices.size()) priority = mPrioritizedDevices.size();
530 }
531 logd("Set connection priority of " + device + " to " + priority);
532 mPrioritizedDevices.add(priority, device);
533 commit();
Sal Savage703c46f2019-04-15 08:39:25 -0700534 }
Sal Savage703c46f2019-04-15 08:39:25 -0700535 }
536
537 /**
538 * Connect a specific device on this profile.
539 *
540 * @param device - The device to connect
541 * @return true on success, false otherwise
542 */
543 private boolean connect(BluetoothDevice device) {
544 logd("Connecting " + device);
545 try {
Sal Savage619b04b2019-05-10 10:45:27 -0700546 return mBluetoothUserProxies.bluetoothConnectToProfile(mProfileId, device);
Sal Savage703c46f2019-04-15 08:39:25 -0700547 } catch (RemoteException e) {
548 logw("Failed to connect " + device + ", Reason: " + e);
Sal Savage703c46f2019-04-15 08:39:25 -0700549 }
Sal Savage619b04b2019-05-10 10:45:27 -0700550 return false;
Sal Savage703c46f2019-04-15 08:39:25 -0700551 }
552
553 /**
554 * Disconnect a specific device from this profile.
555 *
556 * @param device - The device to disconnect
557 * @return true on success, false otherwise
558 */
559 private boolean disconnect(BluetoothDevice device) {
560 logd("Disconnecting " + device);
561 try {
Sal Savage619b04b2019-05-10 10:45:27 -0700562 return mBluetoothUserProxies.bluetoothDisconnectFromProfile(mProfileId, device);
Sal Savage703c46f2019-04-15 08:39:25 -0700563 } catch (RemoteException e) {
564 logw("Failed to disconnect " + device + ", Reason: " + e);
Sal Savage703c46f2019-04-15 08:39:25 -0700565 }
Sal Savage619b04b2019-05-10 10:45:27 -0700566 return false;
Sal Savage703c46f2019-04-15 08:39:25 -0700567 }
568
569 /**
570 * Gets the Bluetooth stack priority on this profile for a specific device.
571 *
572 * @param device - The device to get the Bluetooth stack priority of
573 * @return The Bluetooth stack priority on this profile for the given device
574 */
575 private int getProfilePriority(BluetoothDevice device) {
576 try {
577 return mBluetoothUserProxies.getProfilePriority(mProfileId, device);
578 } catch (RemoteException e) {
579 logw("Failed to get bluetooth stack priority for " + device + ", Reason: " + e);
580 }
581 return BluetoothProfile.PRIORITY_UNDEFINED;
582 }
583
584 /**
585 * Gets the Bluetooth stack priority on this profile for a specific device.
586 *
587 * @param device - The device to set the Bluetooth stack priority of
588 * @return true on success, false otherwise
589 */
590 private boolean setProfilePriority(BluetoothDevice device, int priority) {
591 logd("Set " + device + " stack priority to " + Utils.getProfilePriorityName(priority));
592 try {
593 mBluetoothUserProxies.setProfilePriority(mProfileId, device, priority);
594 } catch (RemoteException e) {
595 logw("Failed to set bluetooth stack priority for " + device + ", Reason: " + e);
596 return false;
597 }
598 return true;
599 }
600
601 /**
602 * Begins the process of connecting to devices, one by one, in the order that the priority
603 * list currently specifies.
604 *
605 * If we are already connecting, or no devices are present, then no work is done.
606 */
607 public void beginAutoConnecting() {
608 logd("Request to begin auto connection process");
609 synchronized (mAutoConnectLock) {
610 if (isAutoConnecting()) {
611 logd("Auto connect requested while we are already auto connecting.");
612 return;
613 }
614 if (mBluetoothAdapter.getState() != BluetoothAdapter.STATE_ON) {
615 logd("Bluetooth Adapter is not on, cannot connect devices");
616 return;
617 }
618 mAutoConnectingDevices = getDeviceListSnapshot();
619 if (mAutoConnectingDevices.size() == 0) {
620 logd("No saved devices to auto-connect to.");
621 cancelAutoConnecting();
622 return;
623 }
624 mConnecting = true;
625 mAutoConnectPriority = 0;
626 }
627 autoConnectWithTimeout();
628 }
629
630 /**
631 * Connects the current priority device and sets a timeout timer to indicate when to give up and
632 * move on to the next one.
633 */
634 private void autoConnectWithTimeout() {
635 synchronized (mAutoConnectLock) {
636 if (!isAutoConnecting()) {
637 logd("Autoconnect process was cancelled, skipping connecting next device.");
638 return;
639 }
640 if (mAutoConnectPriority < 0 || mAutoConnectPriority >= mAutoConnectingDevices.size()) {
641 return;
642 }
643
644 BluetoothDevice device = mAutoConnectingDevices.get(mAutoConnectPriority);
645 logd("Auto connecting (" + mAutoConnectPriority + ") device: " + device);
646
647 mHandler.post(() -> {
Sal Savage619b04b2019-05-10 10:45:27 -0700648 boolean connectStatus = connect(device);
649 if (!connectStatus) {
650 logw("Connection attempt immediately failed, moving to the next device");
651 continueAutoConnecting();
652 }
Sal Savage703c46f2019-04-15 08:39:25 -0700653 });
654 mHandler.postDelayed(() -> {
655 logw("Auto connect process has timed out connecting to " + device);
656 continueAutoConnecting();
657 }, AUTO_CONNECT_TOKEN, AUTO_CONNECT_TIMEOUT_MS);
658 }
659 }
660
661 /**
662 * Will forcibly move the auto connect process to the next device, or finish it if no more
663 * devices are available.
664 */
665 private void continueAutoConnecting() {
666 logd("Continue auto-connect process on next device");
667 synchronized (mAutoConnectLock) {
668 if (!isAutoConnecting()) {
669 logd("Autoconnect process was cancelled, no need to continue.");
670 return;
671 }
672 mHandler.removeCallbacksAndMessages(AUTO_CONNECT_TOKEN);
673 mAutoConnectPriority++;
674 if (mAutoConnectPriority >= mAutoConnectingDevices.size()) {
675 logd("No more devices to connect to");
676 cancelAutoConnecting();
677 return;
678 }
679 }
680 autoConnectWithTimeout();
681 }
682
683 /**
684 * Cancels the auto-connection process. Any in-flight connection attempts will still be tried.
685 *
686 * Canceling is defined as deleting the snapshot of devices, resetting the device to connect
687 * index, setting the connecting boolean to null, and removing any pending timeouts if they
688 * exist.
689 *
690 * If there are no auto-connects in process this will do nothing.
691 */
692 private void cancelAutoConnecting() {
693 logd("Cleaning up any auto-connect process");
694 synchronized (mAutoConnectLock) {
695 if (!isAutoConnecting()) return;
696 mHandler.removeCallbacksAndMessages(AUTO_CONNECT_TOKEN);
697 mConnecting = false;
698 mAutoConnectPriority = -1;
699 mAutoConnectingDevices = null;
700 }
701 }
702
703 /**
704 * Get the auto-connect status of thie profile device manager
705 *
706 * @return true on success, false otherwise
707 */
708 public boolean isAutoConnecting() {
709 synchronized (mAutoConnectLock) {
710 return mConnecting;
711 }
712 }
713
714 /**
715 * Determine if a device is the currently auto-connecting device
716 *
717 * @param device - A BluetoothDevice object to compare against any know auto connecting device
718 * @return true if the input device is the device we're currently connecting, false otherwise
719 */
720 private boolean isAutoConnectingDevice(BluetoothDevice device) {
721 synchronized (mAutoConnectLock) {
722 if (mAutoConnectingDevices == null) return false;
723 return mAutoConnectingDevices.get(mAutoConnectPriority).equals(device);
724 }
725 }
726
727 /**
728 * Given a device, will check the cached UUID set and see if it supports this profile. If it
729 * does then we will add it to the end of our prioritized set and attempt a connection if and
730 * only if the Bluetooth device priority allows a connection.
731 *
732 * Will do nothing if the device isn't bonded.
733 */
734 private void addBondedDeviceIfSupported(BluetoothDevice device) {
735 logd("Add device " + device + " if it is supported");
736 if (device.getBondState() != BluetoothDevice.BOND_BONDED) return;
737 if (BluetoothUuid.containsAnyUuid(device.getUuids(), mProfileUuids)
738 && getProfilePriority(device) >= BluetoothProfile.PRIORITY_ON) {
739 addDevice(device);
740 }
741 }
742
743 /**
744 * Checks the reported UUIDs for a device to see if the device supports this profile. If it does
745 * then it will update the underlying Bluetooth stack with PRIORITY_ON so long as the device
746 * doesn't have a PRIORITY_OFF value set.
747 *
748 * @param device - The device that may support our profile
749 * @param uuids - The set of UUIDs for the device, which may include our profile
750 */
751 private void provisionDeviceIfSupported(BluetoothDevice device, ParcelUuid[] uuids) {
752 logd("Checking UUIDs for device: " + device);
753 if (BluetoothUuid.containsAnyUuid(uuids, mProfileUuids)) {
754 int devicePriority = getProfilePriority(device);
755 logd("Device " + device + " supports this profile. Priority: "
756 + Utils.getProfilePriorityName(devicePriority));
757 // Transition from PRIORITY_OFF to any other Bluetooth stack priority value is supposed
758 // to be a user choice, enabled through the Settings applications. That's why we don't
759 // do it here for them.
760 if (devicePriority == BluetoothProfile.PRIORITY_UNDEFINED) {
761 // As a note, UUID updates happen during pairing, as well as each time the adapter
762 // turns on. Initiating connections to bonded device following UUID verification
763 // would defeat the purpose of the priority list. They don't arrive in a predictable
764 // order either. Since we call this function on UUID discovery, don't connect here!
765 setProfilePriority(device, BluetoothProfile.PRIORITY_ON);
766 return;
767 }
768 }
769 logd("Provisioning of " + device + " has ended without priority being set");
770 }
771
772 /**
773 * Trigger connections of related Bluetooth profiles on a device
774 *
775 * @param device - The Bluetooth device you would like to connect to
776 */
777 private void triggerConnections(BluetoothDevice device) {
778 for (int profile : mProfileTriggers) {
779 logd("Trigger connection to " + Utils.getProfileName(profile) + "on " + device);
780 try {
781 mBluetoothUserProxies.bluetoothConnectToProfile(profile, device);
782 } catch (RemoteException e) {
783 logw("Failed to connect " + device + ", Reason: " + e);
784 }
785 }
786 }
787
788 /**
789 * Writes the verbose current state of the object to the PrintWriter
790 *
791 * @param writer PrintWriter object to write lines to
792 */
793 public void dump(PrintWriter writer, String indent) {
794 writer.println(indent + "BluetoothProfileDeviceManager [" + Utils.getProfileName(mProfileId)
795 + "]");
796 writer.println(indent + "\tUser: " + mUserId);
797 writer.println(indent + "\tSettings Location: " + mSettingsKey);
798 writer.println(indent + "\tUser Proxies Exist: "
799 + (mBluetoothUserProxies != null ? "Yes" : "No"));
800 writer.println(indent + "\tAuto-Connecting: " + (isAutoConnecting() ? "Yes" : "No"));
801 writer.println(indent + "\tPriority List:");
802 ArrayList<BluetoothDevice> devices = getDeviceListSnapshot();
803 for (BluetoothDevice device : devices) {
804 writer.println(indent + "\t\t" + device.getAddress() + " - " + device.getName());
805 }
806 }
807
808 /**
809 * Log a message to DEBUG
810 */
811 private void logd(String msg) {
812 if (DBG) {
813 Log.d(TAG, "[" + Utils.getProfileName(mProfileId) + " - User: " + mUserId + "] " + msg);
814 }
815 }
816
817 /**
818 * Log a message to WARN
819 */
820 private void logw(String msg) {
821 Log.w(TAG, "[" + Utils.getProfileName(mProfileId) + " - User: " + mUserId + "] " + msg);
822 }
823}