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

Commit 8fe7e0a8 authored by Jorim Jaggi's avatar Jorim Jaggi
Browse files

Fix clip reveal animation in docked window case

- Move ClipRectTB/LRAnimation to wm package, because that's the only
place we use it.
- Extend ClipRectTBAnimation to combine it with translation animation
so the clipping gets applied after the translation.
- Fix clip reveal transitions when a window is docked.
- Make the docked divider minimizing animations synchronized with clip
reveal animation.

Bug: 27154882
Bug: 22174716

Change-Id: If5c94c777f3b51c6f53f6f34cc261bf3439cfc88
parent 42625d1b
Loading
Loading
Loading
Loading
+98 −40
Original line number Diff line number Diff line
@@ -46,7 +46,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;

import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Debug;
@@ -64,8 +63,6 @@ import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
import android.view.animation.ClipRectAnimation;
import android.view.animation.ClipRectLRAnimation;
import android.view.animation.ClipRectTBAnimation;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.view.animation.ScaleAnimation;
@@ -74,10 +71,11 @@ import android.view.animation.TranslateAnimation;
import com.android.internal.util.DumpUtils.Dump;
import com.android.server.AttributeCache;
import com.android.server.wm.WindowManagerService.H;
import com.android.server.wm.animation.ClipRectLRAnimation;
import com.android.server.wm.animation.ClipRectTBAnimation;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@@ -136,6 +134,16 @@ public class AppTransition implements Dump {
    private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.5f;

    static final int DEFAULT_APP_TRANSITION_DURATION = 336;

    /** Interpolator to be used for animations that respond directly to a touch */
    static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
            new PathInterpolator(0.3f, 0f, 0.1f, 1f);

    /**
     * Maximum duration for the clip reveal animation. This is used when there is a lot of movement
     * involved, to make it more understandable.
     */
    private static final int MAX_CLIP_REVEAL_TRANSITION_DURATION = 420;
    private static final int THUMBNAIL_APP_TRANSITION_DURATION = 336;
    private static final int THUMBNAIL_APP_TRANSITION_ALPHA_DURATION = 336;
    private static final long APP_TRANSITION_TIMEOUT_MS = 5000;
@@ -200,13 +208,10 @@ public class AppTransition implements Dump {
    private final Interpolator mFastOutLinearInInterpolator;
    private final Interpolator mClipHorizontalInterpolator = new PathInterpolator(0, 0, 0.4f, 1f);

    /** Interpolator to be used for animations that respond directly to a touch */
    private final Interpolator mTouchResponseInterpolator =
            new PathInterpolator(0.3f, 0f, 0.1f, 1f);

    private final int mClipRevealTranslationY;

    private int mCurrentUserId = 0;
    private long mLastClipRevealTransitionDuration = DEFAULT_APP_TRANSITION_DURATION;

    private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>();
    private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor();
@@ -636,50 +641,101 @@ public class AppTransition implements Dump {
                bitmap, new Rect(left, top, left + width, top + height));
    }

    private Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame) {
    long getLastClipRevealTransitionDuration() {
        return mLastClipRevealTransitionDuration;
    }

    /**
     * Calculates the duration for the clip reveal animation. If the clip is "cut off", meaning that
     * the start rect is outside of the target rect, and there is a lot of movement going on.
     *
     * @param cutOff whether the start rect was not fully contained by the end rect
     * @param translationX the total translation the surface moves in x direction
     * @param translationY the total translation the surfaces moves in y direction
     * @param displayFrame our display frame
     *
     * @return the duration of the clip reveal animation, in milliseconds
     */
    private long calculateClipRevealTransitionDuration(boolean cutOff, float translationX,
            float translationY, Rect displayFrame) {
        if (!cutOff) {
            return DEFAULT_APP_TRANSITION_DURATION;
        }
        final float fraction = Math.max(Math.abs(translationX) / displayFrame.width(),
                Math.abs(translationY) / displayFrame.height());
        return (long) (DEFAULT_APP_TRANSITION_DURATION + fraction *
                (MAX_CLIP_REVEAL_TRANSITION_DURATION - DEFAULT_APP_TRANSITION_DURATION));
    }

    private Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame,
            Rect displayFrame) {
        final Animation anim;
        if (enter) {
            // Reveal will expand and move faster in horizontal direction

            final int appWidth = appFrame.width();
            final int appHeight = appFrame.height();

            // mTmpRect will contain an area around the launcher icon that was pressed. We will
            // clip reveal from that area in the final area of the app.
            getDefaultNextAppTransitionStartRect(mTmpRect);

            float t = 0f;
            if (appHeight > 0) {
                t = (float) mTmpRect.left / appHeight;
                t = (float) mTmpRect.top / displayFrame.height();
            }
            int translationY = mClipRevealTranslationY + (int)(appHeight / 7f * t);

            int translationY = mClipRevealTranslationY + (int)(displayFrame.height() / 7f * t);
            int translationX = 0;
            int translationYCorrection = translationY;
            int centerX = mTmpRect.centerX();
            int centerY = mTmpRect.centerY();
            int halfWidth = mTmpRect.width() / 2;
            int halfHeight = mTmpRect.height() / 2;
            int clipStartX = centerX - halfWidth - appFrame.left;
            int clipStartY = centerY - halfHeight - appFrame.top;
            boolean cutOff = false;

            // If the starting rectangle is fully or partially outside of the target rectangle, we
            // need to start the clipping at the edge and then achieve the rest with translation
            // and extending the clip rect from that edge.
            if (appFrame.top > centerY - halfHeight) {
                translationY = (centerY - halfHeight) - appFrame.top;
                translationYCorrection = 0;
                clipStartY = 0;
                cutOff = true;
            }
            if (appFrame.left > centerX - halfWidth) {
                translationX = (centerX - halfWidth) - appFrame.left;
                clipStartX = 0;
                cutOff = true;
            }
            if (appFrame.right < centerX + halfWidth) {
                translationX = (centerX + halfWidth) - appFrame.right;
                clipStartX = appWidth - mTmpRect.width();
                cutOff = true;
            }
            final long duration = calculateClipRevealTransitionDuration(cutOff, translationX,
                    translationY, displayFrame);

            // Clip third of the from size of launch icon, expand to full width/height
            Animation clipAnimLR = new ClipRectLRAnimation(
                    centerX - halfWidth, centerX + halfWidth, 0, appWidth);
                    clipStartX, clipStartX + mTmpRect.width(), 0, appWidth);
            clipAnimLR.setInterpolator(mClipHorizontalInterpolator);
            clipAnimLR.setDuration((long) (DEFAULT_APP_TRANSITION_DURATION / 2.5f));

            Animation clipAnimTB = new ClipRectTBAnimation(centerY - halfHeight - translationY,
                    centerY + halfHeight/ 2 - translationY, 0, appHeight);
            clipAnimTB.setInterpolator(mTouchResponseInterpolator);
            clipAnimTB.setDuration(DEFAULT_APP_TRANSITION_DURATION);

            // We might be animating entrance of a docked task, so we need the translate to account
            // for the app frame in which the window will reside. Every other calculation here
            // is performed as if the window started at 0,0.
            translationY -= appFrame.top;
            TranslateAnimation translate = new TranslateAnimation(-appFrame.left, 0, translationY,
                    0);
            translate.setInterpolator(mLinearOutSlowInInterpolator);
            translate.setDuration(DEFAULT_APP_TRANSITION_DURATION);
            clipAnimLR.setDuration((long) (duration / 2.5f));

            TranslateAnimation translate = new TranslateAnimation(translationX, 0, translationY, 0);
            translate.setInterpolator(cutOff ? TOUCH_RESPONSE_INTERPOLATOR
                    : mLinearOutSlowInInterpolator);
            translate.setDuration(duration);

            Animation clipAnimTB = new ClipRectTBAnimation(
                    clipStartY, clipStartY + mTmpRect.height(),
                    0, appHeight,
                    translationYCorrection, 0,
                    mLinearOutSlowInInterpolator);
            clipAnimTB.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
            clipAnimTB.setDuration(duration);

            // Quick fade-in from icon to app window
            final int alphaDuration = DEFAULT_APP_TRANSITION_DURATION / 4;
            final long alphaDuration = duration / 4;
            AlphaAnimation alpha = new AlphaAnimation(0.5f, 1);
            alpha.setDuration(alphaDuration);
            alpha.setInterpolator(mLinearOutSlowInInterpolator);
@@ -692,6 +748,7 @@ public class AppTransition implements Dump {
            set.setZAdjustment(Animation.ZORDER_TOP);
            set.initialize(appWidth, appHeight, appWidth, appHeight);
            anim = set;
            mLastClipRevealTransitionDuration = duration;
        } else {
            final long duration;
            switch (transit) {
@@ -798,7 +855,7 @@ public class AppTransition implements Dump {
            // Animation up from the thumbnail to the full screen
            Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW,
                    mTmpRect.left + (thumbWidth / 2f), mTmpRect.top + (thumbHeight / 2f));
            scale.setInterpolator(mTouchResponseInterpolator);
            scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
            scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
            Animation alpha = new AlphaAnimation(1f, 0f);
            alpha.setInterpolator(mThumbnailFadeOutInterpolator);
@@ -806,7 +863,7 @@ public class AppTransition implements Dump {
            final float toX = appRect.left + appRect.width() / 2 -
                    (mTmpRect.left + thumbWidth / 2);
            Animation translate = new TranslateAnimation(0, toX, 0, toY);
            translate.setInterpolator(mTouchResponseInterpolator);
            translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
            translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);

            // This AnimationSet uses the Interpolators assigned above.
@@ -819,7 +876,7 @@ public class AppTransition implements Dump {
            // Animation down from the full screen to the thumbnail
            Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f,
                    mTmpRect.left + (thumbWidth / 2f), mTmpRect.top + (thumbHeight / 2f));
            scale.setInterpolator(mTouchResponseInterpolator);
            scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
            scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
            Animation alpha = new AlphaAnimation(0f, 1f);
            alpha.setInterpolator(mThumbnailFadeInInterpolator);
@@ -827,7 +884,7 @@ public class AppTransition implements Dump {
            final float toX = appRect.left + appRect.width() / 2 -
                    (mTmpRect.left + thumbWidth / 2);
            Animation translate = new TranslateAnimation(toX, 0, toY, 0);
            translate.setInterpolator(mTouchResponseInterpolator);
            translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
            translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);

            // This AnimationSet uses the Interpolators assigned above.
@@ -839,7 +896,7 @@ public class AppTransition implements Dump {

        }
        return prepareThumbnailAnimationWithDuration(a, appWidth, appRect.height(), 0,
                mTouchResponseInterpolator);
                TOUCH_RESPONSE_INTERPOLATOR);
    }

    /**
@@ -971,7 +1028,7 @@ public class AppTransition implements Dump {
        int duration = Math.max(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION,
                THUMBNAIL_APP_TRANSITION_DURATION);
        return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, duration,
                mTouchResponseInterpolator);
                TOUCH_RESPONSE_INTERPOLATOR);
    }

    private Animation createAspectScaledThumbnailEnterFreeformAnimationLocked(Rect frame,
@@ -1223,8 +1280,9 @@ public class AppTransition implements Dump {
     *                      bigger.
     */
    Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
            int orientation, Rect frame, Rect insets, @Nullable Rect surfaceInsets,
            boolean isVoiceInteraction, boolean freeform, int taskId) {
            int orientation, Rect frame, Rect displayFrame, Rect insets,
            @Nullable Rect surfaceInsets, boolean isVoiceInteraction, boolean freeform,
            int taskId) {
        Animation a;
        if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN
                || transit == TRANSIT_TASK_OPEN
@@ -1269,7 +1327,7 @@ public class AppTransition implements Dump {
                    + " transit=" + appTransitionToString(transit)
                    + " Callers=" + Debug.getCallers(3));
        } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
            a = createClipRevealAnimationLocked(transit, enter, frame);
            a = createClipRevealAnimationLocked(transit, enter, frame, displayFrame);
            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                    "applyAnimation:"
                            + " anim=" + a + " nextAppTransition=ANIM_CLIP_REVEAL"
+17 −6
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.view.IDockedStackListener;
import android.view.SurfaceControl;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;

import com.android.server.wm.DimLayer.DimLayerUser;

@@ -39,6 +40,8 @@ import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
import static android.view.WindowManager.DOCKED_TOP;
import static com.android.server.wm.AppTransition.DEFAULT_APP_TRANSITION_DURATION;
import static com.android.server.wm.AppTransition.TOUCH_RESPONSE_INTERPOLATOR;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;

@@ -49,8 +52,6 @@ public class DockedStackDividerController implements DimLayerUser {

    private static final String TAG = TAG_WITH_CLASS_NAME ? "DockedStackDividerController" : TAG_WM;

    private static final long MINIMIZED_DOCK_ANIMATION_DURATION = 400;

    private final WindowManagerService mService;
    private final DisplayContent mDisplayContent;
    private final int mDividerWindowWidth;
@@ -71,6 +72,7 @@ public class DockedStackDividerController implements DimLayerUser {
    private long mAnimationStartTime;
    private float mAnimationStart;
    private float mAnimationTarget;
    private long mAnimationDuration;
    private final Interpolator mMinimizedDockInterpolator;

    DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
@@ -331,6 +333,10 @@ public class DockedStackDividerController implements DimLayerUser {
        notifyDockedStackMinimizedChanged(minimized, 0);
    }

    private boolean isAnimationMaximizing() {
        return mAnimationTarget == 0f;
    }

    public boolean animate(long now) {
        if (!mAnimating) {
            return false;
@@ -339,12 +345,17 @@ public class DockedStackDividerController implements DimLayerUser {
        if (!mAnimationStarted) {
            mAnimationStarted = true;
            mAnimationStartTime = now;
            final long transitionDuration = isAnimationMaximizing()
                    ? mService.mAppTransition.getLastClipRevealTransitionDuration()
                    : DEFAULT_APP_TRANSITION_DURATION;
            mAnimationDuration = (long)
                    (transitionDuration * mService.getTransitionAnimationScaleLocked());
            notifyDockedStackMinimizedChanged(mMinimizedDock,
                    MINIMIZED_DOCK_ANIMATION_DURATION);
                    mAnimationDuration);
        }
        float t = Math.min(1f, (float) (now - mAnimationStartTime)
                / MINIMIZED_DOCK_ANIMATION_DURATION);
        t = mMinimizedDockInterpolator.getInterpolation(t);
        float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration);
        t = (isAnimationMaximizing() ? TOUCH_RESPONSE_INTERPOLATOR : mMinimizedDockInterpolator)
                .getInterpolation(t);
        final TaskStack stack = mDisplayContent.getDockedStackVisibleForUserLocked();
        if (stack != null) {
            final float amount = t * mAnimationTarget + (1 - t) * mAnimationStart;
+4 −2
Original line number Diff line number Diff line
@@ -2968,6 +2968,8 @@ public class WindowManagerService extends IWindowManager.Stub
            // Determine the visible rect to calculate the thumbnail clip
            final WindowState win = atoken.findMainWindow();
            final Rect frame = new Rect(0, 0, width, height);
            final Rect displayFrame = new Rect(0, 0,
                    displayInfo.logicalWidth, displayInfo.logicalHeight);
            final Rect insets = new Rect();
            Rect surfaceInsets = null;
            final boolean freeform = win != null && win.inFreeformWorkspace();
@@ -2995,8 +2997,8 @@ public class WindowManagerService extends IWindowManager.Stub
                    + " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
                    + " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
            Animation a = mAppTransition.loadAnimation(lp, transit, enter,
                    mCurConfiguration.orientation, frame, insets, surfaceInsets, isVoiceInteraction,
                    freeform, atoken.mTask.mTaskId);
                    mCurConfiguration.orientation, frame, displayFrame, insets, surfaceInsets,
                    isVoiceInteraction, freeform, atoken.mTask.mTaskId);
            if (a != null) {
                if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + atoken);
                final int containingWidth = frame.width();
+5 −3
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -11,12 +11,14 @@
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * limitations under the License
 */

package android.view.animation;
package com.android.server.wm.animation;

import android.graphics.Rect;
import android.view.animation.ClipRectAnimation;
import android.view.animation.Transformation;

/**
 * Special case of ClipRectAnimation that animates only the left/right
+86 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -11,27 +11,60 @@
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * limitations under the License
 */

package android.view.animation;
package com.android.server.wm.animation;

import android.graphics.Rect;
import android.view.animation.ClipRectAnimation;
import android.view.animation.Interpolator;
import android.view.animation.Transformation;
import android.view.animation.TranslateAnimation;

/**
 * Special case of ClipRectAnimation that animates only the top/bottom
 * dimensions of the clip, picking up the other dimensions from whatever is
 * set on the transform already.
 *
 * @hide
 * set on the transform already. In addition to that, information about a vertical translation
 * animation can be specified so this animation simulates as the clip would be applied after instead
 * of before applying the translation.
 */
public class ClipRectTBAnimation extends ClipRectAnimation {

    private final int mFromTranslateY;
    private final int mToTranslateY;
    private final Interpolator mTranslateInterpolator;
    private float mNormalizedTime;

    /**
     * Constructor. Passes in 0 for Left/Right parameters of ClipRectAnimation
     */
    public ClipRectTBAnimation(int fromT, int fromB, int toT, int toB) {
    public ClipRectTBAnimation(int fromT, int fromB, int toT, int toB,
            int fromTranslateY, int toTranslateY, Interpolator translateInterpolator) {
        super(0, fromT, 0, fromB, 0, toT, 0, toB);
        mFromTranslateY = fromTranslateY;
        mToTranslateY = toTranslateY;
        mTranslateInterpolator = translateInterpolator;
    }

    @Override
    public boolean getTransformation(long currentTime, Transformation outTransformation) {

        // Hack: Because translation animation has a different interpolator, we need to duplicate
        // code from Animation here and use it to calculate/store the uninterpolated normalized
        // time.
        final long startOffset = getStartOffset();
        final long duration = getDuration();
        float normalizedTime;
        if (duration != 0) {
            normalizedTime = ((float) (currentTime - (getStartTime() + startOffset))) /
                    (float) duration;
        } else {
            // time is a step-change with a zero duration
            normalizedTime = currentTime < getStartTime() ? 0.0f : 1.0f;
        }
        mNormalizedTime = normalizedTime;
        return super.getTransformation(currentTime, outTransformation);
    }

    /**
@@ -40,10 +73,14 @@ public class ClipRectTBAnimation extends ClipRectAnimation {
     */
    @Override
    protected void applyTransformation(float it, Transformation tr) {
        float translationT = mTranslateInterpolator.getInterpolation(mNormalizedTime);
        int translation =
                (int) (mFromTranslateY + (mToTranslateY - mFromTranslateY) * translationT);
        Rect oldClipRect = tr.getClipRect();
        tr.setClipRect(oldClipRect.left, mFromRect.top + (int) ((mToRect.top - mFromRect.top) * it),
        tr.setClipRect(oldClipRect.left,
                mFromRect.top - translation + (int) ((mToRect.top - mFromRect.top) * it),
                oldClipRect.right,
                mFromRect.bottom + (int) ((mToRect.bottom - mFromRect.bottom) * it));
                mFromRect.bottom - translation + (int) ((mToRect.bottom - mFromRect.bottom) * it));
    }

}
Loading