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

Commit 8546d327 authored by Evan Rosky's avatar Evan Rosky
Browse files

Detect an activity "replacement" transition and animate accordingly

There's an occasionally-used pattern among some apps where they
want to "replace" the current activity with another one (rather
than just open a new activity). This involves calling startActivity
and finish at the same time. It's technically more-correct to
start the new activity before calling finish; however, in practice
it kinda works (end result is same but it causes WM to do more
work) to finish before startActivity so of-course some
apps do that.

Because of this "out-of-order"ness, we end up with a CLOSE
transit which apparently looks confusing. So, this CL adds
detection for this specific scenario and treats the
transition as OPEN.

Bug: 295727324
Test: Call finish() then startActivity() immediately after.
Change-Id: I5ce06f83f00042a1cff7f32ce5beb4ab50eec191
parent 8a5c6453
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -245,8 +245,8 @@ class ActivityEmbeddingAnimationSpec {

    private boolean shouldShowBackdrop(@NonNull TransitionInfo info,
            @NonNull TransitionInfo.Change change) {
        final Animation a = loadAttributeAnimation(info, change, WALLPAPER_TRANSITION_NONE,
                mTransitionAnimation, false);
        final Animation a = loadAttributeAnimation(info.getType(), info, change,
                WALLPAPER_TRANSITION_NONE, mTransitionAnimation, false);
        return a != null && a.getShowBackdrop();
    }
}
+60 −5
Original line number Diff line number Diff line
@@ -37,8 +37,12 @@ import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
@@ -334,6 +338,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
        boolean isDisplayRotationAnimationStarted = false;
        final boolean isDreamTransition = isDreamTransition(info);
        final boolean isOnlyTranslucent = isOnlyTranslucent(info);
        final boolean isActivityReplace = checkActivityReplacement(info, startTransaction);
        // Some patterns (eg. activity "replacement") require us to re-interpret the type
        @WindowManager.TransitionType final int transitType =
                isActivityReplace ? TRANSIT_OPEN : info.getType();

        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
            final TransitionInfo.Change change = info.getChanges().get(i);
@@ -430,7 +438,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
            // Don't animate anything that isn't independent.
            if (!TransitionInfo.isIndependent(change, info)) continue;

            Animation a = loadAnimation(info, change, wallpaperTransit, isDreamTransition);
            Animation a = loadAnimation(transitType, info, change, wallpaperTransit,
                    isDreamTransition);
            if (a != null) {
                if (isTask) {
                    final boolean isTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0;
@@ -604,6 +613,53 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
        return (translucentOpen + translucentClose) > 0;
    }

    /**
     * Checks for an edge-case where an activity calls finish() followed immediately by
     * startActivity() to "replace" itself. If in this case, it will swap the layer of the
     * close/open activities and return `true`. This way, we pretend like we are just "opening"
     * the new activity.
     */
    private static boolean checkActivityReplacement(@NonNull TransitionInfo info,
            SurfaceControl.Transaction t) {
        if (info.getType() != TRANSIT_CLOSE) {
            return false;
        }
        int closing = -1;
        int opening = -1;
        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
            final TransitionInfo.Change change = info.getChanges().get(i);
            if ((change.getTaskInfo() != null || change.hasFlags(FLAG_IS_DISPLAY))
                    && !TransitionUtil.isOrderOnly(change)) {
                // This isn't an activity-level transition.
                return false;
            }
            if (change.getTaskInfo() != null
                    && change.hasFlags(FLAG_IS_DISPLAY | FLAGS_IS_NON_APP_WINDOW)) {
                // Ignore non-activity containers.
                continue;
            }
            if (TransitionUtil.isClosingType(change.getMode())) {
                closing = i;
            } else if (change.getMode() == TRANSIT_OPEN) {
                // OPEN implies that it is a new launch. If going "back" the opening app will be
                // TO_FRONT
                opening = i;
            } else if (change.getMode() == TRANSIT_TO_FRONT) {
                // Normal "going back", so not a replacement.
                return false;
            }
        }
        if (closing < 0 || opening < 0) {
            return false;
        }
        // Swap the opening and closing z-orders since we're swapping the transit type.
        final int numChanges = info.getChanges().size();
        final int zSplitLine = numChanges + 1;
        t.setLayer(info.getChanges().get(opening).getLeash(), zSplitLine + numChanges - opening);
        t.setLayer(info.getChanges().get(closing).getLeash(), zSplitLine - closing);
        return true;
    }

    @Override
    public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -656,12 +712,11 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
    }

    @Nullable
    private Animation loadAnimation(@NonNull TransitionInfo info,
    private Animation loadAnimation(int type, @NonNull TransitionInfo info,
            @NonNull TransitionInfo.Change change, int wallpaperTransit,
            boolean isDreamTransition) {
        Animation a;

        final int type = info.getType();
        final int flags = info.getFlags();
        final int changeMode = change.getMode();
        final int changeFlags = change.getFlags();
@@ -716,8 +771,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
            // If there's a scene-transition, then jump-cut.
            return null;
        } else {
            a = loadAttributeAnimation(
                    info, change, wallpaperTransit, mTransitionAnimation, isDreamTransition);
            a = loadAttributeAnimation(type, info, change, wallpaperTransit, mTransitionAnimation,
                    isDreamTransition);
        }

        if (a != null) {
+5 −4
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import android.graphics.Rect;
import android.graphics.Shader;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.window.ScreenCapture;
@@ -61,10 +62,10 @@ public class TransitionAnimationHelper {

    /** Loads the animation that is defined through attribute id for the given transition. */
    @Nullable
    public static Animation loadAttributeAnimation(@NonNull TransitionInfo info,
            @NonNull TransitionInfo.Change change, int wallpaperTransit,
            @NonNull TransitionAnimation transitionAnimation, boolean isDreamTransition) {
        final int type = info.getType();
    public static Animation loadAttributeAnimation(@WindowManager.TransitionType int type,
            @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
            int wallpaperTransit, @NonNull TransitionAnimation transitionAnimation,
            boolean isDreamTransition) {
        final int changeMode = change.getMode();
        final int changeFlags = change.getFlags();
        final boolean enter = TransitionUtil.isOpeningType(changeMode);