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