summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Josh Yang <yzj@google.com> 2025-01-03 13:30:02 -0800
committer Josh Yang <yzj@google.com> 2025-01-03 15:13:00 -0800
commitf59a78dd417c3629dc7148ffb48cc5d60a1bd10d (patch)
tree81e0fb9a87e2e6087c131e08c238193b1fcca94a
parentefee0571730e4cfeb6902d232d02f7883f40292e (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
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java15
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java8
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java88
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;
}