blob: a860ff1492eff3ce5d44d8e88478a9cb8d2b5a0b [file] [log] [blame]
hughchena73686f2018-11-12 17:42:08 +08001/*
2 * Copyright 2018 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 */
16package com.android.settingslib.media;
17
18import android.app.Notification;
hughchenb3d68f62020-02-11 14:30:14 +080019import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothDevice;
hughchenbd0dd492019-01-08 14:34:10 +080021import android.bluetooth.BluetoothProfile;
hughchena73686f2018-11-12 17:42:08 +080022import android.content.Context;
Tim Peng8a0f3612020-04-30 12:33:20 +080023import android.media.RoutingSessionInfo;
hughchendf7b8382020-02-03 17:48:49 +080024import android.text.TextUtils;
hughchena73686f2018-11-12 17:42:08 +080025import android.util.Log;
26
27import androidx.annotation.IntDef;
hughchen5bd39682020-05-08 10:56:30 +080028import androidx.annotation.Nullable;
hughchena73686f2018-11-12 17:42:08 +080029
hughchenbd0dd492019-01-08 14:34:10 +080030import com.android.internal.annotations.VisibleForTesting;
hughchen81efaa52020-05-06 15:08:29 +080031import com.android.settingslib.bluetooth.A2dpProfile;
hughchena73686f2018-11-12 17:42:08 +080032import com.android.settingslib.bluetooth.BluetoothCallback;
hughchenbd0dd492019-01-08 14:34:10 +080033import com.android.settingslib.bluetooth.CachedBluetoothDevice;
hughchenb3d68f62020-02-11 14:30:14 +080034import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
hughchen81efaa52020-05-06 15:08:29 +080035import com.android.settingslib.bluetooth.HearingAidProfile;
hughchena73686f2018-11-12 17:42:08 +080036import com.android.settingslib.bluetooth.LocalBluetoothManager;
hughchen81efaa52020-05-06 15:08:29 +080037import com.android.settingslib.bluetooth.LocalBluetoothProfile;
hughchena73686f2018-11-12 17:42:08 +080038
39import java.lang.annotation.Retention;
40import java.lang.annotation.RetentionPolicy;
41import java.util.ArrayList;
42import java.util.Collection;
hughchen31901c02020-04-22 19:31:28 +080043import java.util.Collections;
timhypenga8b826c2018-11-14 15:33:53 +080044import java.util.Comparator;
hughchena73686f2018-11-12 17:42:08 +080045import java.util.List;
hughchen8175bf22020-02-12 18:52:34 +080046import java.util.concurrent.CopyOnWriteArrayList;
hughchena73686f2018-11-12 17:42:08 +080047
48/**
49 * LocalMediaManager provide interface to get MediaDevice list and transfer media to MediaDevice.
50 */
51public class LocalMediaManager implements BluetoothCallback {
timhypenga8b826c2018-11-14 15:33:53 +080052 private static final Comparator<MediaDevice> COMPARATOR = Comparator.naturalOrder();
hughchena73686f2018-11-12 17:42:08 +080053 private static final String TAG = "LocalMediaManager";
hughchen11264e22020-03-30 14:11:18 +080054 private static final int MAX_DISCONNECTED_DEVICE_NUM = 5;
hughchena73686f2018-11-12 17:42:08 +080055
56 @Retention(RetentionPolicy.SOURCE)
57 @IntDef({MediaDeviceState.STATE_CONNECTED,
58 MediaDeviceState.STATE_CONNECTING,
Tim Pengdbdfaef2020-03-11 11:30:01 +080059 MediaDeviceState.STATE_DISCONNECTED,
60 MediaDeviceState.STATE_CONNECTING_FAILED})
hughchena73686f2018-11-12 17:42:08 +080061 public @interface MediaDeviceState {
Tim Pengdbdfaef2020-03-11 11:30:01 +080062 int STATE_CONNECTED = 0;
63 int STATE_CONNECTING = 1;
64 int STATE_DISCONNECTED = 2;
65 int STATE_CONNECTING_FAILED = 3;
hughchena73686f2018-11-12 17:42:08 +080066 }
67
hughchen8175bf22020-02-12 18:52:34 +080068 private final Collection<DeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
hughchen5bd39682020-05-08 10:56:30 +080069 private final Object mMediaDevicesLock = new Object();
hughchenbd0dd492019-01-08 14:34:10 +080070 @VisibleForTesting
71 final MediaDeviceCallback mMediaDeviceCallback = new MediaDeviceCallback();
hughchena73686f2018-11-12 17:42:08 +080072
73 private Context mContext;
hughchena73686f2018-11-12 17:42:08 +080074 private LocalBluetoothManager mLocalBluetoothManager;
hughchene6a4f482019-10-18 17:34:15 +080075 private InfoMediaManager mInfoMediaManager;
76 private String mPackageName;
Tim Peng48c23672020-03-24 12:53:16 +080077 private MediaDevice mOnTransferBluetoothDevice;
hughchenbd0dd492019-01-08 14:34:10 +080078
79 @VisibleForTesting
hughchen37282f02020-03-25 18:12:52 +080080 List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
hughchenbd0dd492019-01-08 14:34:10 +080081 @VisibleForTesting
hughchen37282f02020-03-25 18:12:52 +080082 List<MediaDevice> mDisconnectedMediaDevices = new CopyOnWriteArrayList<>();
hughchenb3d68f62020-02-11 14:30:14 +080083 @VisibleForTesting
hughchenbd0dd492019-01-08 14:34:10 +080084 MediaDevice mPhoneDevice;
85 @VisibleForTesting
86 MediaDevice mCurrentConnectedDevice;
hughchenb3d68f62020-02-11 14:30:14 +080087 @VisibleForTesting
88 DeviceAttributeChangeCallback mDeviceAttributeChangeCallback =
89 new DeviceAttributeChangeCallback();
90 @VisibleForTesting
91 BluetoothAdapter mBluetoothAdapter;
hughchena73686f2018-11-12 17:42:08 +080092
93 /**
94 * Register to start receiving callbacks for MediaDevice events.
95 */
96 public void registerCallback(DeviceCallback callback) {
hughchen8175bf22020-02-12 18:52:34 +080097 mCallbacks.add(callback);
hughchena73686f2018-11-12 17:42:08 +080098 }
99
100 /**
101 * Unregister to stop receiving callbacks for MediaDevice events
102 */
103 public void unregisterCallback(DeviceCallback callback) {
hughchen8175bf22020-02-12 18:52:34 +0800104 mCallbacks.remove(callback);
hughchena73686f2018-11-12 17:42:08 +0800105 }
106
Fabian Kozynskie7316342020-03-16 13:31:06 -0400107 /**
108 * Creates a LocalMediaManager with references to given managers.
109 *
110 * It will obtain a {@link LocalBluetoothManager} by calling
111 * {@link LocalBluetoothManager#getInstance} and create an {@link InfoMediaManager} passing
112 * that bluetooth manager.
113 *
114 * It will use {@link BluetoothAdapter#getDefaultAdapter()] for setting the bluetooth adapter.
115 */
hughchena73686f2018-11-12 17:42:08 +0800116 public LocalMediaManager(Context context, String packageName, Notification notification) {
117 mContext = context;
hughchene6a4f482019-10-18 17:34:15 +0800118 mPackageName = packageName;
hughchena73686f2018-11-12 17:42:08 +0800119 mLocalBluetoothManager =
120 LocalBluetoothManager.getInstance(context, /* onInitCallback= */ null);
hughchenb3d68f62020-02-11 14:30:14 +0800121 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
hughchena73686f2018-11-12 17:42:08 +0800122 if (mLocalBluetoothManager == null) {
123 Log.e(TAG, "Bluetooth is not supported on this device");
124 return;
125 }
126
hughchen491b9fc2020-02-05 18:31:36 +0800127 mInfoMediaManager =
128 new InfoMediaManager(context, packageName, notification, mLocalBluetoothManager);
hughchena73686f2018-11-12 17:42:08 +0800129 }
130
Fabian Kozynskie7316342020-03-16 13:31:06 -0400131 /**
132 * Creates a LocalMediaManager with references to given managers.
133 *
134 * It will use {@link BluetoothAdapter#getDefaultAdapter()] for setting the bluetooth adapter.
135 */
Fabian Kozynskicc4c6c32020-03-09 15:11:33 -0400136 public LocalMediaManager(Context context, LocalBluetoothManager localBluetoothManager,
hughchendf7b8382020-02-03 17:48:49 +0800137 InfoMediaManager infoMediaManager, String packageName) {
hughchenbd0dd492019-01-08 14:34:10 +0800138 mContext = context;
139 mLocalBluetoothManager = localBluetoothManager;
hughchene6a4f482019-10-18 17:34:15 +0800140 mInfoMediaManager = infoMediaManager;
hughchendf7b8382020-02-03 17:48:49 +0800141 mPackageName = packageName;
Fabian Kozynskie7316342020-03-16 13:31:06 -0400142 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
hughchenbd0dd492019-01-08 14:34:10 +0800143 }
144
hughchena73686f2018-11-12 17:42:08 +0800145 /**
146 * Connect the MediaDevice to transfer media
147 * @param connectDevice the MediaDevice
148 */
149 public void connectDevice(MediaDevice connectDevice) {
hughchen5bd39682020-05-08 10:56:30 +0800150 MediaDevice device = null;
151 synchronized (mMediaDevicesLock) {
152 device = getMediaDeviceById(mMediaDevices, connectDevice.getId());
153 }
154 if (device == null) {
155 Log.w(TAG, "connectDevice() connectDevice not in the list!");
156 return;
157 }
hughchenc0c11632019-03-07 16:21:17 +0800158 if (device instanceof BluetoothMediaDevice) {
159 final CachedBluetoothDevice cachedDevice =
160 ((BluetoothMediaDevice) device).getCachedDevice();
161 if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) {
Tim Peng48c23672020-03-24 12:53:16 +0800162 mOnTransferBluetoothDevice = connectDevice;
Tim Peng92fb2192020-03-31 11:34:20 +0800163 device.setState(MediaDeviceState.STATE_CONNECTING);
hughchen4ae21862020-01-14 15:55:29 +0800164 cachedDevice.connect();
hughchenc0c11632019-03-07 16:21:17 +0800165 return;
166 }
167 }
168
hughchenbd0dd492019-01-08 14:34:10 +0800169 if (device == mCurrentConnectedDevice) {
170 Log.d(TAG, "connectDevice() this device all ready connected! : " + device.getName());
hughchena73686f2018-11-12 17:42:08 +0800171 return;
172 }
173
hughchene6a4f482019-10-18 17:34:15 +0800174 if (mCurrentConnectedDevice != null) {
hughchenbd0dd492019-01-08 14:34:10 +0800175 mCurrentConnectedDevice.disconnect();
hughchena73686f2018-11-12 17:42:08 +0800176 }
177
Tim Pengdbdfaef2020-03-11 11:30:01 +0800178 device.setState(MediaDeviceState.STATE_CONNECTING);
hughchendf7b8382020-02-03 17:48:49 +0800179 if (TextUtils.isEmpty(mPackageName)) {
hughchen063a35a2020-03-04 17:51:57 +0800180 mInfoMediaManager.connectDeviceWithoutPackageName(device);
hughchendf7b8382020-02-03 17:48:49 +0800181 } else {
hughchen063a35a2020-03-04 17:51:57 +0800182 device.connect();
hughchendf7b8382020-02-03 17:48:49 +0800183 }
hughchena73686f2018-11-12 17:42:08 +0800184 }
185
timhypeng97c6cf62018-12-27 15:40:32 +0800186 void dispatchSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state) {
hughchen8175bf22020-02-12 18:52:34 +0800187 for (DeviceCallback callback : getCallbacks()) {
188 callback.onSelectedDeviceStateChanged(device, state);
hughchena73686f2018-11-12 17:42:08 +0800189 }
190 }
191
192 /**
193 * Start scan connected MediaDevice
194 */
195 public void startScan() {
hughchen5bd39682020-05-08 10:56:30 +0800196 synchronized (mMediaDevicesLock) {
197 mMediaDevices.clear();
198 }
hughchen0d202042020-01-17 18:30:03 +0800199 mInfoMediaManager.registerCallback(mMediaDeviceCallback);
200 mInfoMediaManager.startScan();
hughchena73686f2018-11-12 17:42:08 +0800201 }
202
hughchena73686f2018-11-12 17:42:08 +0800203 void dispatchDeviceListUpdate() {
hughchen5bd39682020-05-08 10:56:30 +0800204 final List<MediaDevice> mediaDevices = new ArrayList<>(mMediaDevices);
205 Collections.sort(mediaDevices, COMPARATOR);
hughchen8175bf22020-02-12 18:52:34 +0800206 for (DeviceCallback callback : getCallbacks()) {
hughchen5bd39682020-05-08 10:56:30 +0800207 callback.onDeviceListUpdate(mediaDevices);
hughchena73686f2018-11-12 17:42:08 +0800208 }
209 }
210
hughchene572b832020-01-08 18:07:18 +0800211 void dispatchDeviceAttributesChanged() {
hughchen8175bf22020-02-12 18:52:34 +0800212 for (DeviceCallback callback : getCallbacks()) {
213 callback.onDeviceAttributesChanged();
hughchene572b832020-01-08 18:07:18 +0800214 }
215 }
216
hughchen063a35a2020-03-04 17:51:57 +0800217 void dispatchOnRequestFailed(int reason) {
218 for (DeviceCallback callback : getCallbacks()) {
219 callback.onRequestFailed(reason);
220 }
221 }
222
hughchena73686f2018-11-12 17:42:08 +0800223 /**
224 * Stop scan MediaDevice
225 */
226 public void stopScan() {
hughchen0d202042020-01-17 18:30:03 +0800227 mInfoMediaManager.unregisterCallback(mMediaDeviceCallback);
228 mInfoMediaManager.stopScan();
hughchen37282f02020-03-25 18:12:52 +0800229 unRegisterDeviceAttributeChangeCallback();
hughchena73686f2018-11-12 17:42:08 +0800230 }
231
hughchen04872632018-12-24 14:41:20 +0800232 /**
233 * Find the MediaDevice through id.
234 *
235 * @param devices the list of MediaDevice
236 * @param id the unique id of MediaDevice
237 * @return MediaDevice
238 */
239 public MediaDevice getMediaDeviceById(List<MediaDevice> devices, String id) {
240 for (MediaDevice mediaDevice : devices) {
hughchen4e6e5242020-04-06 11:42:33 +0800241 if (TextUtils.equals(mediaDevice.getId(), id)) {
hughchen04872632018-12-24 14:41:20 +0800242 return mediaDevice;
243 }
244 }
245 Log.i(TAG, "getMediaDeviceById() can't found device");
246 return null;
247 }
248
hughchenbd0dd492019-01-08 14:34:10 +0800249 /**
timhypengd37d70b2020-02-05 13:52:37 +0800250 * Find the MediaDevice from all media devices by id.
251 *
252 * @param id the unique id of MediaDevice
253 * @return MediaDevice
254 */
255 public MediaDevice getMediaDeviceById(String id) {
hughchen5bd39682020-05-08 10:56:30 +0800256 synchronized (mMediaDevicesLock) {
257 for (MediaDevice mediaDevice : mMediaDevices) {
258 if (TextUtils.equals(mediaDevice.getId(), id)) {
259 return mediaDevice;
260 }
timhypengd37d70b2020-02-05 13:52:37 +0800261 }
262 }
263 Log.i(TAG, "Unable to find device " + id);
264 return null;
265 }
266
267 /**
hughchenbd0dd492019-01-08 14:34:10 +0800268 * Find the current connected MediaDevice.
269 *
270 * @return MediaDevice
271 */
hughchen5bd39682020-05-08 10:56:30 +0800272 @Nullable
hughchenbd0dd492019-01-08 14:34:10 +0800273 public MediaDevice getCurrentConnectedDevice() {
274 return mCurrentConnectedDevice;
275 }
276
timhypenga812e882020-02-04 17:15:48 +0800277 /**
hughchen6ab62ca2020-02-12 10:40:04 +0800278 * Add a MediaDevice to let it play current media.
279 *
280 * @param device MediaDevice
281 * @return If add device successful return {@code true}, otherwise return {@code false}
282 */
283 public boolean addDeviceToPlayMedia(MediaDevice device) {
284 return mInfoMediaManager.addDeviceToPlayMedia(device);
285 }
286
287 /**
288 * Remove a {@code device} from current media.
289 *
290 * @param device MediaDevice
291 * @return If device stop successful return {@code true}, otherwise return {@code false}
292 */
293 public boolean removeDeviceFromPlayMedia(MediaDevice device) {
294 return mInfoMediaManager.removeDeviceFromPlayMedia(device);
295 }
296
297 /**
298 * Get the MediaDevice list that can be added to current media.
299 *
300 * @return list of MediaDevice
301 */
302 public List<MediaDevice> getSelectableMediaDevice() {
303 return mInfoMediaManager.getSelectableMediaDevice();
304 }
305
306 /**
hughchen61bced02020-02-20 18:46:32 +0800307 * Release session to stop playing media on MediaDevice.
308 */
309 public boolean releaseSession() {
310 return mInfoMediaManager.releaseSession();
311 }
312
313 /**
timhypeng6bbd5552020-02-17 14:53:45 +0800314 * Get the MediaDevice list that has been selected to current media.
315 *
316 * @return list of MediaDevice
317 */
318 public List<MediaDevice> getSelectedMediaDevice() {
319 return mInfoMediaManager.getSelectedMediaDevice();
320 }
321
322 /**
hughchen6ab62ca2020-02-12 10:40:04 +0800323 * Adjust the volume of session.
324 *
Tim Peng8a0f3612020-04-30 12:33:20 +0800325 * @param sessionId the value of media session id
326 * @param volume the value of volume
327 */
328 public void adjustSessionVolume(String sessionId, int volume) {
329 final List<RoutingSessionInfo> infos = getActiveMediaSession();
330 for (RoutingSessionInfo info : infos) {
331 if (TextUtils.equals(sessionId, info.getId())) {
332 mInfoMediaManager.adjustSessionVolume(info, volume);
333 return;
334 }
335 }
336 Log.w(TAG, "adjustSessionVolume: Unable to find session: " + sessionId);
337 }
338
339 /**
340 * Adjust the volume of session.
341 *
hughchen6ab62ca2020-02-12 10:40:04 +0800342 * @param volume the value of volume
343 */
344 public void adjustSessionVolume(int volume) {
345 mInfoMediaManager.adjustSessionVolume(volume);
346 }
347
348 /**
349 * Gets the maximum volume of the {@link android.media.RoutingSessionInfo}.
350 *
351 * @return maximum volume of the session, and return -1 if not found.
352 */
353 public int getSessionVolumeMax() {
354 return mInfoMediaManager.getSessionVolumeMax();
355 }
356
357 /**
358 * Gets the current volume of the {@link android.media.RoutingSessionInfo}.
359 *
360 * @return current volume of the session, and return -1 if not found.
361 */
362 public int getSessionVolume() {
363 return mInfoMediaManager.getSessionVolume();
364 }
365
Tim Peng635b04a2020-03-05 13:08:02 +0800366 /**
367 * Gets the user-visible name of the {@link android.media.RoutingSessionInfo}.
368 *
369 * @return current name of the session, and return {@code null} if not found.
370 */
371 public CharSequence getSessionName() {
372 return mInfoMediaManager.getSessionName();
373 }
374
Tim Peng8a0f3612020-04-30 12:33:20 +0800375 /**
376 * Gets the current active session.
377 *
378 * @return current active session list{@link android.media.RoutingSessionInfo}
379 */
380 public List<RoutingSessionInfo> getActiveMediaSession() {
381 return mInfoMediaManager.getActiveMediaSession();
382 }
383
hughchenbd0dd492019-01-08 14:34:10 +0800384 private MediaDevice updateCurrentConnectedDevice() {
hughchen5bd39682020-05-08 10:56:30 +0800385 synchronized (mMediaDevicesLock) {
386 for (MediaDevice device : mMediaDevices) {
387 if (device instanceof BluetoothMediaDevice) {
388 if (isActiveDevice(((BluetoothMediaDevice) device).getCachedDevice())) {
389 return device;
390 }
391 } else if (device instanceof PhoneMediaDevice) {
hughchenbd0dd492019-01-08 14:34:10 +0800392 return device;
393 }
394 }
395 }
hughchen5bd39682020-05-08 10:56:30 +0800396 Log.w(TAG, "updateCurrentConnectedDevice() can't found current connected device");
397 return null;
hughchenbd0dd492019-01-08 14:34:10 +0800398 }
399
hughchen063a35a2020-03-04 17:51:57 +0800400 private boolean isActiveDevice(CachedBluetoothDevice device) {
hughchenbd0dd492019-01-08 14:34:10 +0800401 return device.isActiveDevice(BluetoothProfile.A2DP)
402 || device.isActiveDevice(BluetoothProfile.HEARING_AID);
403 }
404
hughchen8175bf22020-02-12 18:52:34 +0800405 private Collection<DeviceCallback> getCallbacks() {
406 return new CopyOnWriteArrayList<>(mCallbacks);
407 }
408
hughchena73686f2018-11-12 17:42:08 +0800409 class MediaDeviceCallback implements MediaManager.MediaDeviceCallback {
410 @Override
411 public void onDeviceAdded(MediaDevice device) {
hughchen5bd39682020-05-08 10:56:30 +0800412 boolean isAdded = false;
413 synchronized (mMediaDevicesLock) {
414 if (!mMediaDevices.contains(device)) {
415 mMediaDevices.add(device);
416 isAdded = true;
417 }
418 }
419
420 if (isAdded) {
hughchena73686f2018-11-12 17:42:08 +0800421 dispatchDeviceListUpdate();
422 }
423 }
424
425 @Override
426 public void onDeviceListAdded(List<MediaDevice> devices) {
hughchen5bd39682020-05-08 10:56:30 +0800427 synchronized (mMediaDevicesLock) {
428 mMediaDevices.clear();
429 mMediaDevices.addAll(devices);
430 mMediaDevices.addAll(buildDisconnectedBluetoothDevice());
431 }
hughchenb3d68f62020-02-11 14:30:14 +0800432
hughchene6a4f482019-10-18 17:34:15 +0800433 final MediaDevice infoMediaDevice = mInfoMediaManager.getCurrentConnectedDevice();
434 mCurrentConnectedDevice = infoMediaDevice != null
435 ? infoMediaDevice : updateCurrentConnectedDevice();
hughchena73686f2018-11-12 17:42:08 +0800436 dispatchDeviceListUpdate();
Tim Peng48c23672020-03-24 12:53:16 +0800437 if (mOnTransferBluetoothDevice != null && mOnTransferBluetoothDevice.isConnected()) {
438 connectDevice(mOnTransferBluetoothDevice);
Tim Peng92fb2192020-03-31 11:34:20 +0800439 mOnTransferBluetoothDevice.setState(MediaDeviceState.STATE_CONNECTED);
Tim Peng48c23672020-03-24 12:53:16 +0800440 mOnTransferBluetoothDevice = null;
441 }
hughchena73686f2018-11-12 17:42:08 +0800442 }
443
hughchenb3d68f62020-02-11 14:30:14 +0800444 private List<MediaDevice> buildDisconnectedBluetoothDevice() {
hughchenbe514002020-04-14 11:50:55 +0800445 if (mBluetoothAdapter == null) {
446 Log.w(TAG, "buildDisconnectedBluetoothDevice() BluetoothAdapter is null");
447 return new ArrayList<>();
448 }
449
hughchenb3d68f62020-02-11 14:30:14 +0800450 final List<BluetoothDevice> bluetoothDevices =
451 mBluetoothAdapter.getMostRecentlyConnectedDevices();
452 final CachedBluetoothDeviceManager cachedDeviceManager =
453 mLocalBluetoothManager.getCachedDeviceManager();
454
hughchen37282f02020-03-25 18:12:52 +0800455 final List<CachedBluetoothDevice> cachedBluetoothDeviceList = new ArrayList<>();
hughchen11264e22020-03-30 14:11:18 +0800456 int deviceCount = 0;
hughchenb3d68f62020-02-11 14:30:14 +0800457 for (BluetoothDevice device : bluetoothDevices) {
458 final CachedBluetoothDevice cachedDevice =
459 cachedDeviceManager.findDevice(device);
460 if (cachedDevice != null) {
461 if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
hughchen81efaa52020-05-06 15:08:29 +0800462 && !cachedDevice.isConnected()
463 && isA2dpOrHearingAidDevice(cachedDevice)) {
hughchen11264e22020-03-30 14:11:18 +0800464 deviceCount++;
hughchen37282f02020-03-25 18:12:52 +0800465 cachedBluetoothDeviceList.add(cachedDevice);
hughchen11264e22020-03-30 14:11:18 +0800466 if (deviceCount >= MAX_DISCONNECTED_DEVICE_NUM) {
467 break;
468 }
hughchenb3d68f62020-02-11 14:30:14 +0800469 }
470 }
471 }
hughchen37282f02020-03-25 18:12:52 +0800472
473 unRegisterDeviceAttributeChangeCallback();
474 mDisconnectedMediaDevices.clear();
475 for (CachedBluetoothDevice cachedDevice : cachedBluetoothDeviceList) {
476 final MediaDevice mediaDevice = new BluetoothMediaDevice(mContext,
477 cachedDevice,
478 null, null, mPackageName);
479 if (!mMediaDevices.contains(mediaDevice)) {
480 cachedDevice.registerCallback(mDeviceAttributeChangeCallback);
481 mDisconnectedMediaDevices.add(mediaDevice);
482 }
483 }
hughchenb3d68f62020-02-11 14:30:14 +0800484 return new ArrayList<>(mDisconnectedMediaDevices);
485 }
486
hughchen81efaa52020-05-06 15:08:29 +0800487 private boolean isA2dpOrHearingAidDevice(CachedBluetoothDevice device) {
488 for (LocalBluetoothProfile profile : device.getConnectableProfiles()) {
489 if (profile instanceof A2dpProfile || profile instanceof HearingAidProfile) {
490 return true;
491 }
492 }
493 return false;
494 }
495
hughchena73686f2018-11-12 17:42:08 +0800496 @Override
497 public void onDeviceRemoved(MediaDevice device) {
hughchen5bd39682020-05-08 10:56:30 +0800498 boolean isRemoved = false;
499 synchronized (mMediaDevicesLock) {
500 if (mMediaDevices.contains(device)) {
501 mMediaDevices.remove(device);
502 isRemoved = true;
503 }
504 }
505 if (isRemoved) {
hughchena73686f2018-11-12 17:42:08 +0800506 dispatchDeviceListUpdate();
507 }
508 }
509
510 @Override
511 public void onDeviceListRemoved(List<MediaDevice> devices) {
hughchen5bd39682020-05-08 10:56:30 +0800512 synchronized (mMediaDevicesLock) {
513 mMediaDevices.removeAll(devices);
514 }
hughchena73686f2018-11-12 17:42:08 +0800515 dispatchDeviceListUpdate();
516 }
517
518 @Override
hughchenbd0dd492019-01-08 14:34:10 +0800519 public void onConnectedDeviceChanged(String id) {
hughchen5bd39682020-05-08 10:56:30 +0800520 MediaDevice connectDevice = null;
521 synchronized (mMediaDevicesLock) {
522 connectDevice = getMediaDeviceById(mMediaDevices, id);
523 }
hughchen063a35a2020-03-04 17:51:57 +0800524 connectDevice = connectDevice != null
525 ? connectDevice : updateCurrentConnectedDevice();
hughchen97104352020-05-04 13:51:06 +0800526
hughchenbd0dd492019-01-08 14:34:10 +0800527 mCurrentConnectedDevice = connectDevice;
hughchen5bd39682020-05-08 10:56:30 +0800528 if (connectDevice != null) {
529 connectDevice.setState(MediaDeviceState.STATE_CONNECTED);
530
531 dispatchSelectedDeviceStateChanged(mCurrentConnectedDevice,
532 MediaDeviceState.STATE_CONNECTED);
533 }
hughchenc0c11632019-03-07 16:21:17 +0800534 }
hughchenbd0dd492019-01-08 14:34:10 +0800535
hughchenc0c11632019-03-07 16:21:17 +0800536 @Override
537 public void onDeviceAttributesChanged() {
hughchene572b832020-01-08 18:07:18 +0800538 dispatchDeviceAttributesChanged();
hughchenbd0dd492019-01-08 14:34:10 +0800539 }
hughchen063a35a2020-03-04 17:51:57 +0800540
541 @Override
542 public void onRequestFailed(int reason) {
hughchen5bd39682020-05-08 10:56:30 +0800543 synchronized (mMediaDevicesLock) {
544 for (MediaDevice device : mMediaDevices) {
545 if (device.getState() == MediaDeviceState.STATE_CONNECTING) {
546 device.setState(MediaDeviceState.STATE_CONNECTING_FAILED);
547 }
Tim Pengdbdfaef2020-03-11 11:30:01 +0800548 }
549 }
hughchen063a35a2020-03-04 17:51:57 +0800550 dispatchOnRequestFailed(reason);
551 }
hughchena73686f2018-11-12 17:42:08 +0800552 }
553
hughchen37282f02020-03-25 18:12:52 +0800554 private void unRegisterDeviceAttributeChangeCallback() {
555 for (MediaDevice device : mDisconnectedMediaDevices) {
556 ((BluetoothMediaDevice) device).getCachedDevice()
557 .unregisterCallback(mDeviceAttributeChangeCallback);
558 }
559 }
hughchena73686f2018-11-12 17:42:08 +0800560
561 /**
562 * Callback for notifying device information updating
563 */
564 public interface DeviceCallback {
565 /**
566 * Callback for notifying device list updated.
567 *
568 * @param devices MediaDevice list
569 */
hughchene572b832020-01-08 18:07:18 +0800570 default void onDeviceListUpdate(List<MediaDevice> devices) {};
hughchena73686f2018-11-12 17:42:08 +0800571
572 /**
573 * Callback for notifying the connected device is changed.
574 *
575 * @param device the changed connected MediaDevice
576 * @param state the current MediaDevice state, the possible values are:
577 * {@link MediaDeviceState#STATE_CONNECTED},
578 * {@link MediaDeviceState#STATE_CONNECTING},
579 * {@link MediaDeviceState#STATE_DISCONNECTED}
580 */
hughchene572b832020-01-08 18:07:18 +0800581 default void onSelectedDeviceStateChanged(MediaDevice device,
582 @MediaDeviceState int state) {};
583
584 /**
585 * Callback for notifying the device attributes is changed.
586 */
587 default void onDeviceAttributesChanged() {};
hughchen063a35a2020-03-04 17:51:57 +0800588
589 /**
590 * Callback for notifying that transferring is failed.
591 *
592 * @param reason the reason that the request has failed. Can be one of followings:
593 * {@link android.media.MediaRoute2ProviderService#REASON_UNKNOWN_ERROR},
594 * {@link android.media.MediaRoute2ProviderService#REASON_REJECTED},
595 * {@link android.media.MediaRoute2ProviderService#REASON_NETWORK_ERROR},
596 * {@link android.media.MediaRoute2ProviderService#REASON_ROUTE_NOT_AVAILABLE},
597 * {@link android.media.MediaRoute2ProviderService#REASON_INVALID_COMMAND},
598 */
599 default void onRequestFailed(int reason){};
hughchena73686f2018-11-12 17:42:08 +0800600 }
hughchenb3d68f62020-02-11 14:30:14 +0800601
602 /**
603 * This callback is for update {@link BluetoothMediaDevice} summary when
604 * {@link CachedBluetoothDevice} connection state is changed.
605 */
606 @VisibleForTesting
607 class DeviceAttributeChangeCallback implements CachedBluetoothDevice.Callback {
608
609 @Override
610 public void onDeviceAttributesChanged() {
Tim Peng92fb2192020-03-31 11:34:20 +0800611 if (mOnTransferBluetoothDevice != null
612 && !((BluetoothMediaDevice) mOnTransferBluetoothDevice).getCachedDevice()
613 .isBusy()
614 && !mOnTransferBluetoothDevice.isConnected()) {
615 // Failed to connect
616 mOnTransferBluetoothDevice.setState(MediaDeviceState.STATE_DISCONNECTED);
617 mOnTransferBluetoothDevice = null;
618 }
hughchenb3d68f62020-02-11 14:30:14 +0800619 dispatchDeviceAttributesChanged();
620 }
621 }
hughchena73686f2018-11-12 17:42:08 +0800622}