1 /* 2 * Copyright (C) 2020 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 17 package com.android.systemui.media.dialog; 18 19 import static android.media.RouteListingPreference.ACTION_TRANSFER_MEDIA; 20 import static android.media.RouteListingPreference.EXTRA_ROUTE_ID; 21 import static android.provider.Settings.ACTION_BLUETOOTH_SETTINGS; 22 23 import android.annotation.CallbackExecutor; 24 import android.app.AlertDialog; 25 import android.app.KeyguardManager; 26 import android.app.Notification; 27 import android.app.WallpaperColors; 28 import android.bluetooth.BluetoothDevice; 29 import android.bluetooth.BluetoothLeBroadcast; 30 import android.bluetooth.BluetoothLeBroadcastAssistant; 31 import android.bluetooth.BluetoothLeBroadcastMetadata; 32 import android.bluetooth.BluetoothLeBroadcastReceiveState; 33 import android.content.ComponentName; 34 import android.content.Context; 35 import android.content.DialogInterface; 36 import android.content.Intent; 37 import android.content.pm.ApplicationInfo; 38 import android.content.pm.PackageManager; 39 import android.graphics.Bitmap; 40 import android.graphics.PorterDuff; 41 import android.graphics.PorterDuffColorFilter; 42 import android.graphics.drawable.BitmapDrawable; 43 import android.graphics.drawable.Drawable; 44 import android.graphics.drawable.Icon; 45 import android.media.AudioManager; 46 import android.media.INearbyMediaDevicesUpdateCallback; 47 import android.media.MediaMetadata; 48 import android.media.MediaRoute2Info; 49 import android.media.NearbyDevice; 50 import android.media.RoutingSessionInfo; 51 import android.media.session.MediaController; 52 import android.media.session.MediaSession; 53 import android.media.session.MediaSessionManager; 54 import android.media.session.PlaybackState; 55 import android.os.IBinder; 56 import android.os.PowerExemptionManager; 57 import android.os.RemoteException; 58 import android.os.UserHandle; 59 import android.os.UserManager; 60 import android.provider.Settings; 61 import android.text.TextUtils; 62 import android.util.Log; 63 import android.view.View; 64 import android.view.WindowManager; 65 66 import androidx.annotation.NonNull; 67 import androidx.annotation.VisibleForTesting; 68 import androidx.core.graphics.drawable.IconCompat; 69 70 import com.android.settingslib.RestrictedLockUtilsInternal; 71 import com.android.settingslib.Utils; 72 import com.android.settingslib.bluetooth.BluetoothUtils; 73 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; 74 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; 75 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastMetadata; 76 import com.android.settingslib.bluetooth.LocalBluetoothManager; 77 import com.android.settingslib.media.InfoMediaManager; 78 import com.android.settingslib.media.LocalMediaManager; 79 import com.android.settingslib.media.MediaDevice; 80 import com.android.settingslib.utils.ThreadUtils; 81 import com.android.systemui.R; 82 import com.android.systemui.animation.ActivityLaunchAnimator; 83 import com.android.systemui.animation.DialogLaunchAnimator; 84 import com.android.systemui.broadcast.BroadcastSender; 85 import com.android.systemui.flags.FeatureFlags; 86 import com.android.systemui.media.nearby.NearbyMediaDevicesManager; 87 import com.android.systemui.monet.ColorScheme; 88 import com.android.systemui.plugins.ActivityStarter; 89 import com.android.systemui.settings.UserTracker; 90 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 91 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; 92 import com.android.systemui.statusbar.phone.SystemUIDialog; 93 94 import java.nio.charset.StandardCharsets; 95 import java.util.ArrayList; 96 import java.util.Collection; 97 import java.util.Collections; 98 import java.util.Comparator; 99 import java.util.HashMap; 100 import java.util.List; 101 import java.util.Map; 102 import java.util.Set; 103 import java.util.concurrent.ConcurrentHashMap; 104 import java.util.concurrent.CopyOnWriteArrayList; 105 import java.util.concurrent.Executor; 106 import java.util.stream.Collectors; 107 108 import javax.inject.Inject; 109 110 /** 111 * Controller for media output dialog 112 */ 113 public class MediaOutputController implements LocalMediaManager.DeviceCallback, 114 INearbyMediaDevicesUpdateCallback { 115 116 private static final String TAG = "MediaOutputController"; 117 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 118 private static final String PAGE_CONNECTED_DEVICES_KEY = 119 "top_level_connected_devices"; 120 private static final long ALLOWLIST_DURATION_MS = 20000; 121 private static final String ALLOWLIST_REASON = "mediaoutput:remote_transfer"; 122 123 private final String mPackageName; 124 private final Context mContext; 125 private final MediaSessionManager mMediaSessionManager; 126 private final LocalBluetoothManager mLocalBluetoothManager; 127 private final ActivityStarter mActivityStarter; 128 private final DialogLaunchAnimator mDialogLaunchAnimator; 129 private final CommonNotifCollection mNotifCollection; 130 private final Object mMediaDevicesLock = new Object(); 131 @VisibleForTesting 132 final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>(); 133 final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>(); 134 private final List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>(); 135 private final AudioManager mAudioManager; 136 private final PowerExemptionManager mPowerExemptionManager; 137 private final KeyguardManager mKeyGuardManager; 138 private final NearbyMediaDevicesManager mNearbyMediaDevicesManager; 139 private final Map<String, Integer> mNearbyDeviceInfoMap = new ConcurrentHashMap<>(); 140 141 @VisibleForTesting 142 boolean mIsRefreshing = false; 143 @VisibleForTesting 144 boolean mNeedRefresh = false; 145 private MediaController mMediaController; 146 @VisibleForTesting 147 Callback mCallback; 148 @VisibleForTesting 149 LocalMediaManager mLocalMediaManager; 150 @VisibleForTesting 151 MediaOutputMetricLogger mMetricLogger; 152 private int mCurrentState; 153 154 private int mColorItemContent; 155 private int mColorSeekbarProgress; 156 private int mColorButtonBackground; 157 private int mColorItemBackground; 158 private int mColorConnectedItemBackground; 159 private int mColorPositiveButtonText; 160 private int mColorDialogBackground; 161 private int mItemMarginEndDefault; 162 private int mItemMarginEndSelectable; 163 private float mInactiveRadius; 164 private float mActiveRadius; 165 private FeatureFlags mFeatureFlags; 166 private UserTracker mUserTracker; 167 168 public enum BroadcastNotifyDialog { 169 ACTION_FIRST_LAUNCH, 170 ACTION_BROADCAST_INFO_ICON 171 } 172 173 @Inject MediaOutputController(@onNull Context context, String packageName, MediaSessionManager mediaSessionManager, LocalBluetoothManager lbm, ActivityStarter starter, CommonNotifCollection notifCollection, DialogLaunchAnimator dialogLaunchAnimator, NearbyMediaDevicesManager nearbyMediaDevicesManager, AudioManager audioManager, PowerExemptionManager powerExemptionManager, KeyguardManager keyGuardManager, FeatureFlags featureFlags, UserTracker userTracker)174 public MediaOutputController(@NonNull Context context, String packageName, 175 MediaSessionManager mediaSessionManager, LocalBluetoothManager 176 lbm, ActivityStarter starter, 177 CommonNotifCollection notifCollection, 178 DialogLaunchAnimator dialogLaunchAnimator, 179 NearbyMediaDevicesManager nearbyMediaDevicesManager, 180 AudioManager audioManager, 181 PowerExemptionManager powerExemptionManager, 182 KeyguardManager keyGuardManager, 183 FeatureFlags featureFlags, 184 UserTracker userTracker) { 185 mContext = context; 186 mPackageName = packageName; 187 mMediaSessionManager = mediaSessionManager; 188 mLocalBluetoothManager = lbm; 189 mActivityStarter = starter; 190 mNotifCollection = notifCollection; 191 mAudioManager = audioManager; 192 mPowerExemptionManager = powerExemptionManager; 193 mKeyGuardManager = keyGuardManager; 194 mFeatureFlags = featureFlags; 195 mUserTracker = userTracker; 196 InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm); 197 mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName); 198 mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName); 199 mDialogLaunchAnimator = dialogLaunchAnimator; 200 mNearbyMediaDevicesManager = nearbyMediaDevicesManager; 201 mColorItemContent = Utils.getColorStateListDefaultColor(mContext, 202 R.color.media_dialog_item_main_content); 203 mColorSeekbarProgress = Utils.getColorStateListDefaultColor(mContext, 204 R.color.media_dialog_seekbar_progress); 205 mColorButtonBackground = Utils.getColorStateListDefaultColor(mContext, 206 R.color.media_dialog_button_background); 207 mColorItemBackground = Utils.getColorStateListDefaultColor(mContext, 208 R.color.media_dialog_item_background); 209 mColorConnectedItemBackground = Utils.getColorStateListDefaultColor(mContext, 210 R.color.media_dialog_connected_item_background); 211 mColorPositiveButtonText = Utils.getColorStateListDefaultColor(mContext, 212 R.color.media_dialog_solid_button_text); 213 mInactiveRadius = mContext.getResources().getDimension( 214 R.dimen.media_output_dialog_background_radius); 215 mActiveRadius = mContext.getResources().getDimension( 216 R.dimen.media_output_dialog_active_background_radius); 217 mColorDialogBackground = Utils.getColorStateListDefaultColor(mContext, 218 R.color.media_dialog_background); 219 mItemMarginEndDefault = (int) mContext.getResources().getDimension( 220 R.dimen.media_output_dialog_default_margin_end); 221 mItemMarginEndSelectable = (int) mContext.getResources().getDimension( 222 R.dimen.media_output_dialog_selectable_margin_end); 223 } 224 start(@onNull Callback cb)225 void start(@NonNull Callback cb) { 226 synchronized (mMediaDevicesLock) { 227 mCachedMediaDevices.clear(); 228 mMediaItemList.clear(); 229 } 230 mNearbyDeviceInfoMap.clear(); 231 if (mNearbyMediaDevicesManager != null) { 232 mNearbyMediaDevicesManager.registerNearbyDevicesCallback(this); 233 } 234 if (!TextUtils.isEmpty(mPackageName)) { 235 mMediaController = getMediaController(); 236 if (mMediaController != null) { 237 mMediaController.unregisterCallback(mCb); 238 if (mMediaController.getPlaybackState() != null) { 239 mCurrentState = mMediaController.getPlaybackState().getState(); 240 } 241 mMediaController.registerCallback(mCb); 242 } 243 } 244 if (mMediaController == null) { 245 if (DEBUG) { 246 Log.d(TAG, "No media controller for " + mPackageName); 247 } 248 } 249 mCallback = cb; 250 mLocalMediaManager.registerCallback(this); 251 mLocalMediaManager.startScan(); 252 } 253 shouldShowLaunchSection()254 boolean shouldShowLaunchSection() { 255 // TODO(b/231398073): Implements this when available. 256 return false; 257 } 258 isRefreshing()259 boolean isRefreshing() { 260 return mIsRefreshing; 261 } 262 setRefreshing(boolean refreshing)263 void setRefreshing(boolean refreshing) { 264 mIsRefreshing = refreshing; 265 } 266 stop()267 void stop() { 268 if (mMediaController != null) { 269 mMediaController.unregisterCallback(mCb); 270 } 271 mLocalMediaManager.unregisterCallback(this); 272 mLocalMediaManager.stopScan(); 273 synchronized (mMediaDevicesLock) { 274 mCachedMediaDevices.clear(); 275 mMediaItemList.clear(); 276 } 277 if (mNearbyMediaDevicesManager != null) { 278 mNearbyMediaDevicesManager.unregisterNearbyDevicesCallback(this); 279 } 280 mNearbyDeviceInfoMap.clear(); 281 } 282 getMediaController()283 private MediaController getMediaController() { 284 for (NotificationEntry entry : mNotifCollection.getAllNotifs()) { 285 final Notification notification = entry.getSbn().getNotification(); 286 if (notification.isMediaNotification() 287 && TextUtils.equals(entry.getSbn().getPackageName(), mPackageName)) { 288 MediaSession.Token token = notification.extras.getParcelable( 289 Notification.EXTRA_MEDIA_SESSION, 290 MediaSession.Token.class); 291 return new MediaController(mContext, token); 292 } 293 } 294 for (MediaController controller : mMediaSessionManager.getActiveSessionsForUser(null, 295 mUserTracker.getUserHandle())) { 296 if (TextUtils.equals(controller.getPackageName(), mPackageName)) { 297 return controller; 298 } 299 } 300 return null; 301 } 302 303 @Override onDeviceListUpdate(List<MediaDevice> devices)304 public void onDeviceListUpdate(List<MediaDevice> devices) { 305 boolean isListEmpty = mMediaItemList.isEmpty(); 306 if (isListEmpty || !mIsRefreshing) { 307 buildMediaItems(devices); 308 mCallback.onDeviceListChanged(); 309 } else { 310 synchronized (mMediaDevicesLock) { 311 mNeedRefresh = true; 312 mCachedMediaDevices.clear(); 313 mCachedMediaDevices.addAll(devices); 314 } 315 } 316 } 317 318 @Override onSelectedDeviceStateChanged(MediaDevice device, @LocalMediaManager.MediaDeviceState int state)319 public void onSelectedDeviceStateChanged(MediaDevice device, 320 @LocalMediaManager.MediaDeviceState int state) { 321 mCallback.onRouteChanged(); 322 mMetricLogger.logOutputItemSuccess(device.toString(), new ArrayList<>(mMediaItemList)); 323 } 324 325 @Override onDeviceAttributesChanged()326 public void onDeviceAttributesChanged() { 327 mCallback.onRouteChanged(); 328 } 329 330 @Override onRequestFailed(int reason)331 public void onRequestFailed(int reason) { 332 mCallback.onRouteChanged(); 333 mMetricLogger.logOutputItemFailure(new ArrayList<>(mMediaItemList), reason); 334 } 335 336 /** 337 * Checks if there's any muting expected device exist 338 */ hasMutingExpectedDevice()339 public boolean hasMutingExpectedDevice() { 340 return mAudioManager.getMutingExpectedDevice() != null; 341 } 342 343 /** 344 * Cancels mute await connection action in follow up request 345 */ cancelMuteAwaitConnection()346 public void cancelMuteAwaitConnection() { 347 if (mAudioManager.getMutingExpectedDevice() == null) { 348 return; 349 } 350 try { 351 synchronized (mMediaDevicesLock) { 352 mMediaItemList.removeIf((MediaItem::isMutingExpectedDevice)); 353 } 354 mAudioManager.cancelMuteAwaitConnection(mAudioManager.getMutingExpectedDevice()); 355 } catch (Exception e) { 356 Log.d(TAG, "Unable to cancel mute await connection"); 357 } 358 } 359 getAppSourceIconFromPackage()360 Drawable getAppSourceIconFromPackage() { 361 if (mPackageName.isEmpty()) { 362 return null; 363 } 364 try { 365 Log.d(TAG, "try to get app icon"); 366 return mContext.getPackageManager() 367 .getApplicationIcon(mPackageName); 368 } catch (PackageManager.NameNotFoundException e) { 369 Log.d(TAG, "icon not found"); 370 return null; 371 } 372 } 373 getAppSourceName()374 String getAppSourceName() { 375 if (mPackageName.isEmpty()) { 376 return null; 377 } 378 final PackageManager packageManager = mContext.getPackageManager(); 379 ApplicationInfo applicationInfo; 380 try { 381 applicationInfo = packageManager.getApplicationInfo(mPackageName, 382 PackageManager.ApplicationInfoFlags.of(0)); 383 } catch (PackageManager.NameNotFoundException e) { 384 applicationInfo = null; 385 } 386 final String applicationName = 387 (String) (applicationInfo != null ? packageManager.getApplicationLabel( 388 applicationInfo) 389 : mContext.getString(R.string.media_output_dialog_unknown_launch_app_name)); 390 return applicationName; 391 } 392 getAppLaunchIntent()393 Intent getAppLaunchIntent() { 394 if (mPackageName.isEmpty()) { 395 return null; 396 } 397 return mContext.getPackageManager().getLaunchIntentForPackage(mPackageName); 398 } 399 tryToLaunchInAppRoutingIntent(String routeId, View view)400 void tryToLaunchInAppRoutingIntent(String routeId, View view) { 401 ComponentName componentName = mLocalMediaManager.getLinkedItemComponentName(); 402 if (componentName != null) { 403 ActivityLaunchAnimator.Controller controller = 404 mDialogLaunchAnimator.createActivityLaunchController(view); 405 Intent launchIntent = new Intent(ACTION_TRANSFER_MEDIA); 406 launchIntent.setComponent(componentName); 407 launchIntent.putExtra(EXTRA_ROUTE_ID, routeId); 408 launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 409 mCallback.dismissDialog(); 410 mActivityStarter.startActivity(launchIntent, true, controller); 411 } 412 } 413 tryToLaunchMediaApplication(View view)414 void tryToLaunchMediaApplication(View view) { 415 ActivityLaunchAnimator.Controller controller = 416 mDialogLaunchAnimator.createActivityLaunchController(view); 417 Intent launchIntent = getAppLaunchIntent(); 418 if (launchIntent != null) { 419 launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 420 mCallback.dismissDialog(); 421 mActivityStarter.startActivity(launchIntent, true, controller); 422 } 423 } 424 getHeaderTitle()425 CharSequence getHeaderTitle() { 426 if (mMediaController != null) { 427 final MediaMetadata metadata = mMediaController.getMetadata(); 428 if (metadata != null) { 429 return metadata.getDescription().getTitle(); 430 } 431 } 432 return mContext.getText(R.string.controls_media_title); 433 } 434 getHeaderSubTitle()435 CharSequence getHeaderSubTitle() { 436 if (mMediaController == null) { 437 return null; 438 } 439 final MediaMetadata metadata = mMediaController.getMetadata(); 440 if (metadata == null) { 441 return null; 442 } 443 return metadata.getDescription().getSubtitle(); 444 } 445 getHeaderIcon()446 IconCompat getHeaderIcon() { 447 if (mMediaController == null) { 448 return null; 449 } 450 final MediaMetadata metadata = mMediaController.getMetadata(); 451 if (metadata != null) { 452 final Bitmap bitmap = metadata.getDescription().getIconBitmap(); 453 if (bitmap != null) { 454 final Bitmap roundBitmap = Utils.convertCornerRadiusBitmap(mContext, bitmap, 455 (float) mContext.getResources().getDimensionPixelSize( 456 R.dimen.media_output_dialog_icon_corner_radius)); 457 return IconCompat.createWithBitmap(roundBitmap); 458 } 459 } 460 if (DEBUG) { 461 Log.d(TAG, "Media meta data does not contain icon information"); 462 } 463 return getNotificationIcon(); 464 } 465 getDeviceIconCompat(MediaDevice device)466 IconCompat getDeviceIconCompat(MediaDevice device) { 467 Drawable drawable = device.getIcon(); 468 if (drawable == null) { 469 if (DEBUG) { 470 Log.d(TAG, "getDeviceIconCompat() device : " + device.getName() 471 + ", drawable is null"); 472 } 473 // Use default Bluetooth device icon to handle getIcon() is null case. 474 drawable = mContext.getDrawable(com.android.internal.R.drawable.ic_bt_headphones_a2dp); 475 } 476 if (!(drawable instanceof BitmapDrawable)) { 477 setColorFilter(drawable, isActiveItem(device)); 478 } 479 return BluetoothUtils.createIconWithDrawable(drawable); 480 } 481 setColorFilter(Drawable drawable, boolean isActive)482 void setColorFilter(Drawable drawable, boolean isActive) { 483 drawable.setColorFilter(new PorterDuffColorFilter(mColorItemContent, 484 PorterDuff.Mode.SRC_IN)); 485 } 486 isActiveItem(MediaDevice device)487 boolean isActiveItem(MediaDevice device) { 488 boolean isConnected = mLocalMediaManager.getCurrentConnectedDevice().getId().equals( 489 device.getId()); 490 boolean isSelectedDeviceInGroup = getSelectedMediaDevice().size() > 1 491 && getSelectedMediaDevice().contains(device); 492 return (!hasAdjustVolumeUserRestriction() && isConnected && !isAnyDeviceTransferring()) 493 || isSelectedDeviceInGroup; 494 } 495 getNotificationSmallIcon()496 IconCompat getNotificationSmallIcon() { 497 if (TextUtils.isEmpty(mPackageName)) { 498 return null; 499 } 500 for (NotificationEntry entry : mNotifCollection.getAllNotifs()) { 501 final Notification notification = entry.getSbn().getNotification(); 502 if (notification.isMediaNotification() 503 && TextUtils.equals(entry.getSbn().getPackageName(), mPackageName)) { 504 final Icon icon = notification.getSmallIcon(); 505 if (icon == null) { 506 break; 507 } 508 return IconCompat.createFromIcon(icon); 509 } 510 } 511 return null; 512 } 513 getNotificationIcon()514 IconCompat getNotificationIcon() { 515 if (TextUtils.isEmpty(mPackageName)) { 516 return null; 517 } 518 for (NotificationEntry entry : mNotifCollection.getAllNotifs()) { 519 final Notification notification = entry.getSbn().getNotification(); 520 if (notification.isMediaNotification() 521 && TextUtils.equals(entry.getSbn().getPackageName(), mPackageName)) { 522 final Icon icon = notification.getLargeIcon(); 523 if (icon == null) { 524 break; 525 } 526 return IconCompat.createFromIcon(icon); 527 } 528 } 529 return null; 530 } 531 setCurrentColorScheme(WallpaperColors wallpaperColors, boolean isDarkTheme)532 void setCurrentColorScheme(WallpaperColors wallpaperColors, boolean isDarkTheme) { 533 ColorScheme mCurrentColorScheme = new ColorScheme(wallpaperColors, 534 isDarkTheme); 535 if (isDarkTheme) { 536 mColorItemContent = mCurrentColorScheme.getAccent1().getS100(); // A1-100 537 mColorSeekbarProgress = mCurrentColorScheme.getAccent2().getS600(); // A2-600 538 mColorButtonBackground = mCurrentColorScheme.getAccent1().getS300(); // A1-300 539 mColorItemBackground = mCurrentColorScheme.getNeutral2().getS800(); // N2-800 540 mColorConnectedItemBackground = mCurrentColorScheme.getAccent2().getS800(); // A2-800 541 mColorPositiveButtonText = mCurrentColorScheme.getAccent2().getS800(); // A2-800 542 mColorDialogBackground = mCurrentColorScheme.getNeutral1().getS900(); // N1-900 543 } else { 544 mColorItemContent = mCurrentColorScheme.getAccent1().getS800(); // A1-800 545 mColorSeekbarProgress = mCurrentColorScheme.getAccent1().getS300(); // A1-300 546 mColorButtonBackground = mCurrentColorScheme.getAccent1().getS600(); // A1-600 547 mColorItemBackground = mCurrentColorScheme.getAccent2().getS50(); // A2-50 548 mColorConnectedItemBackground = mCurrentColorScheme.getAccent1().getS100(); // A1-100 549 mColorPositiveButtonText = mCurrentColorScheme.getNeutral1().getS50(); // N1-50 550 mColorDialogBackground = mCurrentColorScheme.getBackgroundColor(); 551 } 552 } 553 refreshDataSetIfNeeded()554 void refreshDataSetIfNeeded() { 555 if (mNeedRefresh) { 556 buildMediaItems(mCachedMediaDevices); 557 mCallback.onDeviceListChanged(); 558 mNeedRefresh = false; 559 } 560 } 561 getColorConnectedItemBackground()562 public int getColorConnectedItemBackground() { 563 return mColorConnectedItemBackground; 564 } 565 getColorPositiveButtonText()566 public int getColorPositiveButtonText() { 567 return mColorPositiveButtonText; 568 } 569 getColorDialogBackground()570 public int getColorDialogBackground() { 571 return mColorDialogBackground; 572 } 573 getColorItemContent()574 public int getColorItemContent() { 575 return mColorItemContent; 576 } 577 getColorSeekbarProgress()578 public int getColorSeekbarProgress() { 579 return mColorSeekbarProgress; 580 } 581 getColorButtonBackground()582 public int getColorButtonBackground() { 583 return mColorButtonBackground; 584 } 585 getColorItemBackground()586 public int getColorItemBackground() { 587 return mColorItemBackground; 588 } 589 getInactiveRadius()590 public float getInactiveRadius() { 591 return mInactiveRadius; 592 } 593 getActiveRadius()594 public float getActiveRadius() { 595 return mActiveRadius; 596 } 597 getItemMarginEndDefault()598 public int getItemMarginEndDefault() { 599 return mItemMarginEndDefault; 600 } 601 getItemMarginEndSelectable()602 public int getItemMarginEndSelectable() { 603 return mItemMarginEndSelectable; 604 } 605 buildMediaItems(List<MediaDevice> devices)606 private void buildMediaItems(List<MediaDevice> devices) { 607 synchronized (mMediaDevicesLock) { 608 if (!mLocalMediaManager.isPreferenceRouteListingExist()) { 609 attachRangeInfo(devices); 610 Collections.sort(devices, Comparator.naturalOrder()); 611 } 612 // For the first time building list, to make sure the top device is the connected 613 // device. 614 boolean needToHandleMutingExpectedDevice = 615 hasMutingExpectedDevice() && !isCurrentConnectedDeviceRemote(); 616 final MediaDevice connectedMediaDevice = 617 needToHandleMutingExpectedDevice ? null 618 : getCurrentConnectedMediaDevice(); 619 if (mMediaItemList.isEmpty()) { 620 if (connectedMediaDevice == null) { 621 if (DEBUG) { 622 Log.d(TAG, "No connected media device or muting expected device exist."); 623 } 624 categorizeMediaItems(null, devices, needToHandleMutingExpectedDevice); 625 return; 626 } 627 // selected device exist 628 categorizeMediaItems(connectedMediaDevice, devices, false); 629 return; 630 } 631 // To keep the same list order 632 final List<MediaDevice> targetMediaDevices = new ArrayList<>(); 633 final Map<Integer, MediaItem> dividerItems = new HashMap<>(); 634 for (MediaItem originalMediaItem : mMediaItemList) { 635 for (MediaDevice newDevice : devices) { 636 if (originalMediaItem.getMediaDevice().isPresent() 637 && TextUtils.equals(originalMediaItem.getMediaDevice().get().getId(), 638 newDevice.getId())) { 639 targetMediaDevices.add(newDevice); 640 break; 641 } 642 } 643 if (originalMediaItem.getMediaItemType() 644 == MediaItem.MediaItemType.TYPE_GROUP_DIVIDER) { 645 dividerItems.put(mMediaItemList.indexOf(originalMediaItem), originalMediaItem); 646 } 647 } 648 if (targetMediaDevices.size() != devices.size()) { 649 devices.removeAll(targetMediaDevices); 650 targetMediaDevices.addAll(devices); 651 } 652 List<MediaItem> finalMediaItems = targetMediaDevices.stream().map( 653 MediaItem::new).collect(Collectors.toList()); 654 dividerItems.forEach((key, item) -> { 655 finalMediaItems.add(key, item); 656 }); 657 attachConnectNewDeviceItemIfNeeded(finalMediaItems); 658 mMediaItemList.clear(); 659 mMediaItemList.addAll(finalMediaItems); 660 } 661 } 662 categorizeMediaItems(MediaDevice connectedMediaDevice, List<MediaDevice> devices, boolean needToHandleMutingExpectedDevice)663 private void categorizeMediaItems(MediaDevice connectedMediaDevice, List<MediaDevice> devices, 664 boolean needToHandleMutingExpectedDevice) { 665 synchronized (mMediaDevicesLock) { 666 List<MediaItem> finalMediaItems = new ArrayList<>(); 667 Set<String> selectedDevicesIds = getSelectedMediaDevice().stream().map( 668 MediaDevice::getId).collect(Collectors.toSet()); 669 if (connectedMediaDevice != null) { 670 selectedDevicesIds.add(connectedMediaDevice.getId()); 671 } 672 boolean suggestedDeviceAdded = false; 673 boolean displayGroupAdded = false; 674 for (MediaDevice device : devices) { 675 if (needToHandleMutingExpectedDevice && device.isMutingExpectedDevice()) { 676 finalMediaItems.add(0, new MediaItem(device)); 677 } else if (!needToHandleMutingExpectedDevice && selectedDevicesIds.contains( 678 device.getId())) { 679 finalMediaItems.add(0, new MediaItem(device)); 680 } else { 681 if (device.isSuggestedDevice() && !suggestedDeviceAdded) { 682 attachGroupDivider(finalMediaItems, mContext.getString( 683 R.string.media_output_group_title_suggested_device)); 684 suggestedDeviceAdded = true; 685 } else if (!device.isSuggestedDevice() && !displayGroupAdded) { 686 attachGroupDivider(finalMediaItems, mContext.getString( 687 R.string.media_output_group_title_speakers_and_displays)); 688 displayGroupAdded = true; 689 } 690 finalMediaItems.add(new MediaItem(device)); 691 } 692 } 693 attachConnectNewDeviceItemIfNeeded(finalMediaItems); 694 mMediaItemList.clear(); 695 mMediaItemList.addAll(finalMediaItems); 696 } 697 } 698 attachGroupDivider(List<MediaItem> mediaItems, String title)699 private void attachGroupDivider(List<MediaItem> mediaItems, String title) { 700 mediaItems.add( 701 new MediaItem(title, MediaItem.MediaItemType.TYPE_GROUP_DIVIDER)); 702 } 703 attachConnectNewDeviceItemIfNeeded(List<MediaItem> mediaItems)704 private void attachConnectNewDeviceItemIfNeeded(List<MediaItem> mediaItems) { 705 // Attach "Connect a device" item only when current output is not remote and not a group 706 if (!isCurrentConnectedDeviceRemote() && getSelectedMediaDevice().size() == 1) { 707 mediaItems.add(new MediaItem()); 708 } 709 } 710 attachRangeInfo(List<MediaDevice> devices)711 private void attachRangeInfo(List<MediaDevice> devices) { 712 for (MediaDevice mediaDevice : devices) { 713 if (mNearbyDeviceInfoMap.containsKey(mediaDevice.getId())) { 714 mediaDevice.setRangeZone(mNearbyDeviceInfoMap.get(mediaDevice.getId())); 715 } 716 } 717 718 } 719 isCurrentConnectedDeviceRemote()720 boolean isCurrentConnectedDeviceRemote() { 721 MediaDevice currentConnectedMediaDevice = getCurrentConnectedMediaDevice(); 722 return currentConnectedMediaDevice != null && isActiveRemoteDevice( 723 currentConnectedMediaDevice); 724 } 725 isCurrentOutputDeviceHasSessionOngoing()726 boolean isCurrentOutputDeviceHasSessionOngoing() { 727 MediaDevice currentConnectedMediaDevice = getCurrentConnectedMediaDevice(); 728 return currentConnectedMediaDevice != null 729 && (currentConnectedMediaDevice.isHostForOngoingSession()); 730 } 731 getGroupMediaDevices()732 List<MediaDevice> getGroupMediaDevices() { 733 final List<MediaDevice> selectedDevices = getSelectedMediaDevice(); 734 final List<MediaDevice> selectableDevices = getSelectableMediaDevice(); 735 if (mGroupMediaDevices.isEmpty()) { 736 mGroupMediaDevices.addAll(selectedDevices); 737 mGroupMediaDevices.addAll(selectableDevices); 738 return mGroupMediaDevices; 739 } 740 // To keep the same list order 741 final Collection<MediaDevice> sourceDevices = new ArrayList<>(); 742 final Collection<MediaDevice> targetMediaDevices = new ArrayList<>(); 743 sourceDevices.addAll(selectedDevices); 744 sourceDevices.addAll(selectableDevices); 745 for (MediaDevice originalDevice : mGroupMediaDevices) { 746 for (MediaDevice newDevice : sourceDevices) { 747 if (TextUtils.equals(originalDevice.getId(), newDevice.getId())) { 748 targetMediaDevices.add(newDevice); 749 sourceDevices.remove(newDevice); 750 break; 751 } 752 } 753 } 754 // Add new devices at the end of list if necessary 755 if (!sourceDevices.isEmpty()) { 756 targetMediaDevices.addAll(sourceDevices); 757 } 758 mGroupMediaDevices.clear(); 759 mGroupMediaDevices.addAll(targetMediaDevices); 760 761 return mGroupMediaDevices; 762 } 763 resetGroupMediaDevices()764 void resetGroupMediaDevices() { 765 mGroupMediaDevices.clear(); 766 } 767 connectDevice(MediaDevice device)768 void connectDevice(MediaDevice device) { 769 mMetricLogger.updateOutputEndPoints(getCurrentConnectedMediaDevice(), device); 770 771 ThreadUtils.postOnBackgroundThread(() -> { 772 mLocalMediaManager.connectDevice(device); 773 }); 774 } 775 getMediaItemList()776 public List<MediaItem> getMediaItemList() { 777 return mMediaItemList; 778 } 779 getCurrentConnectedMediaDevice()780 MediaDevice getCurrentConnectedMediaDevice() { 781 return mLocalMediaManager.getCurrentConnectedDevice(); 782 } 783 addDeviceToPlayMedia(MediaDevice device)784 boolean addDeviceToPlayMedia(MediaDevice device) { 785 mMetricLogger.logInteractionExpansion(device); 786 return mLocalMediaManager.addDeviceToPlayMedia(device); 787 } 788 removeDeviceFromPlayMedia(MediaDevice device)789 boolean removeDeviceFromPlayMedia(MediaDevice device) { 790 return mLocalMediaManager.removeDeviceFromPlayMedia(device); 791 } 792 getSelectableMediaDevice()793 List<MediaDevice> getSelectableMediaDevice() { 794 return mLocalMediaManager.getSelectableMediaDevice(); 795 } 796 getSelectedMediaDevice()797 List<MediaDevice> getSelectedMediaDevice() { 798 return mLocalMediaManager.getSelectedMediaDevice(); 799 } 800 getDeselectableMediaDevice()801 List<MediaDevice> getDeselectableMediaDevice() { 802 return mLocalMediaManager.getDeselectableMediaDevice(); 803 } 804 adjustSessionVolume(int volume)805 void adjustSessionVolume(int volume) { 806 mLocalMediaManager.adjustSessionVolume(volume); 807 } 808 getSessionVolumeMax()809 int getSessionVolumeMax() { 810 return mLocalMediaManager.getSessionVolumeMax(); 811 } 812 getSessionVolume()813 int getSessionVolume() { 814 return mLocalMediaManager.getSessionVolume(); 815 } 816 getSessionName()817 CharSequence getSessionName() { 818 return mLocalMediaManager.getSessionName(); 819 } 820 releaseSession()821 void releaseSession() { 822 mMetricLogger.logInteractionStopCasting(); 823 mLocalMediaManager.releaseSession(); 824 } 825 getActiveRemoteMediaDevices()826 List<RoutingSessionInfo> getActiveRemoteMediaDevices() { 827 final List<RoutingSessionInfo> sessionInfos = new ArrayList<>(); 828 for (RoutingSessionInfo info : mLocalMediaManager.getActiveMediaSession()) { 829 if (!info.isSystemSession()) { 830 sessionInfos.add(info); 831 } 832 } 833 return sessionInfos; 834 } 835 adjustVolume(MediaDevice device, int volume)836 void adjustVolume(MediaDevice device, int volume) { 837 ThreadUtils.postOnBackgroundThread(() -> { 838 device.requestSetVolume(volume); 839 }); 840 } 841 logInteractionAdjustVolume(MediaDevice device)842 void logInteractionAdjustVolume(MediaDevice device) { 843 mMetricLogger.logInteractionAdjustVolume(device); 844 } 845 logInteractionMuteDevice(MediaDevice device)846 void logInteractionMuteDevice(MediaDevice device) { 847 mMetricLogger.logInteractionMute(device); 848 } 849 logInteractionUnmuteDevice(MediaDevice device)850 void logInteractionUnmuteDevice(MediaDevice device) { 851 mMetricLogger.logInteractionUnmute(device); 852 } 853 getPackageName()854 String getPackageName() { 855 return mPackageName; 856 } 857 hasAdjustVolumeUserRestriction()858 boolean hasAdjustVolumeUserRestriction() { 859 if (RestrictedLockUtilsInternal.checkIfRestrictionEnforced( 860 mContext, UserManager.DISALLOW_ADJUST_VOLUME, UserHandle.myUserId()) != null) { 861 return true; 862 } 863 final UserManager um = mContext.getSystemService(UserManager.class); 864 return um.hasBaseUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME, 865 UserHandle.of(UserHandle.myUserId())); 866 } 867 isAnyDeviceTransferring()868 boolean isAnyDeviceTransferring() { 869 synchronized (mMediaDevicesLock) { 870 for (MediaItem mediaItem : mMediaItemList) { 871 if (mediaItem.getMediaDevice().isPresent() 872 && mediaItem.getMediaDevice().get().getState() 873 == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) { 874 return true; 875 } 876 } 877 } 878 return false; 879 } 880 launchBluetoothPairing(View view)881 void launchBluetoothPairing(View view) { 882 ActivityLaunchAnimator.Controller controller = 883 mDialogLaunchAnimator.createActivityLaunchController(view); 884 885 if (controller == null || (mKeyGuardManager != null 886 && mKeyGuardManager.isKeyguardLocked())) { 887 mCallback.dismissDialog(); 888 } 889 890 Intent launchIntent = 891 new Intent(ACTION_BLUETOOTH_SETTINGS) 892 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); 893 final Intent deepLinkIntent = 894 new Intent(Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY); 895 if (deepLinkIntent.resolveActivity(mContext.getPackageManager()) != null) { 896 Log.d(TAG, "Device support split mode, launch page with deep link"); 897 deepLinkIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 898 deepLinkIntent.putExtra( 899 Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI, 900 launchIntent.toUri(Intent.URI_INTENT_SCHEME)); 901 deepLinkIntent.putExtra( 902 Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY, 903 PAGE_CONNECTED_DEVICES_KEY); 904 mActivityStarter.startActivity(deepLinkIntent, true, controller); 905 return; 906 } 907 mActivityStarter.startActivity(launchIntent, true, controller); 908 } 909 launchLeBroadcastNotifyDialog(View mediaOutputDialog, BroadcastSender broadcastSender, BroadcastNotifyDialog action, final DialogInterface.OnClickListener listener)910 void launchLeBroadcastNotifyDialog(View mediaOutputDialog, BroadcastSender broadcastSender, 911 BroadcastNotifyDialog action, final DialogInterface.OnClickListener listener) { 912 final AlertDialog.Builder builder = new AlertDialog.Builder(mContext); 913 switch (action) { 914 case ACTION_FIRST_LAUNCH: 915 builder.setTitle(R.string.media_output_first_broadcast_title); 916 builder.setMessage(R.string.media_output_first_notify_broadcast_message); 917 builder.setNegativeButton(android.R.string.cancel, null); 918 builder.setPositiveButton(R.string.media_output_broadcast, listener); 919 break; 920 case ACTION_BROADCAST_INFO_ICON: 921 builder.setTitle(R.string.media_output_broadcast); 922 builder.setMessage(R.string.media_output_broadcasting_message); 923 builder.setPositiveButton(android.R.string.ok, null); 924 break; 925 } 926 927 final AlertDialog dialog = builder.create(); 928 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 929 SystemUIDialog.setShowForAllUsers(dialog, true); 930 SystemUIDialog.registerDismissListener(dialog); 931 dialog.show(); 932 } 933 launchMediaOutputBroadcastDialog(View mediaOutputDialog, BroadcastSender broadcastSender)934 void launchMediaOutputBroadcastDialog(View mediaOutputDialog, BroadcastSender broadcastSender) { 935 MediaOutputController controller = new MediaOutputController(mContext, mPackageName, 936 mMediaSessionManager, mLocalBluetoothManager, mActivityStarter, 937 mNotifCollection, mDialogLaunchAnimator, mNearbyMediaDevicesManager, 938 mAudioManager, mPowerExemptionManager, mKeyGuardManager, mFeatureFlags, 939 mUserTracker); 940 MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true, 941 broadcastSender, controller); 942 dialog.show(); 943 } 944 getBroadcastName()945 String getBroadcastName() { 946 LocalBluetoothLeBroadcast broadcast = 947 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); 948 if (broadcast == null) { 949 Log.d(TAG, "getBroadcastName: LE Audio Broadcast is null"); 950 return ""; 951 } 952 return broadcast.getProgramInfo(); 953 } 954 setBroadcastName(String broadcastName)955 void setBroadcastName(String broadcastName) { 956 LocalBluetoothLeBroadcast broadcast = 957 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); 958 if (broadcast == null) { 959 Log.d(TAG, "setBroadcastName: LE Audio Broadcast is null"); 960 return; 961 } 962 broadcast.setProgramInfo(broadcastName); 963 } 964 getBroadcastCode()965 String getBroadcastCode() { 966 LocalBluetoothLeBroadcast broadcast = 967 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); 968 if (broadcast == null) { 969 Log.d(TAG, "getBroadcastCode: LE Audio Broadcast is null"); 970 return ""; 971 } 972 return new String(broadcast.getBroadcastCode(), StandardCharsets.UTF_8); 973 } 974 setBroadcastCode(String broadcastCode)975 void setBroadcastCode(String broadcastCode) { 976 LocalBluetoothLeBroadcast broadcast = 977 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); 978 if (broadcast == null) { 979 Log.d(TAG, "setBroadcastCode: LE Audio Broadcast is null"); 980 return; 981 } 982 broadcast.setBroadcastCode(broadcastCode.getBytes(StandardCharsets.UTF_8)); 983 } 984 setTemporaryAllowListExceptionIfNeeded(MediaDevice targetDevice)985 void setTemporaryAllowListExceptionIfNeeded(MediaDevice targetDevice) { 986 if (mPowerExemptionManager == null || mPackageName == null) { 987 Log.w(TAG, "powerExemptionManager or package name is null"); 988 return; 989 } 990 mPowerExemptionManager.addToTemporaryAllowList(mPackageName, 991 PowerExemptionManager.REASON_MEDIA_NOTIFICATION_TRANSFER, 992 ALLOWLIST_REASON, 993 ALLOWLIST_DURATION_MS); 994 } 995 getLocalBroadcastMetadataQrCodeString()996 String getLocalBroadcastMetadataQrCodeString() { 997 LocalBluetoothLeBroadcast broadcast = 998 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); 999 if (broadcast == null) { 1000 Log.d(TAG, "getLocalBroadcastMetadataQrCodeString: LE Audio Broadcast is null"); 1001 return ""; 1002 } 1003 final LocalBluetoothLeBroadcastMetadata metadata = 1004 broadcast.getLocalBluetoothLeBroadcastMetaData(); 1005 return metadata != null ? metadata.convertToQrCodeString() : ""; 1006 } 1007 getBroadcastMetadata()1008 BluetoothLeBroadcastMetadata getBroadcastMetadata() { 1009 LocalBluetoothLeBroadcast broadcast = 1010 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); 1011 if (broadcast == null) { 1012 Log.d(TAG, "getBroadcastMetadata: LE Audio Broadcast is null"); 1013 return null; 1014 } 1015 1016 return broadcast.getLatestBluetoothLeBroadcastMetadata(); 1017 } 1018 isActiveRemoteDevice(@onNull MediaDevice device)1019 boolean isActiveRemoteDevice(@NonNull MediaDevice device) { 1020 final List<String> features = device.getFeatures(); 1021 return (features.contains(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK) 1022 || features.contains(MediaRoute2Info.FEATURE_REMOTE_AUDIO_PLAYBACK) 1023 || features.contains(MediaRoute2Info.FEATURE_REMOTE_VIDEO_PLAYBACK) 1024 || features.contains(MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK)); 1025 } 1026 isBluetoothLeDevice(@onNull MediaDevice device)1027 boolean isBluetoothLeDevice(@NonNull MediaDevice device) { 1028 return device.isBLEDevice(); 1029 } 1030 isBroadcastSupported()1031 boolean isBroadcastSupported() { 1032 LocalBluetoothLeBroadcast broadcast = 1033 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); 1034 return broadcast != null ? true : false; 1035 } 1036 isBluetoothLeBroadcastEnabled()1037 boolean isBluetoothLeBroadcastEnabled() { 1038 LocalBluetoothLeBroadcast broadcast = 1039 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); 1040 if (broadcast == null) { 1041 return false; 1042 } 1043 return broadcast.isEnabled(null); 1044 } 1045 startBluetoothLeBroadcast()1046 boolean startBluetoothLeBroadcast() { 1047 LocalBluetoothLeBroadcast broadcast = 1048 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); 1049 if (broadcast == null) { 1050 Log.d(TAG, "The broadcast profile is null"); 1051 return false; 1052 } 1053 broadcast.startBroadcast(getAppSourceName(), /*language*/ null); 1054 return true; 1055 } 1056 stopBluetoothLeBroadcast()1057 boolean stopBluetoothLeBroadcast() { 1058 LocalBluetoothLeBroadcast broadcast = 1059 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); 1060 if (broadcast == null) { 1061 Log.d(TAG, "The broadcast profile is null"); 1062 return false; 1063 } 1064 broadcast.stopLatestBroadcast(); 1065 return true; 1066 } 1067 updateBluetoothLeBroadcast()1068 boolean updateBluetoothLeBroadcast() { 1069 LocalBluetoothLeBroadcast broadcast = 1070 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); 1071 if (broadcast == null) { 1072 Log.d(TAG, "The broadcast profile is null"); 1073 return false; 1074 } 1075 broadcast.updateBroadcast(getAppSourceName(), /*language*/ null); 1076 return true; 1077 } 1078 registerLeBroadcastServiceCallback( @onNull @allbackExecutor Executor executor, @NonNull BluetoothLeBroadcast.Callback callback)1079 void registerLeBroadcastServiceCallback( 1080 @NonNull @CallbackExecutor Executor executor, 1081 @NonNull BluetoothLeBroadcast.Callback callback) { 1082 LocalBluetoothLeBroadcast broadcast = 1083 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); 1084 if (broadcast == null) { 1085 Log.d(TAG, "The broadcast profile is null"); 1086 return; 1087 } 1088 Log.d(TAG, "Register LE broadcast callback"); 1089 broadcast.registerServiceCallBack(executor, callback); 1090 } 1091 unregisterLeBroadcastServiceCallback( @onNull BluetoothLeBroadcast.Callback callback)1092 void unregisterLeBroadcastServiceCallback( 1093 @NonNull BluetoothLeBroadcast.Callback callback) { 1094 LocalBluetoothLeBroadcast broadcast = 1095 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); 1096 if (broadcast == null) { 1097 Log.d(TAG, "The broadcast profile is null"); 1098 return; 1099 } 1100 Log.d(TAG, "Unregister LE broadcast callback"); 1101 broadcast.unregisterServiceCallBack(callback); 1102 } 1103 getConnectedBroadcastSinkDevices()1104 List<BluetoothDevice> getConnectedBroadcastSinkDevices() { 1105 LocalBluetoothLeBroadcastAssistant assistant = 1106 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile(); 1107 if (assistant == null) { 1108 Log.d(TAG, "getConnectedBroadcastSinkDevices: The broadcast assistant profile is null"); 1109 return null; 1110 } 1111 1112 return assistant.getConnectedDevices(); 1113 } 1114 isThereAnyBroadcastSourceIntoSinkDevice(BluetoothDevice sink)1115 boolean isThereAnyBroadcastSourceIntoSinkDevice(BluetoothDevice sink) { 1116 LocalBluetoothLeBroadcastAssistant assistant = 1117 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile(); 1118 if (assistant == null) { 1119 Log.d(TAG, "isThereAnyBroadcastSourceIntoSinkDevice: The broadcast assistant profile " 1120 + "is null"); 1121 return false; 1122 } 1123 List<BluetoothLeBroadcastReceiveState> sourceList = assistant.getAllSources(sink); 1124 Log.d(TAG, "isThereAnyBroadcastSourceIntoSinkDevice: List size: " + sourceList.size()); 1125 return !sourceList.isEmpty(); 1126 } 1127 addSourceIntoSinkDeviceWithBluetoothLeAssistant(BluetoothDevice sink, BluetoothLeBroadcastMetadata metadata, boolean isGroupOp)1128 boolean addSourceIntoSinkDeviceWithBluetoothLeAssistant(BluetoothDevice sink, 1129 BluetoothLeBroadcastMetadata metadata, boolean isGroupOp) { 1130 LocalBluetoothLeBroadcastAssistant assistant = 1131 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile(); 1132 if (assistant == null) { 1133 Log.d(TAG, "addSourceIntoSinkDeviceWithBluetoothLeAssistant: The broadcast assistant " 1134 + "profile is null"); 1135 return false; 1136 } 1137 assistant.addSource(sink, metadata, isGroupOp); 1138 return true; 1139 } 1140 registerLeBroadcastAssistantServiceCallback( @onNull @allbackExecutor Executor executor, @NonNull BluetoothLeBroadcastAssistant.Callback callback)1141 void registerLeBroadcastAssistantServiceCallback( 1142 @NonNull @CallbackExecutor Executor executor, 1143 @NonNull BluetoothLeBroadcastAssistant.Callback callback) { 1144 LocalBluetoothLeBroadcastAssistant assistant = 1145 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile(); 1146 if (assistant == null) { 1147 Log.d(TAG, "registerLeBroadcastAssistantServiceCallback: The broadcast assistant " 1148 + "profile is null"); 1149 return; 1150 } 1151 Log.d(TAG, "Register LE broadcast assistant callback"); 1152 assistant.registerServiceCallBack(executor, callback); 1153 } 1154 unregisterLeBroadcastAssistantServiceCallback( @onNull BluetoothLeBroadcastAssistant.Callback callback)1155 void unregisterLeBroadcastAssistantServiceCallback( 1156 @NonNull BluetoothLeBroadcastAssistant.Callback callback) { 1157 LocalBluetoothLeBroadcastAssistant assistant = 1158 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile(); 1159 if (assistant == null) { 1160 Log.d(TAG, "unregisterLeBroadcastAssistantServiceCallback: The broadcast assistant " 1161 + "profile is null"); 1162 return; 1163 } 1164 Log.d(TAG, "Unregister LE broadcast assistant callback"); 1165 assistant.unregisterServiceCallBack(callback); 1166 } 1167 isPlayBackInfoLocal()1168 private boolean isPlayBackInfoLocal() { 1169 return mMediaController != null 1170 && mMediaController.getPlaybackInfo() != null 1171 && mMediaController.getPlaybackInfo().getPlaybackType() 1172 == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL; 1173 } 1174 isPlaying()1175 boolean isPlaying() { 1176 if (mMediaController == null) { 1177 return false; 1178 } 1179 1180 PlaybackState state = mMediaController.getPlaybackState(); 1181 if (state == null) { 1182 return false; 1183 } 1184 1185 return (state.getState() == PlaybackState.STATE_PLAYING); 1186 } 1187 isVolumeControlEnabled(@onNull MediaDevice device)1188 boolean isVolumeControlEnabled(@NonNull MediaDevice device) { 1189 return (isPlayBackInfoLocal() 1190 || device.getDeviceType() != MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE) 1191 && !device.isVolumeFixed(); 1192 } 1193 1194 @Override onDevicesUpdated(List<NearbyDevice> nearbyDevices)1195 public void onDevicesUpdated(List<NearbyDevice> nearbyDevices) throws RemoteException { 1196 mNearbyDeviceInfoMap.clear(); 1197 for (NearbyDevice nearbyDevice : nearbyDevices) { 1198 mNearbyDeviceInfoMap.put(nearbyDevice.getMediaRoute2Id(), nearbyDevice.getRangeZone()); 1199 } 1200 mNearbyMediaDevicesManager.unregisterNearbyDevicesCallback(this); 1201 } 1202 1203 @Override asBinder()1204 public IBinder asBinder() { 1205 return null; 1206 } 1207 1208 @VisibleForTesting 1209 final MediaController.Callback mCb = new MediaController.Callback() { 1210 @Override 1211 public void onMetadataChanged(MediaMetadata metadata) { 1212 mCallback.onMediaChanged(); 1213 } 1214 1215 @Override 1216 public void onPlaybackStateChanged(PlaybackState playbackState) { 1217 final int newState = 1218 playbackState == null ? PlaybackState.STATE_STOPPED : playbackState.getState(); 1219 if (mCurrentState == newState) { 1220 return; 1221 } 1222 1223 if (newState == PlaybackState.STATE_STOPPED) { 1224 mCallback.onMediaStoppedOrPaused(); 1225 } 1226 mCurrentState = newState; 1227 } 1228 }; 1229 1230 interface Callback { 1231 /** 1232 * Override to handle the media content updating. 1233 */ onMediaChanged()1234 void onMediaChanged(); 1235 1236 /** 1237 * Override to handle the media state updating. 1238 */ onMediaStoppedOrPaused()1239 void onMediaStoppedOrPaused(); 1240 1241 /** 1242 * Override to handle the device status or attributes updating. 1243 */ onRouteChanged()1244 void onRouteChanged(); 1245 1246 /** 1247 * Override to handle the devices set updating. 1248 */ onDeviceListChanged()1249 void onDeviceListChanged(); 1250 1251 /** 1252 * Override to dismiss dialog. 1253 */ dismissDialog()1254 void dismissDialog(); 1255 } 1256 } 1257