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