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