From 1fa5fd24b1eb844fadf7ca92435f466054185e45 Mon Sep 17 00:00:00 2001 From: ajp_anton Date: Mon, 22 Jun 2026 14:39:17 +0000 Subject: [PATCH] Support animated notification icon snapshots --- .../runtime/render/SnapshotRenderer.java | 115 ++++++++++++++---- 1 file changed, 89 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java index 6faeaae..402abf8 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java @@ -11,6 +11,7 @@ import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.BatteryManager; @@ -239,35 +240,51 @@ final class SnapshotRenderer { proxy.setTag(key); proxyChanged = true; } - BitmapResult bitmapResult = bitmapFor(proxy, bounds); - Bitmap bitmap = bitmapResult.bitmap(); SnapshotRenderState desiredState = renderStateFor(placement); - boolean bitmapChanged = bitmapResult.created() - || !desiredState.equals(statesByProxy.get(proxy)); - if (bitmapChanged) { - if (placement.overflowDot()) { - drawOverflowDot(bitmap, placement.overflowDotColor(), placement.sourceOffsetX()); - } else if (placement.heldSignal() != null) { - drawHeldSignal(placement.heldSignal(), bitmap); - } else if (placement.source() != null - && placement.source().mode() == SnapshotMode.APP_ICON) { - drawAppIcon(host.getContext(), placement.source(), bitmap); - } else if (placement.source() != null) { - drawSnapshot(placement, bitmap); - applyStatusBarTintTarget(placement.source(), bitmap); - normalizeSemiTransparentDarkIcon(bitmap); - if (placement.signal()) { - saveHeldSignal(key, bounds, bitmap); + Drawable animatedDrawable = animatedNotificationDrawable(placement); + if (animatedDrawable != null) { + SnapshotRenderState currentState = statesByProxy.get(proxy); + boolean drawableChanged = !(proxy.getDrawable() instanceof AnimationDrawable) + || !canReuseAnimatedProxy(currentState, desiredState); + if (drawableChanged) { + Drawable proxyDrawable = newDrawableInstance(animatedDrawable); + proxy.setImageDrawable(proxyDrawable); + proxyChanged = true; + } + startAnimationIfNeeded(proxy.getDrawable()); + if (!desiredState.equals(currentState)) { + statesByProxy.put(proxy, desiredState); + } + } else { + BitmapResult bitmapResult = bitmapFor(proxy, bounds); + Bitmap bitmap = bitmapResult.bitmap(); + boolean bitmapChanged = bitmapResult.created() + || !desiredState.equals(statesByProxy.get(proxy)); + if (bitmapChanged) { + if (placement.overflowDot()) { + drawOverflowDot(bitmap, placement.overflowDotColor(), placement.sourceOffsetX()); + } else if (placement.heldSignal() != null) { + drawHeldSignal(placement.heldSignal(), bitmap); + } else if (placement.source() != null + && placement.source().mode() == SnapshotMode.APP_ICON) { + drawAppIcon(host.getContext(), placement.source(), bitmap); + } else if (placement.source() != null) { + drawSnapshot(placement, bitmap); + applyStatusBarTintTarget(placement.source(), bitmap); + normalizeSemiTransparentDarkIcon(bitmap); + if (placement.signal()) { + saveHeldSignal(key, bounds, bitmap); + } + } else { + bitmap.eraseColor(Color.TRANSPARENT); } - } else { - bitmap.eraseColor(Color.TRANSPARENT); + statesByProxy.put(proxy, desiredState); + proxyChanged = true; + } + if (proxy.getDrawable() == null) { + proxy.setImageBitmap(bitmap); + proxyChanged = true; } - statesByProxy.put(proxy, desiredState); - proxyChanged = true; - } - if (proxy.getDrawable() == null) { - proxy.setImageBitmap(bitmap); - proxyChanged = true; } if (proxy.getAlpha() != 1f) { proxy.setAlpha(1f); @@ -305,6 +322,52 @@ final class SnapshotRenderer { return renderedBounds; } + private boolean canReuseAnimatedProxy( + SnapshotRenderState currentState, + SnapshotRenderState desiredState + ) { + return currentState != null + && desiredState != null + && Objects.equals(currentState.key(), desiredState.key()) + && currentState.width() == desiredState.width() + && currentState.height() == desiredState.height() + && currentState.signal() == desiredState.signal() + && currentState.overflowDot() == desiredState.overflowDot() + && currentState.overflowDotColor() == desiredState.overflowDotColor() + && currentState.sourceOffsetX() == desiredState.sourceOffsetX() + && currentState.sourceRenderBoundsSignature() == desiredState.sourceRenderBoundsSignature() + && Objects.equals(currentState.heldSignalKey(), desiredState.heldSignalKey()) + && currentState.heldBitmapId() == desiredState.heldBitmapId() + && currentState.sourceMode() == desiredState.sourceMode() + && currentState.tintSignature() == desiredState.tintSignature(); + } + + private Drawable animatedNotificationDrawable(SnapshotPlacement placement) { + if (placement == null + || placement.source() == null + || placement.source().mode() != SnapshotMode.NOTIFICATION_DRAWABLE + || !(placement.source().view() instanceof ImageView imageView)) { + return null; + } + Drawable drawable = imageView.getDrawable(); + return drawable instanceof AnimationDrawable ? drawable : null; + } + + private Drawable newDrawableInstance(Drawable drawable) { + Drawable.ConstantState constantState = drawable != null ? drawable.getConstantState() : null; + if (constantState != null) { + return constantState.newDrawable().mutate(); + } + return drawable != null ? drawable.mutate() : null; + } + + private void startAnimationIfNeeded(Drawable drawable) { + if (drawable instanceof AnimationDrawable animationDrawable && !animationDrawable.isRunning()) { + animationDrawable.setVisible(true, true); + animationDrawable.start(); + } + } + void clear(ViewGroup host) { LinkedHashMap proxies = proxiesByHost.remove(host); if (proxies == null) {