Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 16128f45 authored by Dan Sandler's avatar Dan Sandler
Browse files

Album artwork returns triumphantly to the lockscreen.

It is pulled from the metadata of the active media
session, which we fetch in one of two ways:

1. From a proper media notification, we can extract the
   session token from EXTRA_MEDIA_SESSION.
2. If none of these is found, we ask MediaSessionManager for
   any outstanding media sessions whose packages match an
   active notification. These include legacy RCC clients,
   whose notifications are oblivious to EXTRA_MEDIA_SESSION.

We then pull the album artwork out of the metadata and fade
it in on the lockscreen.

Bug: 15147533
Bug: 15135254
Change-Id: Ia967bfba310b6c792b14673872cb132ddd7052a5
parent c20533c1
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -26,6 +26,23 @@
    android:fitsSystemWindows="true"
    android:descendantFocusability="afterDescendants">

    <FrameLayout
            android:id="@+id/backdrop"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="gone"
            >
        <ImageView android:id="@+id/backdrop_back"
                   android:layout_width="match_parent"
                   android:scaleType="centerCrop"
                   android:layout_height="match_parent" />
        <ImageView android:id="@+id/backdrop_front"
                   android:layout_width="match_parent"
                   android:layout_height="match_parent"
                   android:scaleType="centerCrop"
                   android:visibility="invisible" />
    </FrameLayout>

    <View android:id="@+id/scrim_behind"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
+1 −1
Original line number Diff line number Diff line
@@ -581,7 +581,7 @@ public abstract class BaseStatusBar extends SystemUI implements
        }
    }

    private boolean isMediaNotification(NotificationData.Entry entry) {
    public boolean isMediaNotification(NotificationData.Entry entry) {
        // TODO: confirm that there's a valid media key
        return entry.expandedBig != null &&
               entry.expandedBig.findViewById(com.android.internal.R.id.media_action_area) != null;
+311 −2
Original line number Diff line number Diff line
@@ -45,15 +45,23 @@ import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.InputMethodService;
import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -91,6 +99,7 @@ import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

@@ -140,6 +149,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
        DragDownHelper.OnDragDownListener, ActivityStarter {
@@ -148,17 +158,19 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
    public static final boolean SPEW = false;
    public static final boolean DUMPTRUCK = true; // extra dumpsys info
    public static final boolean DEBUG_GESTURES = false;
    public static final boolean DEBUG_MEDIA = false;
    public static final boolean DEBUG_MEDIA_FAKE_ARTWORK = false;

    public static final boolean DEBUG_WINDOW_STATE = false;

    public static final boolean SETTINGS_DRAG_SHORTCUT = true;

    // additional instrumentation for testing purposes; intended to be left on during development
    public static final boolean CHATTY = DEBUG;

    public static final String ACTION_STATUSBAR_START
            = "com.android.internal.policy.statusbar.START";

    public static final boolean SHOW_LOCKSCREEN_MEDIA_ARTWORK = true;

    private static final int MSG_OPEN_NOTIFICATION_PANEL = 1000;
    private static final int MSG_CLOSE_PANELS = 1001;
    private static final int MSG_OPEN_SETTINGS_PANEL = 1002;
@@ -378,6 +390,38 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
    private Interpolator mAlphaIn = new PathInterpolator(0f, 0.2f, 1f, 1f);
    private Interpolator mAlphaOut = new PathInterpolator(0f, 0f, 0.8f, 1f);

    private FrameLayout mBackdrop;
    private ImageView mBackdropFront, mBackdropBack;

    private MediaSessionManager mMediaSessionManager;
    private MediaController mMediaController;
    private String mMediaNotificationKey;
    private MediaMetadata mMediaMetadata;
    private MediaController.Callback mMediaListener
            = new MediaController.Callback() {
        @Override
        public void onPlaybackStateChanged(PlaybackState state) {
            super.onPlaybackStateChanged(state);
            if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state);
        }

        @Override
        public void onMetadataChanged(MediaMetadata metadata) {
            super.onMetadataChanged(metadata);
            if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
            mMediaMetadata = metadata;
            updateMediaMetaData(true);
        }
    };

    private final OnChildLocationsChangedListener mOnChildLocationsChangedListener =
            new OnChildLocationsChangedListener() {
        @Override
        public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout) {
            userActivity();
        }
    };

    private int mDisabledUnmodified;

    /** Keys of notifications currently visible to the user. */
@@ -480,6 +524,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
        updateDisplaySize();
        super.start(); // calls createAndAddWindows()

        mMediaSessionManager
                = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
        // TODO: use MediaSessionManager.SessionListener to hook us up to future updates
        // in session state

        addNavigationBar();

        // Lastly, call to the icon policy to install/update all the icons.
@@ -710,6 +759,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
            mHeader.setQSPanel(mQSPanel);
        }

        mBackdrop = (FrameLayout) mStatusBarWindow.findViewById(R.id.backdrop);
        mBackdropFront = (ImageView) mBackdrop.findViewById(R.id.backdrop_front);
        mBackdropBack = (ImageView) mBackdrop.findViewById(R.id.backdrop_back);

        // User info. Trigger first load.
        mHeader.setUserInfoController(mUserInfoController);
        mUserInfoController.reloadUserInfo();
@@ -725,6 +778,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
        filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_SCREEN_ON);
        if (DEBUG_MEDIA_FAKE_ARTWORK) {
            filter.addAction("fake_artwork");
        }
        filter.addAction(ACTION_DEMO);
        context.registerReceiver(mBroadcastReceiver, filter);

@@ -1409,9 +1465,240 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
                .start();
        }

        findAndUpdateMediaNotifications();

        updateCarrierLabelVisibility(false);
    }

    public void findAndUpdateMediaNotifications() {
        boolean metaDataChanged = false;

        synchronized (mNotificationData) {
            final int N = mNotificationData.size();
            Entry mediaNotification = null;
            MediaController controller = null;
            for (int i=0; i<N; i++) {
                final Entry entry = mNotificationData.get(i);
                if (isMediaNotification(entry)) {
                    final MediaSession.Token token = entry.notification.getNotification().extras
                            .getParcelable(Notification.EXTRA_MEDIA_SESSION);
                    if (token != null) {
                        controller = new MediaController(token);
                        if (controller != null) {
                            // we've got a live one, here
                            mediaNotification = entry;
                        }
                    }
                }
            }

            if (mediaNotification == null) {
                // Still nothing? OK, let's just look for live media sessions and see if they match
                // one of our notifications. This will catch apps that aren't (yet!) using media
                // notifications.

                if (mMediaSessionManager != null) {
                    final List<MediaController> sessions
                            = mMediaSessionManager.getActiveSessionsForUser(
                                    null,
                                    UserHandle.USER_ALL);

                    for (MediaController aController : sessions) {
                        if (aController == null) continue;
                        final PlaybackState state = aController.getPlaybackState();
                        if (state == null) continue;
                        switch (state.getState()) {
                            case PlaybackState.STATE_STOPPED:
                            case PlaybackState.STATE_ERROR:
                                continue;
                            default:
                                // now to see if we have one like this
                                final String pkg = aController.getSessionInfo().getPackageName();

                                for (int i = 0; i < N; i++) {
                                    final Entry entry = mNotificationData.get(i);
                                    if (entry.notification.getPackageName().equals(pkg)) {
                                        if (DEBUG_MEDIA) {
                                            Log.v(TAG, "DEBUG_MEDIA: found controller matching "
                                                + entry.notification.getKey());
                                        }
                                        controller = aController;
                                        mediaNotification = entry;
                                        break;
                                    }
                                }
                        }
                    }
                }
            }

            if (controller != mMediaController) {
                // We have a new media session

                if (mMediaController != null) {
                    // something old was playing
                    Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
                            + mMediaController);
                    mMediaController.removeCallback(mMediaListener);
                }
                mMediaController = controller;

                if (mMediaController != null) {
                    mMediaController.addCallback(mMediaListener);
                    mMediaMetadata = mMediaController.getMetadata();
                    if (DEBUG_MEDIA) {
                        Log.v(TAG, "DEBUG_MEDIA: insert listener, receive metadata: "
                                + mMediaMetadata);
                    }

                    final String notificationKey = mediaNotification == null
                            ? null
                            : mediaNotification.notification.getKey();

                    if (notificationKey == null || !notificationKey.equals(mMediaNotificationKey)) {
                        // we have a new notification!
                        if (DEBUG_MEDIA) {
                            Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key="
                                    + notificationKey + " controller=" + controller);
                        }
                        mMediaNotificationKey = notificationKey;
                    }
                } else {
                    mMediaMetadata = null;
                    mMediaNotificationKey = null;
                }

                metaDataChanged = true;
            } else {
                // Media session unchanged

                if (DEBUG_MEDIA) {
                    Log.v(TAG, "DEBUG_MEDIA: Continuing media notification: key=" + mMediaNotificationKey);
                }
            }
        }

        updateMediaMetaData(metaDataChanged);
    }

    private void removeAndRecycleImageViewDrawable(ImageView iv) {
        Bitmap oldBitmap = null;
        final Drawable drawable = iv.getDrawable();
        if (drawable != null && drawable instanceof BitmapDrawable) {
            oldBitmap = ((BitmapDrawable) drawable).getBitmap();
        }
        iv.animate().cancel();
        iv.setImageDrawable(null);
        if (oldBitmap != null) {
            if (DEBUG_MEDIA) {
                Log.v(TAG, "DEBUG_MEDIA: recycling bitmap " + oldBitmap + " from ImageView " + iv);
            }
            oldBitmap.recycle();
        }
    }

    /**
     * Hide the album artwork that is fading out and release its memory.
     */
    private Runnable mHideBackdropFront = new Runnable() {
        @Override
        public void run() {
            if (DEBUG_MEDIA) {
                Log.v(TAG, "DEBUG_MEDIA: removing fade layer");
            }
            mBackdropFront.setVisibility(View.INVISIBLE);
            removeAndRecycleImageViewDrawable(mBackdropFront);
        }
    };

    /**
     * Refresh or remove lockscreen artwork from media metadata.
     */
    public void updateMediaMetaData(boolean metaDataChanged) {
        if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) return;

        if (mBackdrop == null) return; // called too early

        if (DEBUG_MEDIA) {
            Log.v(TAG, "DEBUG_MEDIA: updating album art for notification " + mMediaNotificationKey
                + " metadata=" + mMediaMetadata
                + " metaDataChanged=" + metaDataChanged
                + " state=" + mState);
        }

        Bitmap artworkBitmap = null;
        if (mMediaMetadata != null) {
            artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
            if (artworkBitmap == null) {
                artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
                // might still be null
            }
        }

        final boolean hasArtwork = artworkBitmap != null;

        if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)
                && (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)) {
            // time to show some art!
            if (mBackdrop.getVisibility() != View.VISIBLE) {
                mBackdrop.setVisibility(View.VISIBLE);
                mBackdrop.animate().alpha(1f);
                metaDataChanged = true;
                if (DEBUG_MEDIA) {
                    Log.v(TAG, "DEBUG_MEDIA: Fading in album artwork");
                }
            }
            if (metaDataChanged) {
                if (mBackdropBack.getDrawable() != null) {
                    mBackdropFront.setImageDrawable(mBackdropBack.getDrawable());
                    mBackdropFront.setAlpha(1f);
                    mBackdropFront.setVisibility(View.VISIBLE);
                } else {
                    mBackdropFront.setVisibility(View.INVISIBLE);
                }

                if (DEBUG_MEDIA_FAKE_ARTWORK) {
                    final int c = 0xFF000000 | (int)(Math.random() * 0xFFFFFF);
                    Log.v(TAG, String.format("DEBUG_MEDIA: setting new color: 0x%08x", c));
                    mBackdropBack.setBackgroundColor(0xFFFFFFFF);
                    mBackdropBack.setImageDrawable(new ColorDrawable(c));
                } else {
                    mBackdropBack.setImageBitmap(artworkBitmap);
                }

                if (mBackdropFront.getVisibility() == View.VISIBLE) {
                    if (DEBUG_MEDIA) {
                        Log.v(TAG, "DEBUG_MEDIA: Crossfading album artwork from "
                                + mBackdropFront.getDrawable()
                                + " to "
                                + mBackdropBack.getDrawable());
                    }
                    mBackdropFront.animate().withLayer()
                            .setDuration(250)
                            .alpha(0f).withEndAction(mHideBackdropFront);
                }
            }
        } else {
            // need to hide the album art, either because we are unlocked or because
            // the metadata isn't there to support it
            if (mBackdrop.getVisibility() != View.GONE) {
                if (DEBUG_MEDIA) {
                    Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork");
                }
                mBackdrop.animate().withLayer()
                        .alpha(0f).withEndAction(new Runnable() {
                    @Override
                    public void run() {
                        mBackdrop.setVisibility(View.GONE);
                        mBackdropFront.animate().cancel();
                        mBackdropBack.animate().cancel();
                        mHandler.post(mHideBackdropFront);
                    }
                });
            }
        }
    }

    public void showClock(boolean show) {
        if (mStatusBarView == null) return;
        View clock = mStatusBarView.findViewById(R.id.clock);
@@ -2272,6 +2559,23 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
            mNavigationBarView.dump(fd, pw, args);
        }

        pw.print("  mMediaSessionManager=");
        pw.println(mMediaSessionManager);
        pw.print("  mMediaNotificationKey=");
        pw.println(mMediaNotificationKey);
        pw.print("  mMediaController=");
        pw.print(mMediaController);
        if (mMediaController != null) {
            pw.print(" state=" + mMediaController.getPlaybackState());
        }
        pw.println();
        pw.print("  mMediaMetadata=");
        pw.print(mMediaMetadata);
        if (mMediaMetadata != null) {
            pw.print(" title=" + mMediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE));
        }
        pw.println();

        pw.println("  Panels: ");
        if (mNotificationPanel != null) {
            pw.println("    mNotificationPanel=" +
@@ -2453,6 +2757,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
                        }
                    }
                }
            } else if ("fake_artwork".equals(action)) {
                if (DEBUG_MEDIA_FAKE_ARTWORK) {
                    updateMediaMetaData(true);
                }
            }
        }
    };
@@ -2890,6 +3198,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
        updateNotifications();
        checkBarModes();
        updateCarrierLabelVisibility(false);
        updateMediaMetaData(false);
    }

    private void updateDozingState() {