OSDN Git Service

Merge "docs: Add documentation for equals() method" into qt-dev
[android-x86/frameworks-base.git] / packages / SystemUI / src / com / android / systemui / statusbar / NotificationMediaManager.java
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.Dependency.MAIN_HANDLER;
19 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
20 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_MEDIA_FAKE_ARTWORK;
21 import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_LOCKSCREEN_WALLPAPER;
22 import static com.android.systemui.statusbar.phone.StatusBar.SHOW_LOCKSCREEN_MEDIA_ARTWORK;
23
24 import android.annotation.MainThread;
25 import android.annotation.Nullable;
26 import android.app.Notification;
27 import android.content.Context;
28 import android.graphics.Bitmap;
29 import android.graphics.drawable.BitmapDrawable;
30 import android.graphics.drawable.ColorDrawable;
31 import android.graphics.drawable.Drawable;
32 import android.graphics.drawable.Icon;
33 import android.media.MediaMetadata;
34 import android.media.session.MediaController;
35 import android.media.session.MediaSession;
36 import android.media.session.MediaSessionManager;
37 import android.media.session.PlaybackState;
38 import android.os.AsyncTask;
39 import android.os.Handler;
40 import android.os.Trace;
41 import android.os.UserHandle;
42 import android.provider.DeviceConfig;
43 import android.provider.DeviceConfig.Properties;
44 import android.util.ArraySet;
45 import android.util.Log;
46 import android.view.View;
47 import android.widget.ImageView;
48
49 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
50 import com.android.internal.statusbar.NotificationVisibility;
51 import com.android.systemui.Dependency;
52 import com.android.systemui.Dumpable;
53 import com.android.systemui.Interpolators;
54 import com.android.systemui.colorextraction.SysuiColorExtractor;
55 import com.android.systemui.plugins.statusbar.StatusBarStateController;
56 import com.android.systemui.statusbar.notification.NotificationEntryListener;
57 import com.android.systemui.statusbar.notification.NotificationEntryManager;
58 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
59 import com.android.systemui.statusbar.phone.BiometricUnlockController;
60 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
61 import com.android.systemui.statusbar.phone.ScrimController;
62 import com.android.systemui.statusbar.phone.ScrimState;
63 import com.android.systemui.statusbar.phone.ShadeController;
64 import com.android.systemui.statusbar.phone.StatusBarWindowController;
65 import com.android.systemui.statusbar.policy.KeyguardMonitor;
66
67 import java.io.FileDescriptor;
68 import java.io.PrintWriter;
69 import java.lang.ref.WeakReference;
70 import java.util.ArrayList;
71 import java.util.List;
72 import java.util.Set;
73
74 import javax.inject.Inject;
75 import javax.inject.Singleton;
76
77 import dagger.Lazy;
78
79 /**
80  * Handles tasks and state related to media notifications. For example, there is a 'current' media
81  * notification, which this class keeps track of.
82  */
83 @Singleton
84 public class NotificationMediaManager implements Dumpable {
85     private static final String TAG = "NotificationMediaManager";
86     public static final boolean DEBUG_MEDIA = false;
87
88     private final StatusBarStateController mStatusBarStateController
89             = Dependency.get(StatusBarStateController.class);
90     private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
91     private final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
92
93     // Late binding
94     private NotificationEntryManager mEntryManager;
95
96     // Late binding, also @Nullable due to being in com.android.systemui.statusbar.phone package
97     @Nullable
98     private Lazy<ShadeController> mShadeController;
99     @Nullable
100     private Lazy<StatusBarWindowController> mStatusBarWindowController;
101
102     @Nullable
103     private BiometricUnlockController mBiometricUnlockController;
104     @Nullable
105     private ScrimController mScrimController;
106     @Nullable
107     private LockscreenWallpaper mLockscreenWallpaper;
108
109     private final Handler mHandler = Dependency.get(MAIN_HANDLER);
110
111     private final Context mContext;
112     private final MediaSessionManager mMediaSessionManager;
113     private final ArrayList<MediaListener> mMediaListeners;
114     private final MediaArtworkProcessor mMediaArtworkProcessor;
115     private final Set<AsyncTask<?, ?, ?>> mProcessArtworkTasks = new ArraySet<>();
116
117     protected NotificationPresenter mPresenter;
118     private MediaController mMediaController;
119     private String mMediaNotificationKey;
120     private MediaMetadata mMediaMetadata;
121
122     private BackDropView mBackdrop;
123     private ImageView mBackdropFront;
124     private ImageView mBackdropBack;
125
126     private boolean mShowCompactMediaSeekbar;
127     private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
128             new DeviceConfig.OnPropertiesChangedListener() {
129         @Override
130         public void onPropertiesChanged(Properties properties) {
131             for (String name : properties.getKeyset()) {
132                 if (SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED.equals(name)) {
133                     String value = properties.getString(name, null);
134                     if (DEBUG_MEDIA) {
135                         Log.v(TAG, "DEBUG_MEDIA: compact media seekbar flag updated: " + value);
136                     }
137                     mShowCompactMediaSeekbar = "true".equals(value);
138                 }
139             }
140         }
141     };
142
143     private final MediaController.Callback mMediaListener = new MediaController.Callback() {
144         @Override
145         public void onPlaybackStateChanged(PlaybackState state) {
146             super.onPlaybackStateChanged(state);
147             if (DEBUG_MEDIA) {
148                 Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state);
149             }
150             if (state != null) {
151                 if (!isPlaybackActive(state.getState())) {
152                     clearCurrentMediaNotification();
153                 }
154                 dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */);
155             }
156         }
157
158         @Override
159         public void onMetadataChanged(MediaMetadata metadata) {
160             super.onMetadataChanged(metadata);
161             if (DEBUG_MEDIA) {
162                 Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
163             }
164             mMediaArtworkProcessor.clearCache();
165             mMediaMetadata = metadata;
166             dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */);
167         }
168     };
169
170     @Inject
171     public NotificationMediaManager(
172             Context context,
173             Lazy<ShadeController> shadeController,
174             Lazy<StatusBarWindowController> statusBarWindowController,
175             NotificationEntryManager notificationEntryManager,
176             MediaArtworkProcessor mediaArtworkProcessor) {
177         mContext = context;
178         mMediaArtworkProcessor = mediaArtworkProcessor;
179         mMediaListeners = new ArrayList<>();
180         mMediaSessionManager
181                 = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
182         // TODO: use MediaSessionManager.SessionListener to hook us up to future updates
183         // in session state
184         mShadeController = shadeController;
185         mStatusBarWindowController = statusBarWindowController;
186         mEntryManager = notificationEntryManager;
187         notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
188             @Override
189             public void onEntryRemoved(
190                     NotificationEntry entry,
191                     NotificationVisibility visibility,
192                     boolean removedByUser) {
193                 onNotificationRemoved(entry.key);
194             }
195         });
196
197         mShowCompactMediaSeekbar = "true".equals(
198                 DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
199                     SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED));
200
201         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
202                 mContext.getMainExecutor(),
203                 mPropertiesChangedListener);
204     }
205
206     public void setUpWithPresenter(NotificationPresenter presenter) {
207         mPresenter = presenter;
208     }
209
210     public void onNotificationRemoved(String key) {
211         if (key.equals(mMediaNotificationKey)) {
212             clearCurrentMediaNotification();
213             dispatchUpdateMediaMetaData(true /* changed */, true /* allowEnterAnimation */);
214         }
215     }
216
217     public String getMediaNotificationKey() {
218         return mMediaNotificationKey;
219     }
220
221     public MediaMetadata getMediaMetadata() {
222         return mMediaMetadata;
223     }
224
225     public boolean getShowCompactMediaSeekbar() {
226         return mShowCompactMediaSeekbar;
227     }
228
229     public Icon getMediaIcon() {
230         if (mMediaNotificationKey == null) {
231             return null;
232         }
233         synchronized (mEntryManager.getNotificationData()) {
234             NotificationEntry entry = mEntryManager.getNotificationData().get(mMediaNotificationKey);
235             if (entry == null || entry.expandedIcon == null) {
236                 return null;
237             }
238
239             return entry.expandedIcon.getSourceIcon();
240         }
241     }
242
243     public void addCallback(MediaListener callback) {
244         mMediaListeners.add(callback);
245         callback.onMetadataOrStateChanged(mMediaMetadata,
246                 getMediaControllerPlaybackState(mMediaController));
247     }
248
249     public void removeCallback(MediaListener callback) {
250         mMediaListeners.remove(callback);
251     }
252
253     public void findAndUpdateMediaNotifications() {
254         boolean metaDataChanged = false;
255
256         synchronized (mEntryManager.getNotificationData()) {
257             ArrayList<NotificationEntry> activeNotifications =
258                     mEntryManager.getNotificationData().getActiveNotifications();
259             final int N = activeNotifications.size();
260
261             // Promote the media notification with a controller in 'playing' state, if any.
262             NotificationEntry mediaNotification = null;
263             MediaController controller = null;
264             for (int i = 0; i < N; i++) {
265                 final NotificationEntry entry = activeNotifications.get(i);
266
267                 if (entry.isMediaNotification()) {
268                     final MediaSession.Token token =
269                             entry.notification.getNotification().extras.getParcelable(
270                                     Notification.EXTRA_MEDIA_SESSION);
271                     if (token != null) {
272                         MediaController aController = new MediaController(mContext, token);
273                         if (PlaybackState.STATE_PLAYING ==
274                                 getMediaControllerPlaybackState(aController)) {
275                             if (DEBUG_MEDIA) {
276                                 Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching "
277                                         + entry.notification.getKey());
278                             }
279                             mediaNotification = entry;
280                             controller = aController;
281                             break;
282                         }
283                     }
284                 }
285             }
286             if (mediaNotification == null) {
287                 // Still nothing? OK, let's just look for live media sessions and see if they match
288                 // one of our notifications. This will catch apps that aren't (yet!) using media
289                 // notifications.
290
291                 if (mMediaSessionManager != null) {
292                     // TODO: Should this really be for all users?
293                     final List<MediaController> sessions
294                             = mMediaSessionManager.getActiveSessionsForUser(
295                             null,
296                             UserHandle.USER_ALL);
297
298                     for (MediaController aController : sessions) {
299                         if (PlaybackState.STATE_PLAYING ==
300                                 getMediaControllerPlaybackState(aController)) {
301                             // now to see if we have one like this
302                             final String pkg = aController.getPackageName();
303
304                             for (int i = 0; i < N; i++) {
305                                 final NotificationEntry entry = activeNotifications.get(i);
306                                 if (entry.notification.getPackageName().equals(pkg)) {
307                                     if (DEBUG_MEDIA) {
308                                         Log.v(TAG, "DEBUG_MEDIA: found controller matching "
309                                                 + entry.notification.getKey());
310                                     }
311                                     controller = aController;
312                                     mediaNotification = entry;
313                                     break;
314                                 }
315                             }
316                         }
317                     }
318                 }
319             }
320
321             if (controller != null && !sameSessions(mMediaController, controller)) {
322                 // We have a new media session
323                 clearCurrentMediaNotificationSession();
324                 mMediaController = controller;
325                 mMediaController.registerCallback(mMediaListener);
326                 mMediaMetadata = mMediaController.getMetadata();
327                 if (DEBUG_MEDIA) {
328                     Log.v(TAG, "DEBUG_MEDIA: insert listener, found new controller: "
329                             + mMediaController + ", receive metadata: " + mMediaMetadata);
330                 }
331
332                 metaDataChanged = true;
333             }
334
335             if (mediaNotification != null
336                     && !mediaNotification.notification.getKey().equals(mMediaNotificationKey)) {
337                 mMediaNotificationKey = mediaNotification.notification.getKey();
338                 if (DEBUG_MEDIA) {
339                     Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key="
340                             + mMediaNotificationKey);
341                 }
342             }
343         }
344
345         if (metaDataChanged) {
346             mEntryManager.updateNotifications();
347         }
348
349         dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */);
350     }
351
352     public void clearCurrentMediaNotification() {
353         mMediaNotificationKey = null;
354         clearCurrentMediaNotificationSession();
355     }
356
357     private void dispatchUpdateMediaMetaData(boolean changed, boolean allowEnterAnimation) {
358         if (mPresenter != null) {
359             mPresenter.updateMediaMetaData(changed, allowEnterAnimation);
360         }
361         @PlaybackState.State int state = getMediaControllerPlaybackState(mMediaController);
362         ArrayList<MediaListener> callbacks = new ArrayList<>(mMediaListeners);
363         for (int i = 0; i < callbacks.size(); i++) {
364             callbacks.get(i).onMetadataOrStateChanged(mMediaMetadata, state);
365         }
366     }
367
368     @Override
369     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
370         pw.print("    mMediaSessionManager=");
371         pw.println(mMediaSessionManager);
372         pw.print("    mMediaNotificationKey=");
373         pw.println(mMediaNotificationKey);
374         pw.print("    mMediaController=");
375         pw.print(mMediaController);
376         if (mMediaController != null) {
377             pw.print(" state=" + mMediaController.getPlaybackState());
378         }
379         pw.println();
380         pw.print("    mMediaMetadata=");
381         pw.print(mMediaMetadata);
382         if (mMediaMetadata != null) {
383             pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE));
384         }
385         pw.println();
386     }
387
388     private boolean isPlaybackActive(int state) {
389         return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR
390                 && state != PlaybackState.STATE_NONE;
391     }
392
393     private boolean sameSessions(MediaController a, MediaController b) {
394         if (a == b) {
395             return true;
396         }
397         if (a == null) {
398             return false;
399         }
400         return a.controlsSameSession(b);
401     }
402
403     private int getMediaControllerPlaybackState(MediaController controller) {
404         if (controller != null) {
405             final PlaybackState playbackState = controller.getPlaybackState();
406             if (playbackState != null) {
407                 return playbackState.getState();
408             }
409         }
410         return PlaybackState.STATE_NONE;
411     }
412
413     private void clearCurrentMediaNotificationSession() {
414         mMediaArtworkProcessor.clearCache();
415         mMediaMetadata = null;
416         if (mMediaController != null) {
417             if (DEBUG_MEDIA) {
418                 Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
419                         + mMediaController.getPackageName());
420             }
421             mMediaController.unregisterCallback(mMediaListener);
422         }
423         mMediaController = null;
424     }
425
426     /**
427      * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
428      */
429     public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
430         Trace.beginSection("StatusBar#updateMediaMetaData");
431         if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) {
432             Trace.endSection();
433             return;
434         }
435
436         if (mBackdrop == null) {
437             Trace.endSection();
438             return; // called too early
439         }
440
441         boolean wakeAndUnlock = mBiometricUnlockController != null
442             && mBiometricUnlockController.isWakeAndUnlock();
443         if (mKeyguardMonitor.isLaunchTransitionFadingAway() || wakeAndUnlock) {
444             mBackdrop.setVisibility(View.INVISIBLE);
445             Trace.endSection();
446             return;
447         }
448
449         MediaMetadata mediaMetadata = getMediaMetadata();
450
451         if (DEBUG_MEDIA) {
452             Log.v(TAG, "DEBUG_MEDIA: updating album art for notification "
453                     + getMediaNotificationKey()
454                     + " metadata=" + mediaMetadata
455                     + " metaDataChanged=" + metaDataChanged
456                     + " state=" + mStatusBarStateController.getState());
457         }
458
459         Bitmap artworkBitmap = null;
460         if (mediaMetadata != null) {
461             artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
462             if (artworkBitmap == null) {
463                 artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
464             }
465         }
466
467         // Process artwork on a background thread and send the resulting bitmap to
468         // finishUpdateMediaMetaData.
469         if (metaDataChanged) {
470             for (AsyncTask<?, ?, ?> task : mProcessArtworkTasks) {
471                 task.cancel(true);
472             }
473             mProcessArtworkTasks.clear();
474         }
475         if (artworkBitmap != null) {
476             mProcessArtworkTasks.add(new ProcessArtworkTask(this, metaDataChanged,
477                     allowEnterAnimation).execute(artworkBitmap));
478         } else {
479             finishUpdateMediaMetaData(metaDataChanged, allowEnterAnimation, null);
480         }
481
482         Trace.endSection();
483     }
484
485     private void finishUpdateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation,
486             @Nullable Bitmap bmp) {
487         Drawable artworkDrawable = null;
488         if (bmp != null) {
489             artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp);
490         }
491         boolean hasMediaArtwork = artworkDrawable != null;
492         boolean allowWhenShade = false;
493         if (ENABLE_LOCKSCREEN_WALLPAPER && artworkDrawable == null) {
494             Bitmap lockWallpaper =
495                     mLockscreenWallpaper != null ? mLockscreenWallpaper.getBitmap() : null;
496             if (lockWallpaper != null) {
497                 artworkDrawable = new LockscreenWallpaper.WallpaperDrawable(
498                         mBackdropBack.getResources(), lockWallpaper);
499                 // We're in the SHADE mode on the SIM screen - yet we still need to show
500                 // the lockscreen wallpaper in that mode.
501                 allowWhenShade = mStatusBarStateController.getState() == KEYGUARD;
502             }
503         }
504
505         ShadeController shadeController = mShadeController.get();
506         StatusBarWindowController windowController = mStatusBarWindowController.get();
507         boolean hideBecauseOccluded = shadeController != null && shadeController.isOccluded();
508
509         final boolean hasArtwork = artworkDrawable != null;
510         mColorExtractor.setHasMediaArtwork(hasMediaArtwork);
511         if (mScrimController != null) {
512             mScrimController.setHasBackdrop(hasArtwork);
513         }
514
515         if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)
516                 && (mStatusBarStateController.getState() != StatusBarState.SHADE || allowWhenShade)
517                 &&  mBiometricUnlockController != null && mBiometricUnlockController.getMode()
518                         != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
519                 && !hideBecauseOccluded) {
520             // time to show some art!
521             if (mBackdrop.getVisibility() != View.VISIBLE) {
522                 mBackdrop.setVisibility(View.VISIBLE);
523                 if (allowEnterAnimation) {
524                     mBackdrop.setAlpha(0);
525                     mBackdrop.animate().alpha(1f);
526                 } else {
527                     mBackdrop.animate().cancel();
528                     mBackdrop.setAlpha(1f);
529                 }
530                 if (windowController != null) {
531                     windowController.setBackdropShowing(true);
532                 }
533                 metaDataChanged = true;
534                 if (DEBUG_MEDIA) {
535                     Log.v(TAG, "DEBUG_MEDIA: Fading in album artwork");
536                 }
537             }
538             if (metaDataChanged) {
539                 if (mBackdropBack.getDrawable() != null) {
540                     Drawable drawable =
541                             mBackdropBack.getDrawable().getConstantState()
542                                     .newDrawable(mBackdropFront.getResources()).mutate();
543                     mBackdropFront.setImageDrawable(drawable);
544                     mBackdropFront.setAlpha(1f);
545                     mBackdropFront.setVisibility(View.VISIBLE);
546                 } else {
547                     mBackdropFront.setVisibility(View.INVISIBLE);
548                 }
549
550                 if (DEBUG_MEDIA_FAKE_ARTWORK) {
551                     final int c = 0xFF000000 | (int)(Math.random() * 0xFFFFFF);
552                     Log.v(TAG, String.format("DEBUG_MEDIA: setting new color: 0x%08x", c));
553                     mBackdropBack.setBackgroundColor(0xFFFFFFFF);
554                     mBackdropBack.setImageDrawable(new ColorDrawable(c));
555                 } else {
556                     mBackdropBack.setImageDrawable(artworkDrawable);
557                 }
558
559                 if (mBackdropFront.getVisibility() == View.VISIBLE) {
560                     if (DEBUG_MEDIA) {
561                         Log.v(TAG, "DEBUG_MEDIA: Crossfading album artwork from "
562                                 + mBackdropFront.getDrawable()
563                                 + " to "
564                                 + mBackdropBack.getDrawable());
565                     }
566                     mBackdropFront.animate()
567                             .setDuration(250)
568                             .alpha(0f).withEndAction(mHideBackdropFront);
569                 }
570             }
571         } else {
572             // need to hide the album art, either because we are unlocked, on AOD
573             // or because the metadata isn't there to support it
574             if (mBackdrop.getVisibility() != View.GONE) {
575                 if (DEBUG_MEDIA) {
576                     Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork");
577                 }
578                 boolean cannotAnimateDoze = shadeController != null
579                         && shadeController.isDozing()
580                         && !ScrimState.AOD.getAnimateChange();
581                 if (mBiometricUnlockController != null && mBiometricUnlockController.getMode()
582                         == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
583                         || hideBecauseOccluded || cannotAnimateDoze) {
584
585                     // We are unlocking directly - no animation!
586                     mBackdrop.setVisibility(View.GONE);
587                     mBackdropBack.setImageDrawable(null);
588                     if (windowController != null) {
589                         windowController.setBackdropShowing(false);
590                     }
591                 } else {
592                     if (windowController != null) {
593                         windowController.setBackdropShowing(false);
594                     }
595                     mBackdrop.animate()
596                             .alpha(0)
597                             .setInterpolator(Interpolators.ACCELERATE_DECELERATE)
598                             .setDuration(300)
599                             .setStartDelay(0)
600                             .withEndAction(() -> {
601                                 mBackdrop.setVisibility(View.GONE);
602                                 mBackdropFront.animate().cancel();
603                                 mBackdropBack.setImageDrawable(null);
604                                 mHandler.post(mHideBackdropFront);
605                             });
606                     if (mKeyguardMonitor.isKeyguardFadingAway()) {
607                         mBackdrop.animate()
608                                 // Make it disappear faster, as the focus should be on the activity
609                                 // behind.
610                                 .setDuration(mKeyguardMonitor.getKeyguardFadingAwayDuration() / 2)
611                                 .setStartDelay(mKeyguardMonitor.getKeyguardFadingAwayDelay())
612                                 .setInterpolator(Interpolators.LINEAR)
613                                 .start();
614                     }
615                 }
616             }
617         }
618     }
619
620     public void setup(BackDropView backdrop, ImageView backdropFront, ImageView backdropBack,
621             ScrimController scrimController, LockscreenWallpaper lockscreenWallpaper) {
622         mBackdrop = backdrop;
623         mBackdropFront = backdropFront;
624         mBackdropBack = backdropBack;
625         mScrimController = scrimController;
626         mLockscreenWallpaper = lockscreenWallpaper;
627     }
628
629     public void setBiometricUnlockController(BiometricUnlockController biometricUnlockController) {
630         mBiometricUnlockController = biometricUnlockController;
631     }
632
633     /**
634      * Hide the album artwork that is fading out and release its bitmap.
635      */
636     protected final Runnable mHideBackdropFront = new Runnable() {
637         @Override
638         public void run() {
639             if (DEBUG_MEDIA) {
640                 Log.v(TAG, "DEBUG_MEDIA: removing fade layer");
641             }
642             mBackdropFront.setVisibility(View.INVISIBLE);
643             mBackdropFront.animate().cancel();
644             mBackdropFront.setImageDrawable(null);
645         }
646     };
647
648     private Bitmap processArtwork(Bitmap artwork) {
649         return mMediaArtworkProcessor.processArtwork(mContext, artwork);
650     }
651
652     @MainThread
653     private void removeTask(AsyncTask<?, ?, ?> task) {
654         mProcessArtworkTasks.remove(task);
655     }
656
657     /**
658      * {@link AsyncTask} to prepare album art for use as backdrop on lock screen.
659      */
660     private static final class ProcessArtworkTask extends AsyncTask<Bitmap, Void, Bitmap> {
661
662         private final WeakReference<NotificationMediaManager> mManagerRef;
663         private final boolean mMetaDataChanged;
664         private final boolean mAllowEnterAnimation;
665
666         ProcessArtworkTask(NotificationMediaManager manager, boolean changed,
667                 boolean allowAnimation) {
668             mManagerRef = new WeakReference<>(manager);
669             mMetaDataChanged = changed;
670             mAllowEnterAnimation = allowAnimation;
671         }
672
673         @Override
674         protected Bitmap doInBackground(Bitmap... bitmaps) {
675             NotificationMediaManager manager = mManagerRef.get();
676             if (manager == null || bitmaps.length == 0 || isCancelled()) {
677                 return null;
678             }
679             return manager.processArtwork(bitmaps[0]);
680         }
681
682         @Override
683         protected void onPostExecute(@Nullable Bitmap result) {
684             NotificationMediaManager manager = mManagerRef.get();
685             if (manager != null && !isCancelled()) {
686                 manager.removeTask(this);
687                 manager.finishUpdateMediaMetaData(mMetaDataChanged, mAllowEnterAnimation, result);
688             }
689         }
690
691         @Override
692         protected void onCancelled(Bitmap result) {
693             if (result != null) {
694                 result.recycle();
695             }
696             NotificationMediaManager manager = mManagerRef.get();
697             if (manager != null) {
698                 manager.removeTask(this);
699             }
700         }
701     }
702
703     public interface MediaListener {
704         /**
705          * Called whenever there's new metadata or playback state.
706          * @param metadata Current metadata.
707          * @param state Current playback state
708          * @see PlaybackState.State
709          */
710         void onMetadataOrStateChanged(MediaMetadata metadata, @PlaybackState.State int state);
711     }
712 }