Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f59a78dd authored by Josh Yang's avatar Josh Yang
Browse files

[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
parent efee0571
Loading
Loading
Loading
Loading
+9 −6
Original line number Diff line number Diff line
@@ -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()");
        mHandler.post(
                () -> {
                    if (mAnimator != null) {
                        mAnimator.cancel();
                    }
                });
    }

    private static void logD(String msg) {
+8 −0
Original line number Diff line number Diff line
@@ -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 {
+75 −13
Original line number Diff line number Diff line
@@ -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;
        }