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

Commit ff6b4983 authored by Johannes Gallmann's avatar Johannes Gallmann
Browse files

Play cross-activity back animation without letterboxes

When the closing and entering activity are both letterboxed and they have the same localBounds, let's exclude the letterbox areas from the animation and play the animation on the actual activity content instead. To do so, the letterbox areas are cropped from the animation targets and artificial letterbox layers are added instead.

Bug: 328446606
Flag: ACONFIG com.android.window.flags.predictive_back_system_anims NEXTFOOD
Test: Manual, i.e. testing back animation in letterboxed app (and verifying no regressions in landscape, vertical and horizontal split screen, large screen, light mode, dark mode, resizeableActivity=false etc.)
Change-Id: I838c1bbc41949a69214f170501c777f4f305703b
parent 937bbe8b
Loading
Loading
Loading
Loading
+27 −2
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -111,6 +112,8 @@ public final class BackNavigationInfo implements Parcelable {
    @Nullable
    private final CustomAnimationInfo mCustomAnimationInfo;

    private final int mLetterboxColor;

    /**
     * Create a new {@link BackNavigationInfo} instance.
     *
@@ -124,13 +127,15 @@ public final class BackNavigationInfo implements Parcelable {
            @Nullable IOnBackInvokedCallback onBackInvokedCallback,
            boolean isPrepareRemoteAnimation,
            boolean isAnimationCallback,
            @Nullable CustomAnimationInfo customAnimationInfo) {
            @Nullable CustomAnimationInfo customAnimationInfo,
            int letterboxColor) {
        mType = type;
        mOnBackNavigationDone = onBackNavigationDone;
        mOnBackInvokedCallback = onBackInvokedCallback;
        mPrepareRemoteAnimation = isPrepareRemoteAnimation;
        mAnimationCallback = isAnimationCallback;
        mCustomAnimationInfo = customAnimationInfo;
        mLetterboxColor = letterboxColor;
    }

    private BackNavigationInfo(@NonNull Parcel in) {
@@ -140,6 +145,7 @@ public final class BackNavigationInfo implements Parcelable {
        mPrepareRemoteAnimation = in.readBoolean();
        mAnimationCallback = in.readBoolean();
        mCustomAnimationInfo = in.readTypedObject(CustomAnimationInfo.CREATOR);
        mLetterboxColor = in.readInt();
    }

    /** @hide */
@@ -151,6 +157,7 @@ public final class BackNavigationInfo implements Parcelable {
        dest.writeBoolean(mPrepareRemoteAnimation);
        dest.writeBoolean(mAnimationCallback);
        dest.writeTypedObject(mCustomAnimationInfo, flags);
        dest.writeInt(mLetterboxColor);
    }

    /**
@@ -192,6 +199,13 @@ public final class BackNavigationInfo implements Parcelable {
        return mAnimationCallback;
    }

    /**
     * @return Letterbox color
     * @hide
     */
    public int getLetterboxColor() {
        return mLetterboxColor;
    }
    /**
     * Callback to be called when the back preview is finished in order to notify the server that
     * it can clean up the resources created for the animation.
@@ -387,6 +401,8 @@ public final class BackNavigationInfo implements Parcelable {
        private CustomAnimationInfo mCustomAnimationInfo;
        private boolean mAnimationCallback = false;

        private int mLetterboxColor = Color.TRANSPARENT;

        /**
         * @see BackNavigationInfo#getType()
         */
@@ -453,6 +469,14 @@ public final class BackNavigationInfo implements Parcelable {
            return this;
        }

        /**
         * @param color Non-transparent if there contain letterbox color.
         */
        public Builder setLetterboxColor(int color) {
            mLetterboxColor = color;
            return this;
        }

        /**
         * Builds and returns an instance of {@link BackNavigationInfo}
         */
@@ -461,7 +485,8 @@ public final class BackNavigationInfo implements Parcelable {
                    mOnBackInvokedCallback,
                    mPrepareRemoteAnimation,
                    mAnimationCallback,
                    mCustomAnimationInfo);
                    mCustomAnimationInfo,
                    mLetterboxColor);
        }
    }
}
+99 −10
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
import android.content.res.Configuration
import android.graphics.Color
import android.graphics.Matrix
import android.graphics.PointF
import android.graphics.Rect
@@ -35,6 +36,7 @@ import android.view.animation.DecelerateInterpolator
import android.view.animation.Interpolator
import android.window.BackEvent
import android.window.BackMotionEvent
import android.window.BackNavigationInfo
import android.window.BackProgressAnimator
import android.window.IOnBackInvokedCallback
import com.android.internal.jank.Cuj
@@ -67,6 +69,7 @@ class CrossActivityBackAnimation @Inject constructor(
    private val currentEnteringRect = RectF()

    private val backAnimRect = Rect()
    private val cropRect = Rect()

    private var cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)

@@ -95,6 +98,10 @@ class CrossActivityBackAnimation @Inject constructor(
    private var maxScrimAlpha: Float = 0f

    private var isLetterboxed = false
    private var enteringHasSameLetterbox = false
    private var leftLetterboxLayer: SurfaceControl? = null
    private var rightLetterboxLayer: SurfaceControl? = null
    private var letterboxColor: Int = 0

    override fun onConfigurationChanged(newConfiguration: Configuration) {
        cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
@@ -115,8 +122,12 @@ class CrossActivityBackAnimation @Inject constructor(

        transaction.setAnimationTransaction()
        isLetterboxed = closingTarget!!.taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed
        if (isLetterboxed) {
            // Include letterbox in back animation
        enteringHasSameLetterbox = isLetterboxed &&
                closingTarget!!.localBounds.equals(enteringTarget!!.localBounds)

        if (isLetterboxed && !enteringHasSameLetterbox) {
            // Play animation with letterboxes, if closing and entering target have mismatching
            // letterboxes
            backAnimRect.set(closingTarget!!.windowConfiguration.bounds)
        } else {
            // otherwise play animation on localBounds only
@@ -144,12 +155,25 @@ class CrossActivityBackAnimation @Inject constructor(
        targetEnteringRect.set(startEnteringRect)
        targetEnteringRect.scaleCentered(MAX_SCALE)

        // Draw background with task background color.
        // Draw background with task background color (or letterbox color).
        val backgroundColor = if (isLetterboxed) {
            letterboxColor
        } else {
            enteringTarget!!.taskInfo.taskDescription!!.backgroundColor
        }
        background.ensureBackground(
            closingTarget!!.windowConfiguration.bounds,
            enteringTarget!!.taskInfo.taskDescription!!.backgroundColor, transaction
            closingTarget!!.windowConfiguration.bounds, backgroundColor, transaction
        )
        ensureScrimLayer()
        if (isLetterboxed && enteringHasSameLetterbox) {
            // crop left and right letterboxes
            cropRect.set(closingTarget!!.localBounds.left, 0, closingTarget!!.localBounds.right,
                    closingTarget!!.windowConfiguration.bounds.height())
            // and add fake letterbox square surfaces instead
            ensureLetterboxes()
        } else {
            cropRect.set(backAnimRect)
        }
        applyTransaction()
    }

@@ -249,18 +273,25 @@ class CrossActivityBackAnimation @Inject constructor(
        }
        finishCallback = null
        removeScrimLayer()
        removeLetterbox()
        isLetterboxed = false
        enteringHasSameLetterbox = false
    }

    private fun applyTransform(leash: SurfaceControl?, rect: RectF, alpha: Float) {
        if (leash == null || !leash.isValid) return
        val scale = rect.width() / backAnimRect.width()
        transformMatrix.reset()
        transformMatrix.setScale(scale, scale)
        val scalePivotX = if (isLetterboxed && enteringHasSameLetterbox) {
            closingTarget!!.localBounds.left.toFloat()
        } else {
            0f
        }
        transformMatrix.setScale(scale, scale, scalePivotX, 0f)
        transformMatrix.postTranslate(rect.left, rect.top)
        transaction.setAlpha(leash, alpha)
            .setMatrix(leash, transformMatrix, tmpFloat9)
            .setCrop(leash, backAnimRect)
            .setCrop(leash, cropRect)
            .setCornerRadius(leash, cornerRadius)
    }

@@ -297,15 +328,73 @@ class CrossActivityBackAnimation @Inject constructor(
    }

    private fun removeScrimLayer() {
        scrimLayer?.let {
        if (removeLayer(scrimLayer)) applyTransaction()
        scrimLayer = null
    }

    /**
     * Adds two "fake" letterbox square surfaces to the left and right of the localBounds of the
     * closing target
     */
    private fun ensureLetterboxes() {
        closingTarget?.let { t ->
            if (t.localBounds.left != 0 && leftLetterboxLayer == null) {
                val bounds = Rect(0, t.windowConfiguration.bounds.top, t.localBounds.left,
                        t.windowConfiguration.bounds.bottom)
                leftLetterboxLayer = ensureLetterbox(bounds)
            }
            if (t.localBounds.right != t.windowConfiguration.bounds.right &&
                    rightLetterboxLayer == null) {
                val bounds = Rect(t.localBounds.right, t.windowConfiguration.bounds.top,
                        t.windowConfiguration.bounds.right, t.windowConfiguration.bounds.bottom)
                rightLetterboxLayer = ensureLetterbox(bounds)
            }
        }
    }

    private fun ensureLetterbox(bounds: Rect): SurfaceControl {
        val letterboxBuilder = SurfaceControl.Builder()
                .setName("Cross-Activity back animation letterbox")
                .setCallsite("CrossActivityBackAnimation")
                .setColorLayer()
                .setOpaque(true)
                .setHidden(false)

        rootTaskDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, letterboxBuilder)
        val layer = letterboxBuilder.build()
        val colorComponents = floatArrayOf(Color.red(letterboxColor) / 255f,
                Color.green(letterboxColor) / 255f, Color.blue(letterboxColor) / 255f)
        transaction
                .setColor(layer, colorComponents)
                .setCrop(layer, bounds)
                .setRelativeLayer(layer, closingTarget!!.leash, 1)
                .show(layer)
        return layer
    }

    private fun removeLetterbox() {
        if (removeLayer(leftLetterboxLayer) || removeLayer(rightLetterboxLayer)) applyTransaction()
        leftLetterboxLayer = null
        rightLetterboxLayer = null
    }

    private fun removeLayer(layer: SurfaceControl?): Boolean {
        layer?.let {
            if (it.isValid) {
                transaction.remove(it)
                applyTransaction()
                return true
            }
        }
        scrimLayer = null
        return false
    }

    override fun prepareNextAnimation(
            animationInfo: BackNavigationInfo.CustomAnimationInfo?,
            letterboxColor: Int
    ): Boolean {
        this.letterboxColor = letterboxColor
        return false
    }

    private inner class Callback : IOnBackInvokedCallback.Default() {
        override fun onBackStarted(backMotionEvent: BackMotionEvent) {
+2 −1
Original line number Diff line number Diff line
@@ -271,7 +271,8 @@ public class CustomizeActivityAnimation extends ShellBackAnimation {

    /** Load customize animation before animation start. */
    @Override
    public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
    public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo,
            int letterboxColor) {
        if (animationInfo == null) {
            return false;
        }
+3 −2
Original line number Diff line number Diff line
@@ -42,11 +42,12 @@ public abstract class ShellBackAnimation {
    public abstract BackAnimationRunner getRunner();

    /**
     * Prepare the next animation with customized animation.
     * Prepare the next animation.
     *
     * @return true if this type of back animation should override the default.
     */
    public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
    public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo,
            int letterboxColor) {
        return false;
    }

+4 −1
Original line number Diff line number Diff line
@@ -154,11 +154,14 @@ public class ShellBackAnimationRegistry {
        if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) {
            if (mCustomizeActivityAnimation != null
                    && mCustomizeActivityAnimation.prepareNextAnimation(
                            backNavigationInfo.getCustomAnimationInfo())) {
                            backNavigationInfo.getCustomAnimationInfo(), 0)) {
                mAnimationDefinition.get(type).resetWaitingAnimation();
                mAnimationDefinition.set(
                        BackNavigationInfo.TYPE_CROSS_ACTIVITY,
                        mCustomizeActivityAnimation.getRunner());
            } else if (mDefaultCrossActivityAnimation != null) {
                mDefaultCrossActivityAnimation.prepareNextAnimation(null,
                        backNavigationInfo.getLetterboxColor());
            }
        }
        BackAnimationRunner runner = mAnimationDefinition.get(type);
Loading