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