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