diff options
| author | 2025-01-03 13:30:02 -0800 | |
|---|---|---|
| committer | 2025-01-03 15:13:00 -0800 | |
| commit | f59a78dd417c3629dc7148ffb48cc5d60a1bd10d (patch) | |
| tree | 81e0fb9a87e2e6087c131e08c238193b1fcca94a | |
| parent | efee0571730e4cfeb6902d232d02f7883f40292e (diff) | |
[OriginTransition] Make the library resilient to view being detached in
the middle of transition.
1. In ViewUIComponent, gracefully handle the case when view is detached
from the window.
2. Cancelling an origin transition session will also cancel the running
remote transition.
Flag: EXEMPTED - bug fix
Test: manual test
Change-Id: I72f553c97e62101d32e41a601b355d125e83eb9c
3 files changed, 92 insertions, 19 deletions
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java index 91fad4fb6dc2..56d85ab8aca0 100644 --- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java +++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java @@ -107,7 +107,7 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub { IBinder mergeTarget, IRemoteTransitionFinishedCallback finishCallback) { logD("mergeAnimation - " + info); - mHandler.post(this::cancel); + cancel(); } @Override @@ -129,7 +129,7 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub { @Override public void onTransitionConsumed(IBinder transition, boolean aborted) { logD("onTransitionConsumed - aborted: " + aborted); - mHandler.post(this::cancel); + cancel(); } private void startAnimationInternal( @@ -342,11 +342,14 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub { mFinishCallback = null; } - private void cancel() { + public void cancel() { logD("cancel()"); - if (mAnimator != null) { - mAnimator.cancel(); - } + mHandler.post( + () -> { + if (mAnimator != null) { + mAnimator.cancel(); + } + }); } private static void logD(String msg) { diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java index 6d6aa8895ed0..cb3dfb9e78e7 100644 --- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java +++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java @@ -43,6 +43,7 @@ import java.util.function.Supplier; /** * A session object that holds origin transition states for starting an activity from an on-screen * UI component and smoothly transitioning back from the activity to the same UI component. + * * @hide */ public class OriginTransitionSession { @@ -143,6 +144,12 @@ public class OriginTransitionSession { logE("Unable to cancel origin transition!", e); } } + if (mEntryTransition instanceof OriginRemoteTransition) { + ((OriginRemoteTransition) mEntryTransition).cancel(); + } + if (mExitTransition instanceof OriginRemoteTransition) { + ((OriginRemoteTransition) mExitTransition).cancel(); + } } private boolean hasEntryTransition() { @@ -182,6 +189,7 @@ public class OriginTransitionSession { /** * A builder to build a {@link OriginTransitionSession}. + * * @hide */ public static class Builder { diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java index 7c219c6ca921..2e8f92839fa6 100644 --- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java +++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java @@ -24,13 +24,15 @@ import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; +import android.os.Handler; +import android.os.Looper; import android.util.Log; import android.view.Surface; import android.view.SurfaceControl; import android.view.View; import android.view.ViewGroup; import android.view.ViewRootImpl; -import android.view.ViewTreeObserver.OnDrawListener; +import android.view.ViewTreeObserver; import java.util.ArrayList; import java.util.List; @@ -51,8 +53,9 @@ public class ViewUIComponent implements UIComponent { private final Path mClippingPath = new Path(); private final Outline mClippingOutline = new Outline(); - private final OnDrawListener mOnDrawListener = this::postDraw; + private final LifecycleListener mLifecycleListener = new LifecycleListener(); private final View mView; + private final Handler mMainHandler; @Nullable private SurfaceControl mSurfaceControl; @Nullable private Surface mSurface; @@ -62,6 +65,7 @@ public class ViewUIComponent implements UIComponent { public ViewUIComponent(View view) { mView = view; + mMainHandler = new Handler(Looper.getMainLooper()); } /** @@ -110,11 +114,11 @@ public class ViewUIComponent implements UIComponent { t.reparent(mSurfaceControl, transitionLeash).show(mSurfaceControl); // Make sure view draw triggers surface draw. - mView.getViewTreeObserver().addOnDrawListener(mOnDrawListener); + mLifecycleListener.register(); // Make the view invisible AFTER the surface is shown. t.addTransactionCommittedListener( - mView::post, + this::post, () -> { logD("Surface attached!"); forceDraw(); @@ -129,14 +133,14 @@ public class ViewUIComponent implements UIComponent { SurfaceControl sc = mSurfaceControl; mSurface = null; mSurfaceControl = null; - mView.getViewTreeObserver().removeOnDrawListener(mOnDrawListener); + mLifecycleListener.unregister(); // Restore view visibility mView.setVisibility(mVisibleOverride ? View.VISIBLE : View.INVISIBLE); // Clean up surfaces. SurfaceControl.Transaction t = new SurfaceControl.Transaction(); t.reparent(sc, null) .addTransactionCommittedListener( - mView::post, + this::post, () -> { s.release(); sc.release(); @@ -269,7 +273,66 @@ public class ViewUIComponent implements UIComponent { return; } mDirty = true; - mView.post(this::draw); + post(this::draw); + } + + private void post(Runnable r) { + if (mView.isAttachedToWindow()) { + mView.post(r); + } else { + // If the view is detached from window, {@code View.post()} will postpone the action + // until the view is attached again. However, we don't know if the view will be attached + // again, so we post the action to the main thread in this case. This could lead to race + // condition if the attachment change caused a thread switching, and it's the caller's + // responsibility to ensure the window attachment state doesn't change unexpectedly. + if (DEBUG) { + Log.w(TAG, mView + " is not attached. Posting action to main thread!"); + } + mMainHandler.post(r); + } + } + + /** A listener for monitoring view life cycles. */ + private class LifecycleListener + implements ViewTreeObserver.OnDrawListener, View.OnAttachStateChangeListener { + private boolean mRegistered; + + @Override + public void onDraw() { + // View draw should trigger surface draw. + postDraw(); + } + + @Override + public void onViewAttachedToWindow(View v) { + // empty + } + + @Override + public void onViewDetachedFromWindow(View v) { + Log.w( + TAG, + v + " is detached from the window. Unregistering the life cycle listener ..."); + unregister(); + } + + public void register() { + if (mRegistered) { + return; + } + mRegistered = true; + mView.getViewTreeObserver().addOnDrawListener(this); + mView.addOnAttachStateChangeListener(this); + } + + public void unregister() { + if (!mRegistered) { + return; + } + mRegistered = false; + mView.getViewTreeObserver().removeOnDrawListener(this); + mView.removeOnAttachStateChangeListener(this); + } } /** @hide */ @@ -278,34 +341,33 @@ public class ViewUIComponent implements UIComponent { @Override public Transaction setAlpha(ViewUIComponent ui, float alpha) { - mChanges.add(() -> ui.mView.post(() -> ui.setAlpha(alpha))); + mChanges.add(() -> ui.post(() -> ui.setAlpha(alpha))); return this; } @Override public Transaction setVisible(ViewUIComponent ui, boolean visible) { - mChanges.add(() -> ui.mView.post(() -> ui.setVisible(visible))); + mChanges.add(() -> ui.post(() -> ui.setVisible(visible))); return this; } @Override public Transaction setBounds(ViewUIComponent ui, Rect bounds) { - mChanges.add(() -> ui.mView.post(() -> ui.setBounds(bounds))); + mChanges.add(() -> ui.post(() -> ui.setBounds(bounds))); return this; } @Override public Transaction attachToTransitionLeash( ViewUIComponent ui, SurfaceControl transitionLeash, int w, int h) { - mChanges.add( - () -> ui.mView.post(() -> ui.attachToTransitionLeash(transitionLeash, w, h))); + mChanges.add(() -> ui.post(() -> ui.attachToTransitionLeash(transitionLeash, w, h))); return this; } @Override public Transaction detachFromTransitionLeash( ViewUIComponent ui, Executor executor, Runnable onDone) { - mChanges.add(() -> ui.mView.post(() -> ui.detachFromTransitionLeash(executor, onDone))); + mChanges.add(() -> ui.post(() -> ui.detachFromTransitionLeash(executor, onDone))); return this; } |