diff options
author | 2014-07-17 04:05:16 +0000 | |
---|---|---|
committer | 2014-07-16 23:37:00 +0000 | |
commit | d10b2f17a1ef2773932e9594a7569627426bb823 (patch) | |
tree | 83adaad99fde1bfd2a9f909c94cccb2fb51daca7 | |
parent | 1b68d262cd25428640f3f278402046312759f57d (diff) | |
parent | 16128f4523a484084205aacc6200374ca3029467 (diff) |
Merge "Album artwork returns triumphantly to the lockscreen." into lmp-dev
3 files changed, 329 insertions, 3 deletions
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml index e84300d1201f..e3ac1c1cdde6 100644 --- a/packages/SystemUI/res/layout/super_status_bar.xml +++ b/packages/SystemUI/res/layout/super_status_bar.xml @@ -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" /> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 56ea359cf678..0a288d91b4a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -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; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 0fa48108d8a2..8564409240a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -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() { |