1 /* 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 */ 16 package com.android.settingslib.media; 17 18 import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR; 19 20 import android.app.Notification; 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.BluetoothDevice; 23 import android.content.Context; 24 import android.media.RoutingSessionInfo; 25 import android.os.Build; 26 import android.text.TextUtils; 27 import android.util.Log; 28 29 import androidx.annotation.IntDef; 30 import androidx.annotation.Nullable; 31 import androidx.annotation.RequiresApi; 32 33 import com.android.internal.annotations.VisibleForTesting; 34 import com.android.settingslib.bluetooth.A2dpProfile; 35 import com.android.settingslib.bluetooth.BluetoothCallback; 36 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 37 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; 38 import com.android.settingslib.bluetooth.HearingAidProfile; 39 import com.android.settingslib.bluetooth.LocalBluetoothManager; 40 import com.android.settingslib.bluetooth.LocalBluetoothProfile; 41 42 import java.lang.annotation.Retention; 43 import java.lang.annotation.RetentionPolicy; 44 import java.util.ArrayList; 45 import java.util.Collection; 46 import java.util.Collections; 47 import java.util.Comparator; 48 import java.util.List; 49 import java.util.concurrent.CopyOnWriteArrayList; 50 51 /** 52 * LocalMediaManager provide interface to get MediaDevice list and transfer media to MediaDevice. 53 */ 54 @RequiresApi(Build.VERSION_CODES.R) 55 public class LocalMediaManager implements BluetoothCallback { 56 private static final Comparator<MediaDevice> COMPARATOR = Comparator.naturalOrder(); 57 private static final String TAG = "LocalMediaManager"; 58 private static final int MAX_DISCONNECTED_DEVICE_NUM = 5; 59 60 @Retention(RetentionPolicy.SOURCE) 61 @IntDef({MediaDeviceState.STATE_CONNECTED, 62 MediaDeviceState.STATE_CONNECTING, 63 MediaDeviceState.STATE_DISCONNECTED, 64 MediaDeviceState.STATE_CONNECTING_FAILED}) 65 public @interface MediaDeviceState { 66 int STATE_CONNECTED = 0; 67 int STATE_CONNECTING = 1; 68 int STATE_DISCONNECTED = 2; 69 int STATE_CONNECTING_FAILED = 3; 70 } 71 72 private final Collection<DeviceCallback> mCallbacks = new CopyOnWriteArrayList<>(); 73 private final Object mMediaDevicesLock = new Object(); 74 @VisibleForTesting 75 final MediaDeviceCallback mMediaDeviceCallback = new MediaDeviceCallback(); 76 77 private Context mContext; 78 private LocalBluetoothManager mLocalBluetoothManager; 79 private InfoMediaManager mInfoMediaManager; 80 private String mPackageName; 81 private MediaDevice mOnTransferBluetoothDevice; 82 83 @VisibleForTesting 84 List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>(); 85 @VisibleForTesting 86 List<MediaDevice> mDisconnectedMediaDevices = new CopyOnWriteArrayList<>(); 87 @VisibleForTesting 88 MediaDevice mPhoneDevice; 89 @VisibleForTesting 90 MediaDevice mCurrentConnectedDevice; 91 @VisibleForTesting 92 DeviceAttributeChangeCallback mDeviceAttributeChangeCallback = 93 new DeviceAttributeChangeCallback(); 94 @VisibleForTesting 95 BluetoothAdapter mBluetoothAdapter; 96 97 /** 98 * Register to start receiving callbacks for MediaDevice events. 99 */ registerCallback(DeviceCallback callback)100 public void registerCallback(DeviceCallback callback) { 101 mCallbacks.add(callback); 102 } 103 104 /** 105 * Unregister to stop receiving callbacks for MediaDevice events 106 */ unregisterCallback(DeviceCallback callback)107 public void unregisterCallback(DeviceCallback callback) { 108 mCallbacks.remove(callback); 109 } 110 111 /** 112 * Creates a LocalMediaManager with references to given managers. 113 * 114 * It will obtain a {@link LocalBluetoothManager} by calling 115 * {@link LocalBluetoothManager#getInstance} and create an {@link InfoMediaManager} passing 116 * that bluetooth manager. 117 * 118 * It will use {@link BluetoothAdapter#getDefaultAdapter()] for setting the bluetooth adapter. 119 */ LocalMediaManager(Context context, String packageName, Notification notification)120 public LocalMediaManager(Context context, String packageName, Notification notification) { 121 mContext = context; 122 mPackageName = packageName; 123 mLocalBluetoothManager = 124 LocalBluetoothManager.getInstance(context, /* onInitCallback= */ null); 125 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 126 if (mLocalBluetoothManager == null) { 127 Log.e(TAG, "Bluetooth is not supported on this device"); 128 return; 129 } 130 131 mInfoMediaManager = 132 new InfoMediaManager(context, packageName, notification, mLocalBluetoothManager); 133 } 134 135 /** 136 * Creates a LocalMediaManager with references to given managers. 137 * 138 * It will use {@link BluetoothAdapter#getDefaultAdapter()] for setting the bluetooth adapter. 139 */ LocalMediaManager(Context context, LocalBluetoothManager localBluetoothManager, InfoMediaManager infoMediaManager, String packageName)140 public LocalMediaManager(Context context, LocalBluetoothManager localBluetoothManager, 141 InfoMediaManager infoMediaManager, String packageName) { 142 mContext = context; 143 mLocalBluetoothManager = localBluetoothManager; 144 mInfoMediaManager = infoMediaManager; 145 mPackageName = packageName; 146 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 147 } 148 149 /** 150 * Connect the MediaDevice to transfer media 151 * @param connectDevice the MediaDevice 152 * @return {@code true} if successfully call, otherwise return {@code false} 153 */ connectDevice(MediaDevice connectDevice)154 public boolean connectDevice(MediaDevice connectDevice) { 155 MediaDevice device = null; 156 synchronized (mMediaDevicesLock) { 157 device = getMediaDeviceById(mMediaDevices, connectDevice.getId()); 158 } 159 if (device == null) { 160 Log.w(TAG, "connectDevice() connectDevice not in the list!"); 161 return false; 162 } 163 if (device instanceof BluetoothMediaDevice) { 164 final CachedBluetoothDevice cachedDevice = 165 ((BluetoothMediaDevice) device).getCachedDevice(); 166 if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) { 167 mOnTransferBluetoothDevice = connectDevice; 168 device.setState(MediaDeviceState.STATE_CONNECTING); 169 cachedDevice.connect(); 170 return true; 171 } 172 } 173 174 if (device == mCurrentConnectedDevice) { 175 Log.d(TAG, "connectDevice() this device all ready connected! : " + device.getName()); 176 return false; 177 } 178 179 if (mCurrentConnectedDevice != null) { 180 mCurrentConnectedDevice.disconnect(); 181 } 182 183 device.setState(MediaDeviceState.STATE_CONNECTING); 184 if (TextUtils.isEmpty(mPackageName)) { 185 mInfoMediaManager.connectDeviceWithoutPackageName(device); 186 } else { 187 device.connect(); 188 } 189 return true; 190 } 191 dispatchSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state)192 void dispatchSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state) { 193 for (DeviceCallback callback : getCallbacks()) { 194 callback.onSelectedDeviceStateChanged(device, state); 195 } 196 } 197 198 /** 199 * Start scan connected MediaDevice 200 */ startScan()201 public void startScan() { 202 synchronized (mMediaDevicesLock) { 203 mMediaDevices.clear(); 204 } 205 mInfoMediaManager.registerCallback(mMediaDeviceCallback); 206 mInfoMediaManager.startScan(); 207 } 208 dispatchDeviceListUpdate()209 void dispatchDeviceListUpdate() { 210 final List<MediaDevice> mediaDevices = new ArrayList<>(mMediaDevices); 211 for (DeviceCallback callback : getCallbacks()) { 212 callback.onDeviceListUpdate(mediaDevices); 213 } 214 } 215 dispatchDeviceAttributesChanged()216 void dispatchDeviceAttributesChanged() { 217 for (DeviceCallback callback : getCallbacks()) { 218 callback.onDeviceAttributesChanged(); 219 } 220 } 221 dispatchOnRequestFailed(int reason)222 void dispatchOnRequestFailed(int reason) { 223 for (DeviceCallback callback : getCallbacks()) { 224 callback.onRequestFailed(reason); 225 } 226 } 227 228 /** 229 * Stop scan MediaDevice 230 */ stopScan()231 public void stopScan() { 232 mInfoMediaManager.unregisterCallback(mMediaDeviceCallback); 233 mInfoMediaManager.stopScan(); 234 unRegisterDeviceAttributeChangeCallback(); 235 } 236 237 /** 238 * Find the MediaDevice through id. 239 * 240 * @param devices the list of MediaDevice 241 * @param id the unique id of MediaDevice 242 * @return MediaDevice 243 */ getMediaDeviceById(List<MediaDevice> devices, String id)244 public MediaDevice getMediaDeviceById(List<MediaDevice> devices, String id) { 245 for (MediaDevice mediaDevice : devices) { 246 if (TextUtils.equals(mediaDevice.getId(), id)) { 247 return mediaDevice; 248 } 249 } 250 Log.i(TAG, "getMediaDeviceById() can't found device"); 251 return null; 252 } 253 254 /** 255 * Find the MediaDevice from all media devices by id. 256 * 257 * @param id the unique id of MediaDevice 258 * @return MediaDevice 259 */ getMediaDeviceById(String id)260 public MediaDevice getMediaDeviceById(String id) { 261 synchronized (mMediaDevicesLock) { 262 for (MediaDevice mediaDevice : mMediaDevices) { 263 if (TextUtils.equals(mediaDevice.getId(), id)) { 264 return mediaDevice; 265 } 266 } 267 } 268 Log.i(TAG, "Unable to find device " + id); 269 return null; 270 } 271 272 /** 273 * Find the current connected MediaDevice. 274 * 275 * @return MediaDevice 276 */ 277 @Nullable getCurrentConnectedDevice()278 public MediaDevice getCurrentConnectedDevice() { 279 return mCurrentConnectedDevice; 280 } 281 282 /** 283 * Add a MediaDevice to let it play current media. 284 * 285 * @param device MediaDevice 286 * @return If add device successful return {@code true}, otherwise return {@code false} 287 */ addDeviceToPlayMedia(MediaDevice device)288 public boolean addDeviceToPlayMedia(MediaDevice device) { 289 return mInfoMediaManager.addDeviceToPlayMedia(device); 290 } 291 292 /** 293 * Remove a {@code device} from current media. 294 * 295 * @param device MediaDevice 296 * @return If device stop successful return {@code true}, otherwise return {@code false} 297 */ removeDeviceFromPlayMedia(MediaDevice device)298 public boolean removeDeviceFromPlayMedia(MediaDevice device) { 299 return mInfoMediaManager.removeDeviceFromPlayMedia(device); 300 } 301 302 /** 303 * Get the MediaDevice list that can be added to current media. 304 * 305 * @return list of MediaDevice 306 */ getSelectableMediaDevice()307 public List<MediaDevice> getSelectableMediaDevice() { 308 return mInfoMediaManager.getSelectableMediaDevice(); 309 } 310 311 /** 312 * Get the MediaDevice list that can be removed from current media session. 313 * 314 * @return list of MediaDevice 315 */ getDeselectableMediaDevice()316 public List<MediaDevice> getDeselectableMediaDevice() { 317 return mInfoMediaManager.getDeselectableMediaDevice(); 318 } 319 320 /** 321 * Release session to stop playing media on MediaDevice. 322 */ releaseSession()323 public boolean releaseSession() { 324 return mInfoMediaManager.releaseSession(); 325 } 326 327 /** 328 * Get the MediaDevice list that has been selected to current media. 329 * 330 * @return list of MediaDevice 331 */ getSelectedMediaDevice()332 public List<MediaDevice> getSelectedMediaDevice() { 333 return mInfoMediaManager.getSelectedMediaDevice(); 334 } 335 336 /** 337 * Adjust the volume of session. 338 * 339 * @param sessionId the value of media session id 340 * @param volume the value of volume 341 */ adjustSessionVolume(String sessionId, int volume)342 public void adjustSessionVolume(String sessionId, int volume) { 343 final List<RoutingSessionInfo> infos = getActiveMediaSession(); 344 for (RoutingSessionInfo info : infos) { 345 if (TextUtils.equals(sessionId, info.getId())) { 346 mInfoMediaManager.adjustSessionVolume(info, volume); 347 return; 348 } 349 } 350 Log.w(TAG, "adjustSessionVolume: Unable to find session: " + sessionId); 351 } 352 353 /** 354 * Adjust the volume of session. 355 * 356 * @param volume the value of volume 357 */ adjustSessionVolume(int volume)358 public void adjustSessionVolume(int volume) { 359 mInfoMediaManager.adjustSessionVolume(volume); 360 } 361 362 /** 363 * Gets the maximum volume of the {@link android.media.RoutingSessionInfo}. 364 * 365 * @return maximum volume of the session, and return -1 if not found. 366 */ getSessionVolumeMax()367 public int getSessionVolumeMax() { 368 return mInfoMediaManager.getSessionVolumeMax(); 369 } 370 371 /** 372 * Gets the current volume of the {@link android.media.RoutingSessionInfo}. 373 * 374 * @return current volume of the session, and return -1 if not found. 375 */ getSessionVolume()376 public int getSessionVolume() { 377 return mInfoMediaManager.getSessionVolume(); 378 } 379 380 /** 381 * Gets the user-visible name of the {@link android.media.RoutingSessionInfo}. 382 * 383 * @return current name of the session, and return {@code null} if not found. 384 */ getSessionName()385 public CharSequence getSessionName() { 386 return mInfoMediaManager.getSessionName(); 387 } 388 389 /** 390 * Gets the current active session. 391 * 392 * @return current active session list{@link android.media.RoutingSessionInfo} 393 */ getActiveMediaSession()394 public List<RoutingSessionInfo> getActiveMediaSession() { 395 return mInfoMediaManager.getActiveMediaSession(); 396 } 397 398 /** 399 * Gets the current package name. 400 * 401 * @return current package name 402 */ getPackageName()403 public String getPackageName() { 404 return mPackageName; 405 } 406 407 /** 408 * Returns {@code true} if needed to disable media output, otherwise returns {@code false}. 409 */ shouldDisableMediaOutput(String packageName)410 public boolean shouldDisableMediaOutput(String packageName) { 411 return mInfoMediaManager.shouldDisableMediaOutput(packageName); 412 } 413 414 /** 415 * Returns {@code true} if needed to enable volume seekbar, otherwise returns {@code false}. 416 */ shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo)417 public boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) { 418 return mInfoMediaManager.shouldEnableVolumeSeekBar(sessionInfo); 419 } 420 421 @VisibleForTesting updateCurrentConnectedDevice()422 MediaDevice updateCurrentConnectedDevice() { 423 MediaDevice connectedDevice = null; 424 synchronized (mMediaDevicesLock) { 425 for (MediaDevice device : mMediaDevices) { 426 if (device instanceof BluetoothMediaDevice) { 427 if (isActiveDevice(((BluetoothMediaDevice) device).getCachedDevice()) 428 && device.isConnected()) { 429 return device; 430 } 431 } else if (device instanceof PhoneMediaDevice) { 432 connectedDevice = device; 433 } 434 } 435 } 436 437 return connectedDevice; 438 } 439 isActiveDevice(CachedBluetoothDevice device)440 private boolean isActiveDevice(CachedBluetoothDevice device) { 441 boolean isActiveDeviceA2dp = false; 442 boolean isActiveDeviceHearingAid = false; 443 final A2dpProfile a2dpProfile = mLocalBluetoothManager.getProfileManager().getA2dpProfile(); 444 if (a2dpProfile != null) { 445 isActiveDeviceA2dp = device.getDevice().equals(a2dpProfile.getActiveDevice()); 446 } 447 if (!isActiveDeviceA2dp) { 448 final HearingAidProfile hearingAidProfile = mLocalBluetoothManager.getProfileManager() 449 .getHearingAidProfile(); 450 if (hearingAidProfile != null) { 451 isActiveDeviceHearingAid = 452 hearingAidProfile.getActiveDevices().contains(device.getDevice()); 453 } 454 } 455 456 return isActiveDeviceA2dp || isActiveDeviceHearingAid; 457 } 458 getCallbacks()459 private Collection<DeviceCallback> getCallbacks() { 460 return new CopyOnWriteArrayList<>(mCallbacks); 461 } 462 463 class MediaDeviceCallback implements MediaManager.MediaDeviceCallback { 464 @Override onDeviceAdded(MediaDevice device)465 public void onDeviceAdded(MediaDevice device) { 466 boolean isAdded = false; 467 synchronized (mMediaDevicesLock) { 468 if (!mMediaDevices.contains(device)) { 469 mMediaDevices.add(device); 470 isAdded = true; 471 } 472 } 473 474 if (isAdded) { 475 dispatchDeviceListUpdate(); 476 } 477 } 478 479 @Override onDeviceListAdded(List<MediaDevice> devices)480 public void onDeviceListAdded(List<MediaDevice> devices) { 481 synchronized (mMediaDevicesLock) { 482 Collections.sort(devices, COMPARATOR); 483 mMediaDevices.clear(); 484 mMediaDevices.addAll(devices); 485 // Add disconnected bluetooth devices only when phone output device is available. 486 for (MediaDevice device : devices) { 487 final int type = device.getDeviceType(); 488 if (type == MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE 489 || type == MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE 490 || type == MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE) { 491 mMediaDevices.addAll(buildDisconnectedBluetoothDevice()); 492 break; 493 } 494 } 495 } 496 497 final MediaDevice infoMediaDevice = mInfoMediaManager.getCurrentConnectedDevice(); 498 mCurrentConnectedDevice = infoMediaDevice != null 499 ? infoMediaDevice : updateCurrentConnectedDevice(); 500 dispatchDeviceListUpdate(); 501 if (mOnTransferBluetoothDevice != null && mOnTransferBluetoothDevice.isConnected()) { 502 connectDevice(mOnTransferBluetoothDevice); 503 mOnTransferBluetoothDevice.setState(MediaDeviceState.STATE_CONNECTED); 504 dispatchSelectedDeviceStateChanged(mOnTransferBluetoothDevice, 505 MediaDeviceState.STATE_CONNECTED); 506 mOnTransferBluetoothDevice = null; 507 } 508 } 509 buildDisconnectedBluetoothDevice()510 private List<MediaDevice> buildDisconnectedBluetoothDevice() { 511 if (mBluetoothAdapter == null) { 512 Log.w(TAG, "buildDisconnectedBluetoothDevice() BluetoothAdapter is null"); 513 return new ArrayList<>(); 514 } 515 516 final List<BluetoothDevice> bluetoothDevices = 517 mBluetoothAdapter.getMostRecentlyConnectedDevices(); 518 final CachedBluetoothDeviceManager cachedDeviceManager = 519 mLocalBluetoothManager.getCachedDeviceManager(); 520 521 final List<CachedBluetoothDevice> cachedBluetoothDeviceList = new ArrayList<>(); 522 int deviceCount = 0; 523 for (BluetoothDevice device : bluetoothDevices) { 524 final CachedBluetoothDevice cachedDevice = 525 cachedDeviceManager.findDevice(device); 526 if (cachedDevice != null) { 527 if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED 528 && !cachedDevice.isConnected() 529 && isA2dpOrHearingAidDevice(cachedDevice)) { 530 deviceCount++; 531 cachedBluetoothDeviceList.add(cachedDevice); 532 if (deviceCount >= MAX_DISCONNECTED_DEVICE_NUM) { 533 break; 534 } 535 } 536 } 537 } 538 539 unRegisterDeviceAttributeChangeCallback(); 540 mDisconnectedMediaDevices.clear(); 541 for (CachedBluetoothDevice cachedDevice : cachedBluetoothDeviceList) { 542 final MediaDevice mediaDevice = new BluetoothMediaDevice(mContext, 543 cachedDevice, 544 null, null, mPackageName); 545 if (!mMediaDevices.contains(mediaDevice)) { 546 cachedDevice.registerCallback(mDeviceAttributeChangeCallback); 547 mDisconnectedMediaDevices.add(mediaDevice); 548 } 549 } 550 return new ArrayList<>(mDisconnectedMediaDevices); 551 } 552 isA2dpOrHearingAidDevice(CachedBluetoothDevice device)553 private boolean isA2dpOrHearingAidDevice(CachedBluetoothDevice device) { 554 for (LocalBluetoothProfile profile : device.getConnectableProfiles()) { 555 if (profile instanceof A2dpProfile || profile instanceof HearingAidProfile) { 556 return true; 557 } 558 } 559 return false; 560 } 561 562 @Override onDeviceRemoved(MediaDevice device)563 public void onDeviceRemoved(MediaDevice device) { 564 boolean isRemoved = false; 565 synchronized (mMediaDevicesLock) { 566 if (mMediaDevices.contains(device)) { 567 mMediaDevices.remove(device); 568 isRemoved = true; 569 } 570 } 571 if (isRemoved) { 572 dispatchDeviceListUpdate(); 573 } 574 } 575 576 @Override onDeviceListRemoved(List<MediaDevice> devices)577 public void onDeviceListRemoved(List<MediaDevice> devices) { 578 synchronized (mMediaDevicesLock) { 579 mMediaDevices.removeAll(devices); 580 } 581 dispatchDeviceListUpdate(); 582 } 583 584 @Override onConnectedDeviceChanged(String id)585 public void onConnectedDeviceChanged(String id) { 586 MediaDevice connectDevice = null; 587 synchronized (mMediaDevicesLock) { 588 connectDevice = getMediaDeviceById(mMediaDevices, id); 589 } 590 connectDevice = connectDevice != null 591 ? connectDevice : updateCurrentConnectedDevice(); 592 593 mCurrentConnectedDevice = connectDevice; 594 if (connectDevice != null) { 595 connectDevice.setState(MediaDeviceState.STATE_CONNECTED); 596 597 dispatchSelectedDeviceStateChanged(mCurrentConnectedDevice, 598 MediaDeviceState.STATE_CONNECTED); 599 } 600 } 601 602 @Override onDeviceAttributesChanged()603 public void onDeviceAttributesChanged() { 604 dispatchDeviceAttributesChanged(); 605 } 606 607 @Override onRequestFailed(int reason)608 public void onRequestFailed(int reason) { 609 synchronized (mMediaDevicesLock) { 610 for (MediaDevice device : mMediaDevices) { 611 if (device.getState() == MediaDeviceState.STATE_CONNECTING) { 612 device.setState(MediaDeviceState.STATE_CONNECTING_FAILED); 613 } 614 } 615 } 616 dispatchOnRequestFailed(reason); 617 } 618 } 619 unRegisterDeviceAttributeChangeCallback()620 private void unRegisterDeviceAttributeChangeCallback() { 621 for (MediaDevice device : mDisconnectedMediaDevices) { 622 ((BluetoothMediaDevice) device).getCachedDevice() 623 .unregisterCallback(mDeviceAttributeChangeCallback); 624 } 625 } 626 627 /** 628 * Callback for notifying device information updating 629 */ 630 public interface DeviceCallback { 631 /** 632 * Callback for notifying device list updated. 633 * 634 * @param devices MediaDevice list 635 */ onDeviceListUpdate(List<MediaDevice> devices)636 default void onDeviceListUpdate(List<MediaDevice> devices) {}; 637 638 /** 639 * Callback for notifying the connected device is changed. 640 * 641 * @param device the changed connected MediaDevice 642 * @param state the current MediaDevice state, the possible values are: 643 * {@link MediaDeviceState#STATE_CONNECTED}, 644 * {@link MediaDeviceState#STATE_CONNECTING}, 645 * {@link MediaDeviceState#STATE_DISCONNECTED} 646 */ onSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state)647 default void onSelectedDeviceStateChanged(MediaDevice device, 648 @MediaDeviceState int state) {}; 649 650 /** 651 * Callback for notifying the device attributes is changed. 652 */ onDeviceAttributesChanged()653 default void onDeviceAttributesChanged() {}; 654 655 /** 656 * Callback for notifying that transferring is failed. 657 * 658 * @param reason the reason that the request has failed. Can be one of followings: 659 * {@link android.media.MediaRoute2ProviderService#REASON_UNKNOWN_ERROR}, 660 * {@link android.media.MediaRoute2ProviderService#REASON_REJECTED}, 661 * {@link android.media.MediaRoute2ProviderService#REASON_NETWORK_ERROR}, 662 * {@link android.media.MediaRoute2ProviderService#REASON_ROUTE_NOT_AVAILABLE}, 663 * {@link android.media.MediaRoute2ProviderService#REASON_INVALID_COMMAND}, 664 */ onRequestFailed(int reason)665 default void onRequestFailed(int reason){}; 666 } 667 668 /** 669 * This callback is for update {@link BluetoothMediaDevice} summary when 670 * {@link CachedBluetoothDevice} connection state is changed. 671 */ 672 @VisibleForTesting 673 class DeviceAttributeChangeCallback implements CachedBluetoothDevice.Callback { 674 675 @Override onDeviceAttributesChanged()676 public void onDeviceAttributesChanged() { 677 if (mOnTransferBluetoothDevice != null 678 && !((BluetoothMediaDevice) mOnTransferBluetoothDevice).getCachedDevice() 679 .isBusy() 680 && !mOnTransferBluetoothDevice.isConnected()) { 681 // Failed to connect 682 mOnTransferBluetoothDevice.setState(MediaDeviceState.STATE_CONNECTING_FAILED); 683 mOnTransferBluetoothDevice = null; 684 dispatchOnRequestFailed(REASON_UNKNOWN_ERROR); 685 } 686 dispatchDeviceAttributesChanged(); 687 } 688 } 689 } 690