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

Commit 9a0f560c authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Do not set alpha for rotation enter animation

If alpha < 1 is set on display layer, all visible children layers
are also affected, which may look flickering because the layer
which occludes other layers becomes translucent.

Also add clip effects to reduce the high contrast between overlapped
and non-overlapped area of enter/exit animations. The long side will
shrink to short side, and vise versa.

Bug: 438615184
Flag: com.android.window.flags.no_alpha_rotation_enter_animation
Test: Expand notification shade, rotate device.
      The app content should not flash behind.
Test: Yellow color should not flash when rotating a sample app with
      surfaceView.setZOrderOnTop(true);
      surfaceView.setBackgroundColor(Color.YELLOW);
        surfaceCreated() {
          Surface surface = holder.getSurface();
          Canvas c = surface.lockCanvas(null);
          c.drawColor(Color.GRAY);
          surface.unlockCanvasAndPost(c);
        }

Change-Id: I66e73592695febfdef02b856ebd742e1a8a51b07
parent 431671f1
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -152,6 +152,16 @@ flag {
  }
}

flag {
  name: "no_alpha_rotation_enter_animation"
  namespace: "windowing_frontend"
  description: "Remove alpha effect of rotation enter animation"
  bug: "438615184"
  metadata {
    purpose: PURPOSE_BUGFIX
  }
}

flag {
  name: "fifo_priority_for_major_ui_processes"
  namespace: "windowing_frontend"
+94 −3
Original line number Diff line number Diff line
@@ -36,8 +36,11 @@ import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
import android.view.animation.ClipRectAnimation;
import android.window.ScreenCapture.ScreenCaptureParams;
import android.window.ScreenCaptureInternal;
import android.window.TransitionInfo;
@@ -180,7 +183,8 @@ class ScreenRotationAnimation {
                }
                hardwareBuffer.close();
            }
            if ((flags & FLAG_HAS_WALLPAPER) != 0) {
            if ((flags & FLAG_HAS_WALLPAPER) != 0
                    && !com.android.window.flags.Flags.noAlphaRotationEnterAnimation()) {
                mBackEffectSurface = new SurfaceControl.Builder()
                        .setCallsite("ShellRotationAnimation").setParent(rootLeash)
                        .setEffectLayer().setOpaque(true).setName("BackEffect").build();
@@ -319,6 +323,10 @@ class ScreenRotationAnimation {
                            R.anim.screen_rotate_minus_90_enter);
                    break;
            }
            if (com.android.window.flags.Flags.noAlphaRotationEnterAnimation()) {
                postProcessRotationAnimation((AnimationSet) mRotateEnterAnimation,
                        (AnimationSet) mRotateExitAnimation, delta);
            }
        }

        mRotateExitAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
@@ -357,16 +365,21 @@ class ScreenRotationAnimation {

    private void startScreenshotRotationAnimation(@NonNull ArrayList<Animator> animations,
            @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
        final Rect clipRect = com.android.window.flags.Flags.noAlphaRotationEnterAnimation()
                ? new Rect(0, 0, mEndWidth, mEndHeight) : null;
        buildSurfaceAnimation(animations, mRotateExitAnimation, mAnimLeash, finishCallback,
                mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
                null /* clipRect */, null);
                clipRect, null);
    }

    private void buildScreenshotAlphaAnimation(@NonNull ArrayList<Animator> animations,
            @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
        final Rect clipRect = com.android.window.flags.Flags.noAlphaRotationEnterAnimation()
                // Inverse size because the screenshot layer has rotated transformation.
                ? new Rect(0, 0, mStartHeight, mStartWidth) : null;
        buildSurfaceAnimation(animations, mRotateAlphaAnimation, mAnimLeash, finishCallback,
                mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
                null /* clipRect */, null);
                clipRect, null);
    }

    private void buildLumaAnimation(@NonNull ArrayList<Animator> animations,
@@ -405,6 +418,84 @@ class ScreenRotationAnimation {
        mTransactionPool.release(t);
    }

    private void postProcessRotationAnimation(@NonNull AnimationSet enterAnim,
            @NonNull AnimationSet exitAnim, int rotationDelta) {
        int alphaAnimIndex = -1;
        long enterClipDuration = 0;
        long enterClipStartOffset = 0;
        final var enterAnimations = enterAnim.getAnimations();
        for (int i = enterAnimations.size() - 1; i >= 0; i--) {
            final Animation anim = enterAnimations.get(i);
            if (anim instanceof AlphaAnimation) {
                // Use half duration to avoid showing blank area too long.
                enterClipDuration = anim.getDuration() / 2;
                enterClipStartOffset = anim.getStartOffset() / 2;
                alphaAnimIndex = i;
                break;
            }
        }
        // TODO(b/438615184): Remove alpha animation from screen_rotate_*_enter.xml.
        if (alphaAnimIndex >= 0) {
            enterAnimations.remove(alphaAnimIndex);
        }
        if (rotationDelta % 2 == 0) {
            // 180 degree delta doesn't have size change, so no additional effects are needed.
            return;
        }
        long exitClipDuration = 0;
        for (int i = exitAnim.getAnimations().size() - 1; i >= 0; i--) {
            final Animation anim = exitAnim.getAnimations().get(i);
            if (anim instanceof AlphaAnimation) {
                exitClipDuration = anim.getDuration();
                break;
            }
        }
        final ClipRectAnimation enterClip = createClipRectAnimation(
                mEndWidth, mEndHeight, true /* enter */);
        enterClip.setDuration(enterClipDuration);
        enterClip.setStartOffset(enterClipStartOffset);
        enterAnim.addAnimation(enterClip);
        final ClipRectAnimation exitClip = createClipRectAnimation(
                // Inverse size because the screenshot layer has rotated transformation.
                mStartHeight, mStartWidth, false /* enter */);
        exitClip.setDuration(exitClipDuration);
        exitAnim.addAnimation(exitClip);
    }

    /**
     * The animation that expands/shrinks between the full size and half of the difference between
     * the long and short sides. For example, the "middle" is < and v, and "longSide - middle" is
     * > and ^. Then the paired enter/exit animations will appear as a rectangle deformation of the
     * four anchor points.
     * <pre>
     *   (exit portrait): T to v, B to ^
     *  ________T________
     * |     |     |     |
     * |     |  v  |     |
     * |_____|_____|_____|
     * |     |     |     |
     * L  <  |     |  >  R (enter landscape): < to L, > to R
     * |_____|_____|_____|
     * |     |     |     |
     * |     |  ^  |     |
     * |_____|__B__|_____|
     * </pre>
     */
    private static ClipRectAnimation createClipRectAnimation(int w, int h, boolean enter) {
        final int longSide = Math.max(w, h);
        final int shortSide = Math.min(w, h);
        final int middle = (longSide - shortSide) / 2;
        if (enter) {
            return w > h
                    ? new ClipRectAnimation(middle, 0, longSide - middle, h, 0, 0, w, h)
                    : new ClipRectAnimation(0, middle, w, longSide - middle, 0, 0, w, h);
        } else {
            return w > h
                    ? new ClipRectAnimation(0, 0, w, h, middle, 0, longSide - middle, h)
                    : new ClipRectAnimation(0, 0, w, h, 0, middle, w, longSide - middle);
        }
    }

    /** A no-op wrapper to provide animation duration. */
    private static class LumaAnimation extends Animation {
        LumaAnimation(long durationMillis) {