1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 package com.android.systemui.statusbar; 17 18 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; 19 import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG_MEDIA_FAKE_ARTWORK; 20 import static com.android.systemui.statusbar.phone.CentralSurfaces.ENABLE_LOCKSCREEN_WALLPAPER; 21 import static com.android.systemui.statusbar.phone.CentralSurfaces.SHOW_LOCKSCREEN_MEDIA_ARTWORK; 22 23 import android.annotation.MainThread; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.app.Notification; 27 import android.app.WallpaperManager; 28 import android.content.Context; 29 import android.graphics.Bitmap; 30 import android.graphics.Point; 31 import android.graphics.drawable.BitmapDrawable; 32 import android.graphics.drawable.ColorDrawable; 33 import android.graphics.drawable.Drawable; 34 import android.graphics.drawable.Icon; 35 import android.hardware.display.DisplayManager; 36 import android.media.MediaMetadata; 37 import android.media.session.MediaController; 38 import android.media.session.MediaSession; 39 import android.media.session.PlaybackState; 40 import android.os.AsyncTask; 41 import android.os.Trace; 42 import android.service.notification.NotificationStats; 43 import android.service.notification.StatusBarNotification; 44 import android.util.ArraySet; 45 import android.util.Log; 46 import android.view.Display; 47 import android.view.View; 48 import android.widget.ImageView; 49 50 import com.android.app.animation.Interpolators; 51 import com.android.internal.annotations.VisibleForTesting; 52 import com.android.systemui.Dumpable; 53 import com.android.systemui.colorextraction.SysuiColorExtractor; 54 import com.android.systemui.dagger.qualifiers.Main; 55 import com.android.systemui.dump.DumpManager; 56 import com.android.systemui.media.controls.models.player.MediaData; 57 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData; 58 import com.android.systemui.media.controls.pipeline.MediaDataManager; 59 import com.android.systemui.plugins.statusbar.StatusBarStateController; 60 import com.android.systemui.statusbar.dagger.CentralSurfacesModule; 61 import com.android.systemui.statusbar.notification.collection.NotifCollection; 62 import com.android.systemui.statusbar.notification.collection.NotifPipeline; 63 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 64 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; 65 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; 66 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; 67 import com.android.systemui.statusbar.phone.BiometricUnlockController; 68 import com.android.systemui.statusbar.phone.CentralSurfaces; 69 import com.android.systemui.statusbar.phone.KeyguardBypassController; 70 import com.android.systemui.statusbar.phone.LockscreenWallpaper; 71 import com.android.systemui.statusbar.phone.ScrimController; 72 import com.android.systemui.statusbar.phone.ScrimState; 73 import com.android.systemui.statusbar.policy.KeyguardStateController; 74 import com.android.systemui.util.Utils; 75 import com.android.systemui.util.concurrency.DelayableExecutor; 76 77 import java.io.PrintWriter; 78 import java.lang.ref.WeakReference; 79 import java.util.ArrayList; 80 import java.util.Arrays; 81 import java.util.Collection; 82 import java.util.Comparator; 83 import java.util.HashSet; 84 import java.util.List; 85 import java.util.Objects; 86 import java.util.Optional; 87 import java.util.Set; 88 import java.util.stream.Collectors; 89 90 import dagger.Lazy; 91 92 /** 93 * Handles tasks and state related to media notifications. For example, there is a 'current' media 94 * notification, which this class keeps track of. 95 */ 96 public class NotificationMediaManager implements Dumpable { 97 private static final String TAG = "NotificationMediaManager"; 98 public static final boolean DEBUG_MEDIA = false; 99 100 private final StatusBarStateController mStatusBarStateController; 101 private final SysuiColorExtractor mColorExtractor; 102 private final KeyguardStateController mKeyguardStateController; 103 private final KeyguardBypassController mKeyguardBypassController; 104 private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>(); 105 private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>(); 106 static { 107 PAUSED_MEDIA_STATES.add(PlaybackState.STATE_NONE); 108 PAUSED_MEDIA_STATES.add(PlaybackState.STATE_STOPPED); 109 PAUSED_MEDIA_STATES.add(PlaybackState.STATE_PAUSED); 110 PAUSED_MEDIA_STATES.add(PlaybackState.STATE_ERROR); 111 CONNECTING_MEDIA_STATES.add(PlaybackState.STATE_CONNECTING); 112 CONNECTING_MEDIA_STATES.add(PlaybackState.STATE_BUFFERING); 113 } 114 115 private final NotificationVisibilityProvider mVisibilityProvider; 116 private final MediaDataManager mMediaDataManager; 117 private final NotifPipeline mNotifPipeline; 118 private final NotifCollection mNotifCollection; 119 120 @Nullable 121 private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController; 122 123 @Nullable 124 private BiometricUnlockController mBiometricUnlockController; 125 @Nullable 126 private ScrimController mScrimController; 127 @Nullable 128 private LockscreenWallpaper mLockscreenWallpaper; 129 @VisibleForTesting 130 boolean mIsLockscreenLiveWallpaperEnabled; 131 132 private final DelayableExecutor mMainExecutor; 133 134 private final Context mContext; 135 private final ArrayList<MediaListener> mMediaListeners; 136 private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy; 137 private final MediaArtworkProcessor mMediaArtworkProcessor; 138 private final Set<AsyncTask<?, ?, ?>> mProcessArtworkTasks = new ArraySet<>(); 139 140 protected NotificationPresenter mPresenter; 141 private MediaController mMediaController; 142 private String mMediaNotificationKey; 143 private MediaMetadata mMediaMetadata; 144 145 private BackDropView mBackdrop; 146 private ImageView mBackdropFront; 147 private ImageView mBackdropBack; 148 private final Point mTmpDisplaySize = new Point(); 149 150 private final DisplayManager mDisplayManager; 151 @Nullable 152 private List<String> mSmallerInternalDisplayUids; 153 private Display mCurrentDisplay; 154 155 private LockscreenWallpaper.WallpaperDrawable mWallapperDrawable; 156 157 private final MediaController.Callback mMediaListener = new MediaController.Callback() { 158 @Override 159 public void onPlaybackStateChanged(PlaybackState state) { 160 super.onPlaybackStateChanged(state); 161 if (DEBUG_MEDIA) { 162 Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state); 163 } 164 if (state != null) { 165 if (!isPlaybackActive(state.getState())) { 166 clearCurrentMediaNotification(); 167 } 168 findAndUpdateMediaNotifications(); 169 } 170 } 171 172 @Override 173 public void onMetadataChanged(MediaMetadata metadata) { 174 super.onMetadataChanged(metadata); 175 if (DEBUG_MEDIA) { 176 Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata); 177 } 178 mMediaArtworkProcessor.clearCache(); 179 mMediaMetadata = metadata; 180 dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */); 181 } 182 }; 183 184 /** 185 * Injected constructor. See {@link CentralSurfacesModule}. 186 */ NotificationMediaManager( Context context, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, Lazy<NotificationShadeWindowController> notificationShadeWindowController, NotificationVisibilityProvider visibilityProvider, MediaArtworkProcessor mediaArtworkProcessor, KeyguardBypassController keyguardBypassController, NotifPipeline notifPipeline, NotifCollection notifCollection, @Main DelayableExecutor mainExecutor, MediaDataManager mediaDataManager, StatusBarStateController statusBarStateController, SysuiColorExtractor colorExtractor, KeyguardStateController keyguardStateController, DumpManager dumpManager, WallpaperManager wallpaperManager, DisplayManager displayManager)187 public NotificationMediaManager( 188 Context context, 189 Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, 190 Lazy<NotificationShadeWindowController> notificationShadeWindowController, 191 NotificationVisibilityProvider visibilityProvider, 192 MediaArtworkProcessor mediaArtworkProcessor, 193 KeyguardBypassController keyguardBypassController, 194 NotifPipeline notifPipeline, 195 NotifCollection notifCollection, 196 @Main DelayableExecutor mainExecutor, 197 MediaDataManager mediaDataManager, 198 StatusBarStateController statusBarStateController, 199 SysuiColorExtractor colorExtractor, 200 KeyguardStateController keyguardStateController, 201 DumpManager dumpManager, 202 WallpaperManager wallpaperManager, 203 DisplayManager displayManager) { 204 mContext = context; 205 mMediaArtworkProcessor = mediaArtworkProcessor; 206 mKeyguardBypassController = keyguardBypassController; 207 mMediaListeners = new ArrayList<>(); 208 // TODO: use KeyguardStateController#isOccluded to remove this dependency 209 mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy; 210 mNotificationShadeWindowController = notificationShadeWindowController; 211 mVisibilityProvider = visibilityProvider; 212 mMainExecutor = mainExecutor; 213 mMediaDataManager = mediaDataManager; 214 mNotifPipeline = notifPipeline; 215 mNotifCollection = notifCollection; 216 mStatusBarStateController = statusBarStateController; 217 mColorExtractor = colorExtractor; 218 mKeyguardStateController = keyguardStateController; 219 mDisplayManager = displayManager; 220 mIsLockscreenLiveWallpaperEnabled = wallpaperManager.isLockscreenLiveWallpaperEnabled(); 221 222 setupNotifPipeline(); 223 224 dumpManager.registerDumpable(this); 225 } 226 setupNotifPipeline()227 private void setupNotifPipeline() { 228 mNotifPipeline.addCollectionListener(new NotifCollectionListener() { 229 @Override 230 public void onEntryAdded(@NonNull NotificationEntry entry) { 231 mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn()); 232 } 233 234 @Override 235 public void onEntryUpdated(NotificationEntry entry) { 236 mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn()); 237 } 238 239 @Override 240 public void onEntryBind(NotificationEntry entry, StatusBarNotification sbn) { 241 findAndUpdateMediaNotifications(); 242 } 243 244 @Override 245 public void onEntryRemoved(@NonNull NotificationEntry entry, int reason) { 246 removeEntry(entry); 247 } 248 249 @Override 250 public void onEntryCleanUp(@NonNull NotificationEntry entry) { 251 removeEntry(entry); 252 } 253 }); 254 255 mMediaDataManager.addListener(new MediaDataManager.Listener() { 256 @Override 257 public void onMediaDataLoaded(@NonNull String key, 258 @Nullable String oldKey, @NonNull MediaData data, boolean immediately, 259 int receivedSmartspaceCardLatency, boolean isSsReactivated) { 260 } 261 262 @Override 263 public void onSmartspaceMediaDataLoaded(@NonNull String key, 264 @NonNull SmartspaceMediaData data, boolean shouldPrioritize) { 265 } 266 267 @Override 268 public void onMediaDataRemoved(@NonNull String key) { 269 mNotifPipeline.getAllNotifs() 270 .stream() 271 .filter(entry -> Objects.equals(entry.getKey(), key)) 272 .findAny() 273 .ifPresent(entry -> { 274 // TODO(b/160713608): "removing" this notification won't happen and 275 // won't send the 'deleteIntent' if the notification is ongoing. 276 mNotifCollection.dismissNotification(entry, 277 getDismissedByUserStats(entry)); 278 }); 279 } 280 281 @Override 282 public void onSmartspaceMediaDataRemoved(@NonNull String key, boolean immediately) {} 283 }); 284 } 285 getDismissedByUserStats(NotificationEntry entry)286 private DismissedByUserStats getDismissedByUserStats(NotificationEntry entry) { 287 return new DismissedByUserStats( 288 NotificationStats.DISMISSAL_SHADE, // Add DISMISSAL_MEDIA? 289 NotificationStats.DISMISS_SENTIMENT_NEUTRAL, 290 mVisibilityProvider.obtain(entry, /* visible= */ true)); 291 } 292 removeEntry(NotificationEntry entry)293 private void removeEntry(NotificationEntry entry) { 294 onNotificationRemoved(entry.getKey()); 295 mMediaDataManager.onNotificationRemoved(entry.getKey()); 296 } 297 298 /** 299 * Check if a state should be considered actively playing 300 * @param state a PlaybackState 301 * @return true if playing 302 */ isPlayingState(int state)303 public static boolean isPlayingState(int state) { 304 return !PAUSED_MEDIA_STATES.contains(state) 305 && !CONNECTING_MEDIA_STATES.contains(state); 306 } 307 308 /** 309 * Check if a state should be considered as connecting 310 * @param state a PlaybackState 311 * @return true if connecting or buffering 312 */ isConnectingState(int state)313 public static boolean isConnectingState(int state) { 314 return CONNECTING_MEDIA_STATES.contains(state); 315 } 316 setUpWithPresenter(NotificationPresenter presenter)317 public void setUpWithPresenter(NotificationPresenter presenter) { 318 mPresenter = presenter; 319 } 320 onNotificationRemoved(String key)321 public void onNotificationRemoved(String key) { 322 if (key.equals(mMediaNotificationKey)) { 323 clearCurrentMediaNotification(); 324 dispatchUpdateMediaMetaData(true /* changed */, true /* allowEnterAnimation */); 325 } 326 } 327 328 @Nullable getMediaNotificationKey()329 public String getMediaNotificationKey() { 330 return mMediaNotificationKey; 331 } 332 getMediaMetadata()333 public MediaMetadata getMediaMetadata() { 334 return mMediaMetadata; 335 } 336 getMediaIcon()337 public Icon getMediaIcon() { 338 if (mMediaNotificationKey == null) { 339 return null; 340 } 341 return Optional.ofNullable(mNotifPipeline.getEntry(mMediaNotificationKey)) 342 .map(entry -> entry.getIcons().getShelfIcon()) 343 .map(StatusBarIconView::getSourceIcon) 344 .orElse(null); 345 } 346 addCallback(MediaListener callback)347 public void addCallback(MediaListener callback) { 348 mMediaListeners.add(callback); 349 callback.onPrimaryMetadataOrStateChanged(mMediaMetadata, 350 getMediaControllerPlaybackState(mMediaController)); 351 } 352 removeCallback(MediaListener callback)353 public void removeCallback(MediaListener callback) { 354 mMediaListeners.remove(callback); 355 } 356 findAndUpdateMediaNotifications()357 public void findAndUpdateMediaNotifications() { 358 boolean metaDataChanged; 359 // TODO(b/169655907): get the semi-filtered notifications for current user 360 Collection<NotificationEntry> allNotifications = mNotifPipeline.getAllNotifs(); 361 metaDataChanged = findPlayingMediaNotification(allNotifications); 362 dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */); 363 } 364 365 /** 366 * Find a notification and media controller associated with the playing media session, and 367 * update this manager's internal state. 368 * @return whether the current MediaMetadata changed (and needs to be announced to listeners). 369 */ findPlayingMediaNotification( @onNull Collection<NotificationEntry> allNotifications)370 boolean findPlayingMediaNotification( 371 @NonNull Collection<NotificationEntry> allNotifications) { 372 boolean metaDataChanged = false; 373 // Promote the media notification with a controller in 'playing' state, if any. 374 NotificationEntry mediaNotification = null; 375 MediaController controller = null; 376 for (NotificationEntry entry : allNotifications) { 377 Notification notif = entry.getSbn().getNotification(); 378 if (notif.isMediaNotification()) { 379 final MediaSession.Token token = 380 entry.getSbn().getNotification().extras.getParcelable( 381 Notification.EXTRA_MEDIA_SESSION, MediaSession.Token.class); 382 if (token != null) { 383 MediaController aController = new MediaController(mContext, token); 384 if (PlaybackState.STATE_PLAYING 385 == getMediaControllerPlaybackState(aController)) { 386 if (DEBUG_MEDIA) { 387 Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching " 388 + entry.getSbn().getKey()); 389 } 390 mediaNotification = entry; 391 controller = aController; 392 break; 393 } 394 } 395 } 396 } 397 398 if (controller != null && !sameSessions(mMediaController, controller)) { 399 // We have a new media session 400 clearCurrentMediaNotificationSession(); 401 mMediaController = controller; 402 mMediaController.registerCallback(mMediaListener); 403 mMediaMetadata = mMediaController.getMetadata(); 404 if (DEBUG_MEDIA) { 405 Log.v(TAG, "DEBUG_MEDIA: insert listener, found new controller: " 406 + mMediaController + ", receive metadata: " + mMediaMetadata); 407 } 408 409 metaDataChanged = true; 410 } 411 412 if (mediaNotification != null 413 && !mediaNotification.getSbn().getKey().equals(mMediaNotificationKey)) { 414 mMediaNotificationKey = mediaNotification.getSbn().getKey(); 415 if (DEBUG_MEDIA) { 416 Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key=" 417 + mMediaNotificationKey); 418 } 419 } 420 421 return metaDataChanged; 422 } 423 clearCurrentMediaNotification()424 public void clearCurrentMediaNotification() { 425 mMediaNotificationKey = null; 426 clearCurrentMediaNotificationSession(); 427 } 428 dispatchUpdateMediaMetaData(boolean changed, boolean allowEnterAnimation)429 private void dispatchUpdateMediaMetaData(boolean changed, boolean allowEnterAnimation) { 430 if (mPresenter != null) { 431 mPresenter.updateMediaMetaData(changed, allowEnterAnimation); 432 } 433 @PlaybackState.State int state = getMediaControllerPlaybackState(mMediaController); 434 ArrayList<MediaListener> callbacks = new ArrayList<>(mMediaListeners); 435 for (int i = 0; i < callbacks.size(); i++) { 436 callbacks.get(i).onPrimaryMetadataOrStateChanged(mMediaMetadata, state); 437 } 438 } 439 440 @Override dump(@onNull PrintWriter pw, @NonNull String[] args)441 public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { 442 pw.print(" mMediaNotificationKey="); 443 pw.println(mMediaNotificationKey); 444 pw.print(" mMediaController="); 445 pw.print(mMediaController); 446 if (mMediaController != null) { 447 pw.print(" state=" + mMediaController.getPlaybackState()); 448 } 449 pw.println(); 450 pw.print(" mMediaMetadata="); 451 pw.print(mMediaMetadata); 452 if (mMediaMetadata != null) { 453 pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE)); 454 } 455 pw.println(); 456 } 457 isPlaybackActive(int state)458 private boolean isPlaybackActive(int state) { 459 return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR 460 && state != PlaybackState.STATE_NONE; 461 } 462 sameSessions(MediaController a, MediaController b)463 private boolean sameSessions(MediaController a, MediaController b) { 464 if (a == b) { 465 return true; 466 } 467 if (a == null) { 468 return false; 469 } 470 return a.controlsSameSession(b); 471 } 472 getMediaControllerPlaybackState(MediaController controller)473 private int getMediaControllerPlaybackState(MediaController controller) { 474 if (controller != null) { 475 final PlaybackState playbackState = controller.getPlaybackState(); 476 if (playbackState != null) { 477 return playbackState.getState(); 478 } 479 } 480 return PlaybackState.STATE_NONE; 481 } 482 clearCurrentMediaNotificationSession()483 private void clearCurrentMediaNotificationSession() { 484 mMediaArtworkProcessor.clearCache(); 485 mMediaMetadata = null; 486 if (mMediaController != null) { 487 if (DEBUG_MEDIA) { 488 Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: " 489 + mMediaController.getPackageName()); 490 } 491 mMediaController.unregisterCallback(mMediaListener); 492 } 493 mMediaController = null; 494 } 495 496 /** 497 * Notify lockscreen wallpaper drawable the current internal display. 498 */ onDisplayUpdated(Display display)499 public void onDisplayUpdated(Display display) { 500 Trace.beginSection("NotificationMediaManager#onDisplayUpdated"); 501 mCurrentDisplay = display; 502 if (mWallapperDrawable != null) { 503 mWallapperDrawable.onDisplayUpdated(isOnSmallerInternalDisplays()); 504 } 505 Trace.endSection(); 506 } 507 isOnSmallerInternalDisplays()508 private boolean isOnSmallerInternalDisplays() { 509 if (mSmallerInternalDisplayUids == null) { 510 mSmallerInternalDisplayUids = findSmallerInternalDisplayUids(); 511 } 512 return mSmallerInternalDisplayUids.contains(mCurrentDisplay.getUniqueId()); 513 } 514 findSmallerInternalDisplayUids()515 private List<String> findSmallerInternalDisplayUids() { 516 if (mSmallerInternalDisplayUids != null) { 517 return mSmallerInternalDisplayUids; 518 } 519 List<Display> internalDisplays = Arrays.stream(mDisplayManager.getDisplays( 520 DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) 521 .filter(display -> display.getType() == Display.TYPE_INTERNAL) 522 .collect(Collectors.toList()); 523 if (internalDisplays.isEmpty()) { 524 return List.of(); 525 } 526 Display largestDisplay = internalDisplays.stream() 527 .max(Comparator.comparingInt(this::getRealDisplayArea)) 528 .orElse(internalDisplays.get(0)); 529 internalDisplays.remove(largestDisplay); 530 return internalDisplays.stream().map(Display::getUniqueId).collect(Collectors.toList()); 531 } 532 getRealDisplayArea(Display display)533 private int getRealDisplayArea(Display display) { 534 display.getRealSize(mTmpDisplaySize); 535 return mTmpDisplaySize.x * mTmpDisplaySize.y; 536 } 537 538 /** 539 * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper. 540 */ updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation)541 public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { 542 543 if (mIsLockscreenLiveWallpaperEnabled) return; 544 545 Trace.beginSection("CentralSurfaces#updateMediaMetaData"); 546 if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) { 547 Trace.endSection(); 548 return; 549 } 550 551 if (getBackDropView() == null) { 552 Trace.endSection(); 553 return; // called too early 554 } 555 556 boolean wakeAndUnlock = mBiometricUnlockController != null 557 && mBiometricUnlockController.isWakeAndUnlock(); 558 if (mKeyguardStateController.isLaunchTransitionFadingAway() || wakeAndUnlock) { 559 mBackdrop.setVisibility(View.INVISIBLE); 560 Trace.endSection(); 561 return; 562 } 563 564 MediaMetadata mediaMetadata = getMediaMetadata(); 565 566 if (DEBUG_MEDIA) { 567 Log.v(TAG, "DEBUG_MEDIA: updating album art for notification " 568 + getMediaNotificationKey() 569 + " metadata=" + mediaMetadata 570 + " metaDataChanged=" + metaDataChanged 571 + " state=" + mStatusBarStateController.getState()); 572 } 573 574 Bitmap artworkBitmap = null; 575 if (mediaMetadata != null && !mKeyguardBypassController.getBypassEnabled()) { 576 artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART); 577 if (artworkBitmap == null) { 578 artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); 579 } 580 } 581 582 // Process artwork on a background thread and send the resulting bitmap to 583 // finishUpdateMediaMetaData. 584 if (metaDataChanged) { 585 for (AsyncTask<?, ?, ?> task : mProcessArtworkTasks) { 586 task.cancel(true); 587 } 588 mProcessArtworkTasks.clear(); 589 } 590 if (artworkBitmap != null && !Utils.useQsMediaPlayer(mContext)) { 591 mProcessArtworkTasks.add(new ProcessArtworkTask(this, metaDataChanged, 592 allowEnterAnimation).execute(artworkBitmap)); 593 } else { 594 finishUpdateMediaMetaData(metaDataChanged, allowEnterAnimation, null); 595 } 596 597 Trace.endSection(); 598 } 599 finishUpdateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation, @Nullable Bitmap bmp)600 private void finishUpdateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation, 601 @Nullable Bitmap bmp) { 602 Drawable artworkDrawable = null; 603 if (bmp != null) { 604 artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp); 605 } 606 boolean hasMediaArtwork = artworkDrawable != null; 607 boolean allowWhenShade = false; 608 if (ENABLE_LOCKSCREEN_WALLPAPER && artworkDrawable == null) { 609 Bitmap lockWallpaper = 610 mLockscreenWallpaper != null ? mLockscreenWallpaper.getBitmap() : null; 611 if (lockWallpaper != null) { 612 artworkDrawable = new LockscreenWallpaper.WallpaperDrawable( 613 mBackdropBack.getResources(), lockWallpaper, isOnSmallerInternalDisplays()); 614 // We're in the SHADE mode on the SIM screen - yet we still need to show 615 // the lockscreen wallpaper in that mode. 616 allowWhenShade = mStatusBarStateController.getState() == KEYGUARD; 617 } 618 } 619 620 NotificationShadeWindowController windowController = 621 mNotificationShadeWindowController.get(); 622 boolean hideBecauseOccluded = 623 mCentralSurfacesOptionalLazy.get() 624 .map(CentralSurfaces::isOccluded).orElse(false); 625 626 final boolean hasArtwork = artworkDrawable != null; 627 mColorExtractor.setHasMediaArtwork(hasMediaArtwork); 628 if (mScrimController != null) { 629 mScrimController.setHasBackdrop(hasArtwork); 630 } 631 632 if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK) 633 && (mStatusBarStateController.getState() != StatusBarState.SHADE || allowWhenShade) 634 && mBiometricUnlockController != null && mBiometricUnlockController.getMode() 635 != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING 636 && !hideBecauseOccluded) { 637 // time to show some art! 638 if (mBackdrop.getVisibility() != View.VISIBLE) { 639 mBackdrop.setVisibility(View.VISIBLE); 640 if (allowEnterAnimation) { 641 mBackdrop.setAlpha(0); 642 mBackdrop.animate().alpha(1f); 643 } else { 644 mBackdrop.animate().cancel(); 645 mBackdrop.setAlpha(1f); 646 } 647 if (windowController != null) { 648 windowController.setBackdropShowing(true); 649 } 650 metaDataChanged = true; 651 if (DEBUG_MEDIA) { 652 Log.v(TAG, "DEBUG_MEDIA: Fading in album artwork"); 653 } 654 } 655 if (metaDataChanged) { 656 if (mBackdropBack.getDrawable() != null) { 657 Drawable drawable = 658 mBackdropBack.getDrawable().getConstantState() 659 .newDrawable(mBackdropFront.getResources()).mutate(); 660 mBackdropFront.setImageDrawable(drawable); 661 mBackdropFront.setAlpha(1f); 662 mBackdropFront.setVisibility(View.VISIBLE); 663 } else { 664 mBackdropFront.setVisibility(View.INVISIBLE); 665 } 666 667 if (DEBUG_MEDIA_FAKE_ARTWORK) { 668 final int c = 0xFF000000 | (int)(Math.random() * 0xFFFFFF); 669 Log.v(TAG, String.format("DEBUG_MEDIA: setting new color: 0x%08x", c)); 670 mBackdropBack.setBackgroundColor(0xFFFFFFFF); 671 mBackdropBack.setImageDrawable(new ColorDrawable(c)); 672 } else { 673 if (artworkDrawable instanceof LockscreenWallpaper.WallpaperDrawable) { 674 mWallapperDrawable = 675 (LockscreenWallpaper.WallpaperDrawable) artworkDrawable; 676 } 677 mBackdropBack.setImageDrawable(artworkDrawable); 678 } 679 680 if (mBackdropFront.getVisibility() == View.VISIBLE) { 681 if (DEBUG_MEDIA) { 682 Log.v(TAG, "DEBUG_MEDIA: Crossfading album artwork from " 683 + mBackdropFront.getDrawable() 684 + " to " 685 + mBackdropBack.getDrawable()); 686 } 687 mBackdropFront.animate() 688 .setDuration(250) 689 .alpha(0f).withEndAction(mHideBackdropFront); 690 } 691 } 692 } else { 693 // need to hide the album art, either because we are unlocked, on AOD 694 // or because the metadata isn't there to support it 695 if (mBackdrop.getVisibility() != View.GONE) { 696 if (DEBUG_MEDIA) { 697 Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork"); 698 } 699 boolean cannotAnimateDoze = mStatusBarStateController.isDozing() 700 && !ScrimState.AOD.getAnimateChange(); 701 if (((mBiometricUnlockController != null && mBiometricUnlockController.getMode() 702 == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING 703 || cannotAnimateDoze)) 704 || hideBecauseOccluded) { 705 // We are unlocking directly - no animation! 706 mBackdrop.setVisibility(View.GONE); 707 mBackdropBack.setImageDrawable(null); 708 if (windowController != null) { 709 windowController.setBackdropShowing(false); 710 } 711 } else { 712 if (windowController != null) { 713 windowController.setBackdropShowing(false); 714 } 715 mBackdrop.animate() 716 .alpha(0) 717 .setInterpolator(Interpolators.ACCELERATE_DECELERATE) 718 .setDuration(300) 719 .setStartDelay(0) 720 .withEndAction(() -> { 721 mBackdrop.setVisibility(View.GONE); 722 mBackdropFront.animate().cancel(); 723 mBackdropBack.setImageDrawable(null); 724 mMainExecutor.execute(mHideBackdropFront); 725 }); 726 if (mKeyguardStateController.isKeyguardFadingAway()) { 727 mBackdrop.animate() 728 .setDuration( 729 mKeyguardStateController.getShortenedFadingAwayDuration()) 730 .setStartDelay( 731 mKeyguardStateController.getKeyguardFadingAwayDelay()) 732 .setInterpolator(Interpolators.LINEAR) 733 .start(); 734 } 735 } 736 } 737 } 738 } 739 setup(BackDropView backdrop, ImageView backdropFront, ImageView backdropBack, ScrimController scrimController, LockscreenWallpaper lockscreenWallpaper)740 public void setup(BackDropView backdrop, ImageView backdropFront, ImageView backdropBack, 741 ScrimController scrimController, LockscreenWallpaper lockscreenWallpaper) { 742 mBackdrop = backdrop; 743 mBackdropFront = backdropFront; 744 mBackdropBack = backdropBack; 745 mScrimController = scrimController; 746 mLockscreenWallpaper = lockscreenWallpaper; 747 } 748 setBiometricUnlockController(BiometricUnlockController biometricUnlockController)749 public void setBiometricUnlockController(BiometricUnlockController biometricUnlockController) { 750 mBiometricUnlockController = biometricUnlockController; 751 } 752 753 /** 754 * Hide the album artwork that is fading out and release its bitmap. 755 */ 756 protected final Runnable mHideBackdropFront = new Runnable() { 757 @Override 758 public void run() { 759 if (DEBUG_MEDIA) { 760 Log.v(TAG, "DEBUG_MEDIA: removing fade layer"); 761 } 762 mBackdropFront.setVisibility(View.INVISIBLE); 763 mBackdropFront.animate().cancel(); 764 mBackdropFront.setImageDrawable(null); 765 } 766 }; 767 processArtwork(Bitmap artwork)768 private Bitmap processArtwork(Bitmap artwork) { 769 return mMediaArtworkProcessor.processArtwork(mContext, artwork); 770 } 771 772 @MainThread removeTask(AsyncTask<?, ?, ?> task)773 private void removeTask(AsyncTask<?, ?, ?> task) { 774 mProcessArtworkTasks.remove(task); 775 } 776 777 // TODO(b/273443374): remove isLockscreenWallpaperOnNotificationShade()778 public boolean isLockscreenWallpaperOnNotificationShade() { 779 return mBackdrop != null && mLockscreenWallpaper != null 780 && !mLockscreenWallpaper.isLockscreenLiveWallpaperEnabled() 781 && (mBackdropFront.isVisibleToUser() || mBackdropBack.isVisibleToUser()); 782 } 783 784 // TODO(b/273443374) temporary test helper; remove 785 @VisibleForTesting getBackDropView()786 BackDropView getBackDropView() { 787 return mBackdrop; 788 } 789 790 /** 791 * {@link AsyncTask} to prepare album art for use as backdrop on lock screen. 792 */ 793 private static final class ProcessArtworkTask extends AsyncTask<Bitmap, Void, Bitmap> { 794 795 private final WeakReference<NotificationMediaManager> mManagerRef; 796 private final boolean mMetaDataChanged; 797 private final boolean mAllowEnterAnimation; 798 ProcessArtworkTask(NotificationMediaManager manager, boolean changed, boolean allowAnimation)799 ProcessArtworkTask(NotificationMediaManager manager, boolean changed, 800 boolean allowAnimation) { 801 mManagerRef = new WeakReference<>(manager); 802 mMetaDataChanged = changed; 803 mAllowEnterAnimation = allowAnimation; 804 } 805 806 @Override doInBackground(Bitmap... bitmaps)807 protected Bitmap doInBackground(Bitmap... bitmaps) { 808 NotificationMediaManager manager = mManagerRef.get(); 809 if (manager == null || bitmaps.length == 0 || isCancelled()) { 810 return null; 811 } 812 return manager.processArtwork(bitmaps[0]); 813 } 814 815 @Override onPostExecute(@ullable Bitmap result)816 protected void onPostExecute(@Nullable Bitmap result) { 817 NotificationMediaManager manager = mManagerRef.get(); 818 if (manager != null && !isCancelled()) { 819 manager.removeTask(this); 820 manager.finishUpdateMediaMetaData(mMetaDataChanged, mAllowEnterAnimation, result); 821 } 822 } 823 824 @Override onCancelled(Bitmap result)825 protected void onCancelled(Bitmap result) { 826 if (result != null) { 827 result.recycle(); 828 } 829 NotificationMediaManager manager = mManagerRef.get(); 830 if (manager != null) { 831 manager.removeTask(this); 832 } 833 } 834 } 835 836 public interface MediaListener { 837 /** 838 * Called whenever there's new metadata or playback state. 839 * @param metadata Current metadata. 840 * @param state Current playback state 841 * @see PlaybackState.State 842 */ onPrimaryMetadataOrStateChanged(MediaMetadata metadata, @PlaybackState.State int state)843 default void onPrimaryMetadataOrStateChanged(MediaMetadata metadata, 844 @PlaybackState.State int state) {} 845 } 846 } 847