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