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