Support animated notification icon snapshots

This commit is contained in:
ajp_anton
2026-06-22 14:39:17 +00:00
parent 8e7e825516
commit 1fa5fd24b1
@@ -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<String, ImageView> proxies = proxiesByHost.remove(host);
if (proxies == null) {