From 4981fedc4538da6d74b796ada724c4ceec2fb53c Mon Sep 17 00:00:00 2001 From: Ghosuto Date: Mon, 29 Dec 2025 10:29:35 +0000 Subject: [PATCH] SystemUI: Refactor QS media artwork Change-Id: I711c1e9ebafa886040d12651ed3070157cbd91ab Signed-off-by: Ghosuto --- .../NotificationPanelViewController.java | 3 + .../phone/MediaArtScrimController.java | 204 ++++++++++++++---- .../statusbar/phone/ScrimController.java | 19 +- 3 files changed, 183 insertions(+), 43 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 72b8e48a33a36..c6458804cc2c5 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1049,6 +1049,9 @@ public final class NotificationPanelViewController implements // start fading the shade. mIsBrightnessMirrorShowing.setValue(isShowing); } + if (mScrimController != null) { + mScrimController.notifyBrightnessMirrorChanged(isShowing); + } setAlpha(isShowing ? 0 : 255, true); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MediaArtScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MediaArtScrimController.java index ae275bc5e3aad..00eeb69105b0e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MediaArtScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MediaArtScrimController.java @@ -21,11 +21,9 @@ import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.TransitionDrawable; import android.graphics.RenderEffect; import android.graphics.Shader; import android.media.session.PlaybackState; @@ -34,7 +32,6 @@ import android.os.Looper; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; -import android.view.View; import com.android.systemui.media.MediaSessionManager; import com.android.systemui.scrim.ScrimView; @@ -50,6 +47,11 @@ public class MediaArtScrimController implements MediaSessionManager.MediaDataLis private static final int MAX_BITMAP_SIZE = 400; private static final int DEFAULT_DIM_AMOUNT = 10; + private static final long ARTWORK_UPDATE_DEBOUNCE_MS = 200L; + + private static final int CROSSFADE_DURATION_MS = 300; + private static final boolean ENABLE_CROSSFADE = true; + private final Context mContext; private final ContentResolver mContentResolver; private final Handler mHandler; @@ -75,9 +77,15 @@ public class MediaArtScrimController implements MediaSessionManager.MediaDataLis private float mQsExpansion = 0f; private float mLastAppliedExpansion = 0f; private Bitmap mCurrentBitmap = null; + private boolean mBrightnessMirrorShowing = false; private Runnable mPendingStateUpdate = null; private static final long STATE_UPDATE_DELAY_MS = 30; + + private Runnable mPendingArtworkUpdate = null; + private Drawable mPendingArtwork = null; + + private Drawable mLastAppliedArtwork = null; private final ContentObserver mSettingsObserver = new ContentObserver( new Handler(Looper.getMainLooper())) { @@ -170,16 +178,13 @@ public class MediaArtScrimController implements MediaSessionManager.MediaDataLis } } - private void updateMediaArtScrimEnabled() { - updateSettings(); - } - private boolean shouldShowMediaArt() { if (!mMediaArtScrimEnabled) return false; if (mCurrentMediaArtwork == null || !mHasActiveMedia) return false; if (mKeyguardShowing) return false; if (mBouncerShowing) return false; if (mKeyguardGoingAway) return false; + if (mBrightnessMirrorShowing) return true; if (mQsExpansion < MIN_QS_EXPANSION_FOR_MEDIA_ART) return false; return true; } @@ -195,24 +200,59 @@ public class MediaArtScrimController implements MediaSessionManager.MediaDataLis @Override public void onAlbumArtChanged(Drawable drawable) { - mCurrentMediaArtwork = drawable; - if (mMediaArtScrimEnabled) { - if (mIsApplied) { - if (mPendingStateUpdate != null) { - mHandler.removeCallbacks(mPendingStateUpdate); - } - - mPendingStateUpdate = () -> { - mPendingStateUpdate = null; - mIsApplied = false; - updateScrimState(); - }; - - mHandler.postDelayed(mPendingStateUpdate, 150); - } else { - scheduleStateUpdate(); - } + if (mPendingArtworkUpdate != null) { + mHandler.removeCallbacks(mPendingArtworkUpdate); + mPendingArtworkUpdate = null; } + + mPendingArtwork = drawable; + + if (isSameArtwork(mPendingArtwork, mCurrentMediaArtwork)) { + return; + } + + mPendingArtworkUpdate = () -> { + mCurrentMediaArtwork = mPendingArtwork; + mPendingArtworkUpdate = null; + + if (mMediaArtScrimEnabled) { + if (mIsApplied) { + if (mPendingStateUpdate != null) { + mHandler.removeCallbacks(mPendingStateUpdate); + } + + mPendingStateUpdate = () -> { + mPendingStateUpdate = null; + mIsApplied = false; + updateScrimState(); + }; + + mHandler.postDelayed(mPendingStateUpdate, 150); + } else { + scheduleStateUpdate(); + } + } + }; + + mHandler.postDelayed(mPendingArtworkUpdate, ARTWORK_UPDATE_DEBOUNCE_MS); + } + + private boolean isSameArtwork(Drawable drawable1, Drawable drawable2) { + if (drawable1 == drawable2) return true; + if (drawable1 == null || drawable2 == null) return false; + + if (drawable1.getIntrinsicWidth() != drawable2.getIntrinsicWidth() || + drawable1.getIntrinsicHeight() != drawable2.getIntrinsicHeight()) { + return false; + } + + if (drawable1 instanceof BitmapDrawable && drawable2 instanceof BitmapDrawable) { + Bitmap bitmap1 = ((BitmapDrawable) drawable1).getBitmap(); + Bitmap bitmap2 = ((BitmapDrawable) drawable2).getBitmap(); + return bitmap1 != null && bitmap1.sameAs(bitmap2); + } + + return false; } @Override @@ -222,6 +262,7 @@ public class MediaArtScrimController implements MediaSessionManager.MediaDataLis if (!mHasActiveMedia) { mCurrentMediaArtwork = null; + mLastAppliedArtwork = null; if (mIsApplied) { mHandler.post(() -> { restoreRegularScrimImmediate(); @@ -229,6 +270,11 @@ public class MediaArtScrimController implements MediaSessionManager.MediaDataLis } else { cleanupBitmap(); } + + if (mPendingArtworkUpdate != null) { + mHandler.removeCallbacks(mPendingArtworkUpdate); + mPendingArtworkUpdate = null; + } } if (mMediaArtScrimEnabled && wasActive != mHasActiveMedia) { @@ -244,6 +290,27 @@ public class MediaArtScrimController implements MediaSessionManager.MediaDataLis public void onMetadataChanged(String track, String artist) { } + public void setBrightnessMirrorShowing(boolean showing) { + Log.d(TAG, "setBrightnessMirrorShowing: " + showing + ", mIsApplied: " + mIsApplied); + + boolean wasShowing = mBrightnessMirrorShowing; + mBrightnessMirrorShowing = showing; + + if (showing && mIsApplied) { + if (mNotificationsScrim != null) { + mNotificationsScrim.setAlpha(0f); + } + } else if (!showing && wasShowing && mIsApplied) { + if (mNotificationsScrim != null) { + mNotificationsScrim.setRenderEffect(mBlurEffect); + mNotificationsScrim.setViewAlpha(mQsExpansion); + mNotificationsScrim.setAlpha(mQsExpansion); + } + } else if (!showing && !mIsApplied && canShowMediaArt()) { + scheduleStateUpdate(); + } + } + public void onPanelExpansionChanged(float expansion) { if (!mMediaArtScrimEnabled || mNotificationsScrim == null) { return; @@ -252,6 +319,10 @@ public class MediaArtScrimController implements MediaSessionManager.MediaDataLis float oldExpansion = mQsExpansion; mQsExpansion = expansion; + if (mBrightnessMirrorShowing) { + return; + } + if (!canShowMediaArt() && mIsApplied) { restoreRegularScrimImmediate(); return; @@ -306,20 +377,13 @@ public class MediaArtScrimController implements MediaSessionManager.MediaDataLis private void applyMediaArt() { if (!shouldShowMediaArt()) { - Log.d(TAG, "Skipping media art application - conditions not met"); if (mIsApplied) { restoreRegularScrimImmediate(); } return; } - if (mIsApplied) { - if (Math.abs(mLastAppliedExpansion - mQsExpansion) > 0.01f) { - mNotificationsScrim.setAlpha(mQsExpansion); - mLastAppliedExpansion = mQsExpansion; - } - return; - } + boolean isReapplying = mIsApplied; if (mOriginalTint == -1) { saveOriginalScrimState(); @@ -327,17 +391,24 @@ public class MediaArtScrimController implements MediaSessionManager.MediaDataLis Bitmap bitmap = drawableToBitmap(mCurrentMediaArtwork); if (bitmap != null) { - mNotificationsScrim.setBackground(null); - cleanupBitmap(); mCurrentBitmap = applyDimToBitmap(bitmap); - BitmapDrawable blurredDrawable = new BitmapDrawable( + BitmapDrawable newDrawable = new BitmapDrawable( mContext.getResources(), mCurrentBitmap); - mNotificationsScrim.setMediaArtApplied(true); + if (ENABLE_CROSSFADE && isReapplying + && mLastAppliedArtwork != null + && mNotificationsScrim.getBackground() != null + && !mBrightnessMirrorShowing) { + applyCrossfadeTransition(newDrawable); + } else { + mNotificationsScrim.setBackground(newDrawable); + } - mNotificationsScrim.setBackground(blurredDrawable); + mLastAppliedArtwork = mCurrentMediaArtwork; + + mNotificationsScrim.setMediaArtApplied(true); mNotificationsScrim.setRenderEffect(mBlurEffect); mNotificationsScrim.setTint(android.graphics.Color.TRANSPARENT); mNotificationsScrim.setViewAlpha(mQsExpansion); @@ -345,12 +416,50 @@ public class MediaArtScrimController implements MediaSessionManager.MediaDataLis mIsApplied = true; mLastAppliedExpansion = mQsExpansion; - Log.d(TAG, "Applied media art to notifications scrim with dim amount: " + mMediaArtDimAmount); + Log.d(TAG, "Applied media art to notifications scrim with dim amount: " + + mMediaArtDimAmount + (isReapplying && ENABLE_CROSSFADE ? " (with crossfade)" : "")); } else { Log.e(TAG, "Failed to create bitmap from artwork"); } } + private void applyCrossfadeTransition(Drawable newDrawable) { + try { + Drawable currentBackground = mNotificationsScrim.getBackground(); + + if (currentBackground != null && currentBackground instanceof BitmapDrawable) { + BitmapDrawable oldDrawable = (BitmapDrawable) currentBackground; + Bitmap oldBitmap = oldDrawable.getBitmap(); + + if (oldBitmap != null && !oldBitmap.isRecycled()) { + Drawable[] layers = new Drawable[] { + new BitmapDrawable(mContext.getResources(), oldBitmap), + newDrawable + }; + + TransitionDrawable transition = new TransitionDrawable(layers); + transition.setCrossFadeEnabled(true); + + mNotificationsScrim.setBackground(transition); + + transition.startTransition(CROSSFADE_DURATION_MS); + + Log.d(TAG, "Started crossfade transition (" + CROSSFADE_DURATION_MS + "ms)"); + return; + } + } + + mNotificationsScrim.setBackground(newDrawable); + } catch (Exception e) { + Log.e(TAG, "Error during crossfade transition, falling back to direct set", e); + try { + mNotificationsScrim.setBackground(newDrawable); + } catch (Exception ex) { + Log.e(TAG, "Critical error setting background", ex); + } + } + } + private Bitmap applyDimToBitmap(Bitmap source) { if (source == null || mMediaArtDimAmount <= 0) { return source; @@ -469,6 +578,10 @@ public class MediaArtScrimController implements MediaSessionManager.MediaDataLis return; } + if (mBrightnessMirrorShowing) { + return; + } + if (mPendingStateUpdate != null) { mHandler.removeCallbacks(mPendingStateUpdate); mPendingStateUpdate = null; @@ -476,9 +589,11 @@ public class MediaArtScrimController implements MediaSessionManager.MediaDataLis mNotificationsScrim.setBackground(null); mNotificationsScrim.setRenderEffect(null); + mNotificationsScrim.setMediaArtApplied(false); mIsApplied = false; mLastAppliedExpansion = 0f; + mLastAppliedArtwork = null; cleanupBitmap(); @@ -595,12 +710,21 @@ public class MediaArtScrimController implements MediaSessionManager.MediaDataLis return mMediaArtDimAmount; } + public boolean shouldSkipNotificationsScrimUpdate() { + return mIsApplied; + } + public void destroy() { if (mPendingStateUpdate != null) { mHandler.removeCallbacks(mPendingStateUpdate); mPendingStateUpdate = null; } + if (mPendingArtworkUpdate != null) { + mHandler.removeCallbacks(mPendingArtworkUpdate); + mPendingArtworkUpdate = null; + } + mContentResolver.unregisterContentObserver(mSettingsObserver); if (mListening) { mMediaSessionManager.removeListener(this); @@ -610,6 +734,8 @@ public class MediaArtScrimController implements MediaSessionManager.MediaDataLis restoreRegularScrim(); cleanupBitmap(); mCurrentMediaArtwork = null; + mLastAppliedArtwork = null; + mPendingArtwork = null; mOriginalScrimBackground = null; mBlurEffect = null; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 9e063a54ef5c9..88790867d58a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -1292,7 +1292,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mScrimInFront.setColors(mColors, animateScrimInFront); mScrimBehind.setColors(mColors, animateBehindScrim); - mNotificationsScrim.setColors(mColors, animateScrimNotifications); + + if (!shouldSkipNotificationsScrimUpdate()) { + mNotificationsScrim.setColors(mColors, animateScrimNotifications); + } dispatchBackScrimState(mScrimBehind.getViewAlpha()); } @@ -1300,7 +1303,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump // Blur the notification scrim as needed. The blur is needed only when we show the // expanded shade behind the bouncer. Without it, the notification scrim outline is // visible behind the bouncer. - mNotificationsScrim.setBlurRadius(mState.getNotifBlurRadius()); + if (!shouldSkipNotificationsScrimUpdate()) { + mNotificationsScrim.setBlurRadius(mState.getNotifBlurRadius()); + } } // We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim. @@ -1367,8 +1372,14 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } private boolean shouldSkipNotificationsScrimUpdate() { - return mMediaArtScrimController != null && - mMediaArtScrimController.isMediaArtApplied(); + return mMediaArtScrimController != null + && mMediaArtScrimController.isMediaArtApplied(); + } + + public void notifyBrightnessMirrorChanged(boolean showing) { + if (mMediaArtScrimController != null) { + mMediaArtScrimController.setBrightnessMirrorShowing(showing); + } } private void setScrimAlpha(ScrimView scrim, float alpha) {