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

Commit beb3053f authored by Alex Chau's avatar Alex Chau
Browse files

Use bigger task size in app to overview carousel

- Introduced carouselTaskSize that represent size of TaskView in app to overview carousel, that is bigger than size of TaskView in Overview state
- Use nonGridScale and translation to scale TaskView to the desired carousel size
- For current task, inroduced carouselScale and translation to apply similar transformation at the carousel. They will be reset in `onPrepareGestureEndAnimation` after gesture is released and form the grid. Carousel translation can be invalidated and aniamte to 0.
- Fixed current task left/right wiggle that is caused by task shrinks and translate in different direction. Pivot is now moved to top right (or top left for RTL), to align with movement of current task.
- To compensate for the pivot change, current task is translated back to the carousel position by taskTranslation, and again translated by carouselTranslation for carousel -> fullScreen scaling. A complex interpolator is introduced to make current task moves in a vertical straight line rather than a curve.
- Fixed a bug in AnimatorControllerWithResistance when scaleStartResist==scaleMaxResist that causes division by 0. For grid overview, resistance kicks in after reaching carousel size, and current task size won't reduce further
- Added PendingAnimation#addAnimatedFloat that uses animator provided by AnimatedFloat, so the animator can be canceled and reaniamte from AnimatedFloat side; AnimatedFloat now clears the property values during cancel so the canceled animator still referenced by PendingAnimation can no longer change the values

Fix: 318352235
Fix: 308643507
Flag: ACONFIG com.android.launcher3.enable_grid_only_overview TEAMFOOD
Test: presubmit
Change-Id: I2872d8b2204798fe5e05c10d08480a81e60bb498
parent c2f73d90
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -32,8 +32,11 @@
    <dimen name="overview_minimum_next_prev_size">50dp</dimen>

    <!--  Overview Task Views  -->
    <!--  The primary task thumbnail uses up to this much of the total screen height/width  -->
    <!--  The thumbnail uses up to this much of the total screen height/width in Overview -->
    <item name="overview_max_scale" format="float" type="dimen">0.7</item>
    <!--  The thumbnail should not go smaller than this much of the total screen height/width in
             tablet app to Overview carousel -->
    <item name="overview_carousel_min_scale" format="float" type="dimen">0.46</item>
    <!--  A touch target for icons, sometimes slightly larger than the icons themselves  -->
    <dimen name="task_thumbnail_icon_size">48dp</dimen>
    <!--  The icon size for the focused task, placed in center of touch target  -->
+5 −2
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
import static com.android.launcher3.BaseActivity.EVENT_STARTED;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
import static com.android.launcher3.Flags.enableGridOnlyOverview;
import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
import static com.android.launcher3.PagedView.INVALID_PAGE;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
@@ -2561,9 +2562,11 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
        }

        float scrollOffset = Math.abs(mRecentsView.getScrollOffset(mRecentsView.getCurrentPage()));
        Rect carouselTaskSize = enableGridOnlyOverview()
                ? mRecentsView.getLastComputedCarouselTaskSize()
                : mRecentsView.getLastComputedTaskSize();
        int maxScrollOffset = mRecentsView.getPagedOrientationHandler().getPrimaryValue(
                mRecentsView.getLastComputedTaskSize().width(),
                mRecentsView.getLastComputedTaskSize().height());
                carouselTaskSize.width(), carouselTaskSize.height());
        maxScrollOffset += mRecentsView.getPageSpacing();

        float maxScaleProgress =
+19 −2
Original line number Diff line number Diff line
@@ -261,6 +261,23 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
        }
    }

    /**
     * Calculates the taskView size for carousel during app to overview animation on tablets.
     */
    public final void calculateCarouselTaskSize(Context context, DeviceProfile dp, Rect outRect,
            PagedOrientationHandler orientedState) {
        if (dp.isTablet && dp.isGestureMode) {
            Resources res = context.getResources();
            float minScale = res.getFloat(R.dimen.overview_carousel_min_scale);
            Rect gridRect = new Rect();
            calculateGridSize(dp, context, gridRect);
            calculateTaskSizeInternal(context, dp, gridRect, minScale, Gravity.CENTER | Gravity.TOP,
                    outRect);
        } else {
            calculateTaskSize(context, dp, outRect, orientedState);
        }
    }

    private void calculateFocusTaskSize(Context context, DeviceProfile dp, Rect outRect) {
        Resources res = context.getResources();
        float maxScale = res.getFloat(R.dimen.overview_max_scale);
@@ -286,13 +303,13 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
    }

    private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
            Rect potentialTaskRect, float maxScale, int gravity, Rect outRect) {
            Rect potentialTaskRect, float targetScale, int gravity, Rect outRect) {
        PointF taskDimension = getTaskDimension(context, dp);

        float scale = Math.min(
                potentialTaskRect.width() / taskDimension.x,
                potentialTaskRect.height() / taskDimension.y);
        scale = Math.min(scale, maxScale);
        scale = Math.min(scale, targetScale);
        int outWidth = Math.round(scale * taskDimension.x);
        int outHeight = Math.round(scale * taskDimension.y);

+7 −3
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package com.android.quickstep.util;

import static com.android.app.animation.Interpolators.DECELERATE;
import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.Flags.enableGridOnlyOverview;
import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
@@ -58,6 +59,7 @@ public class AnimatorControllerWithResistance {
        FROM_APP(0.75f, 0.5f, 1f, false),
        FROM_APP_TO_ALL_APPS(1f, 0.6f, 0.8f, false),
        FROM_APP_TABLET(1f, 0.7f, 1f, true),
        FROM_APP_TABLET_GRID_ONLY(1f, 1f, 1f, true),
        FROM_APP_TO_ALL_APPS_TABLET(1f, 0.5f, 0.5f, false),
        FROM_OVERVIEW(1f, 0.75f, 0.5f, false);

@@ -239,10 +241,10 @@ public class AnimatorControllerWithResistance {
        float stopResist =
                params.resistanceParams.stopScalingAtTop ? 1f - startRect.top / endRectF.top : 1f;
        final TimeInterpolator scaleInterpolator = t -> {
            if (t < startResist) {
            if (t <= startResist) {
                return t;
            }
            if (t > stopResist) {
            if (t >= stopResist) {
                return maxResist;
            }
            float resistProgress = Utilities.getProgress(t, startResist, stopResist);
@@ -304,6 +306,8 @@ public class AnimatorControllerWithResistance {
                resistanceParams =
                        recentsOrientedState.getActivityInterface().allowAllAppsFromOverview()
                                ? RecentsResistanceParams.FROM_APP_TO_ALL_APPS_TABLET
                                : enableGridOnlyOverview()
                                        ? RecentsResistanceParams.FROM_APP_TABLET_GRID_ONLY
                                        : RecentsResistanceParams.FROM_APP_TABLET;
            } else {
                resistanceParams =
+96 −12
Original line number Diff line number Diff line
@@ -38,10 +38,12 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Log;
import android.view.RemoteAnimationTarget;
import android.view.animation.Interpolator;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.app.animation.Interpolators;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
@@ -76,6 +78,8 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {

    private final Rect mTaskRect = new Rect();
    private final Rect mFullTaskSize = new Rect();
    private final Rect mCarouselTaskSize = new Rect();
    private PointF mPivotOverride = null;
    private final PointF mPivot = new PointF();
    private DeviceProfile mDp;
    @StagePosition
@@ -95,6 +99,11 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
    public final AnimatedFloat taskPrimaryTranslation = new AnimatedFloat();
    public final AnimatedFloat taskSecondaryTranslation = new AnimatedFloat();

    // Carousel properties
    public final AnimatedFloat carouselScale = new AnimatedFloat();
    public final AnimatedFloat carouselPrimaryTranslation = new AnimatedFloat();
    public final AnimatedFloat carouselSecondaryTranslation = new AnimatedFloat();

    // RecentsView properties
    public final AnimatedFloat recentsViewScale = new AnimatedFloat();
    public final AnimatedFloat fullScreenProgress = new AnimatedFloat();
@@ -109,9 +118,9 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
    private Boolean mDrawsBelowRecents = null;
    private boolean mIsGridTask;
    private boolean mIsDesktopTask;
    private boolean mScaleToCarouselTaskSize = false;
    private int mTaskRectTranslationX;
    private int mTaskRectTranslationY;
    private int mPivotOffsetX;

    public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) {
        mContext = context;
@@ -124,6 +133,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
        mOrientationStateId = mOrientationState.getStateId();
        Resources resources = context.getResources();
        mIsRecentsRtl = mOrientationState.getOrientationHandler().getRecentsRtlSetting(resources);
        carouselScale.value = 1f;
    }

    /**
@@ -149,6 +159,11 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
                    mOrientationState.getOrientationHandler());
        }

        if (enableGridOnlyOverview()) {
            mSizeStrategy.calculateCarouselTaskSize(mContext, mDp, mCarouselTaskSize,
                    mOrientationState.getOrientationHandler());
        }

        if (mSplitBounds != null) {
            // The task rect changes according to the staged split task sizes, but recents
            // fullscreen scale and pivot remains the same since the task fits into the existing
@@ -193,9 +208,18 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
        }
        // Copy mFullTaskSize instead of updating it directly so it could be reused next time
        // without recalculating
        Rect scaleRect = new Rect(mFullTaskSize);
        scaleRect.offset(mTaskRectTranslationX + mPivotOffsetX, mTaskRectTranslationY);
        return mOrientationState.getFullScreenScaleAndPivot(scaleRect, mDp, mPivot);
        Rect scaleRect = new Rect();
        if (mScaleToCarouselTaskSize) {
            scaleRect.set(mCarouselTaskSize);
        } else {
            scaleRect.set(mFullTaskSize);
        }
        scaleRect.offset(mTaskRectTranslationX, mTaskRectTranslationY);
        float scale = mOrientationState.getFullScreenScaleAndPivot(scaleRect, mDp, mPivot);
        if (mPivotOverride != null) {
            mPivot.set(mPivotOverride);
        }
        return scale;
    }

    /**
@@ -278,14 +302,64 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
    /**
     * Adds animation for all the components corresponding to transition from an app to overview.
     */
    public void addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator) {
    public void addAppToOverviewAnim(PendingAnimation pa, Interpolator interpolator) {
        pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator);
        if (enableGridOnlyOverview() && mDp.isTablet) {
            int translationXToMiddle = mDp.widthPx / 2 - mFullTaskSize.centerX();
            taskPrimaryTranslation.value = translationXToMiddle;
            mPivotOffsetX = translationXToMiddle;
        float fullScreenScale;
        if (enableGridOnlyOverview() && mDp.isTablet && mDp.isGestureMode) {
            // Move pivot to top right edge of the screen, to avoid task scaling down in opposite
            // direction of app window movement, otherwise the animation will wiggle left and right.
            // Also translate the app window to top right edge of the screen to simplify
            // calculations.
            taskPrimaryTranslation.value = mIsRecentsRtl
                    ? mDp.widthPx - mFullTaskSize.right
                    : -mFullTaskSize.left;
            taskSecondaryTranslation.value = -mFullTaskSize.top;
            mPivotOverride = new PointF(mIsRecentsRtl ? mDp.widthPx : 0, 0);

            // Scale down to the carousel and use the carousel Rect to calculate fullScreenScale.
            mScaleToCarouselTaskSize = true;
            carouselScale.value = mCarouselTaskSize.width() / (float) mFullTaskSize.width();
            fullScreenScale = getFullScreenScale();

            float carouselPrimaryTranslationTarget = mIsRecentsRtl
                    ? mCarouselTaskSize.right - mDp.widthPx
                    : mCarouselTaskSize.left;
            float carouselSecondaryTranslationTarget = mCarouselTaskSize.top;

            // Expected carousel position's center is in the middle, and invariant of
            // recentsViewScale.
            float exceptedCarouselCenterX = mCarouselTaskSize.centerX();
            // Animating carousel translations linearly will result in a curved path, therefore
            // we'll need to calculate the expected translation at each recentsView scale. Luckily
            // primary and secondary follow the same translation, and primary is used here due to
            // it being simpler.
            Interpolator carouselTranslationInterpolator = t -> {
                // recentsViewScale is calculated rather than using recentsViewScale.value, so that
                // this interpolator works independently even if recentsViewScale don't animate.
                float recentsViewScale =
                        Utilities.mapToRange(t, 0, 1, fullScreenScale, 1, Interpolators.LINEAR);
                // Without the translation, the app window will animate from fullscreen into top
                // right corner.
                float expectedTaskCenterX = mIsRecentsRtl
                        ? mDp.widthPx - mCarouselTaskSize.width() * recentsViewScale / 2f
                        : mCarouselTaskSize.width() * recentsViewScale / 2f;
                // Calculate the expected translation, then work back the animatedFraction that
                // results in this value.
                float carouselPrimaryTranslation =
                        (exceptedCarouselCenterX - expectedTaskCenterX) / recentsViewScale;
                return carouselPrimaryTranslation / carouselPrimaryTranslationTarget;
            };

            // Use addAnimatedFloat so this animation can later be canceled and animate to a
            // different value in RecentsView.onPrepareGestureEndAnimation.
            pa.addAnimatedFloat(carouselPrimaryTranslation, 0, carouselPrimaryTranslationTarget,
                    carouselTranslationInterpolator);
            pa.addAnimatedFloat(carouselSecondaryTranslation, 0, carouselSecondaryTranslationTarget,
                    carouselTranslationInterpolator);
        } else {
            fullScreenScale = getFullScreenScale();
        }
        pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, getFullScreenScale(), 1, interpolator);
        pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, fullScreenScale, 1, interpolator);
    }

    /**
@@ -382,7 +456,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {

        float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1);
        mCurrentFullscreenParams.setProgress(fullScreenProgress, recentsViewScale.value,
                /* taskViewScale= */1f);
                carouselScale.value);

        // Apply thumbnail matrix
        float taskWidth = mTaskRect.width();
@@ -396,6 +470,13 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
                taskPrimaryTranslation.value);
        mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
                taskSecondaryTranslation.value);

        mMatrix.postScale(carouselScale.value, carouselScale.value, mPivot.x, mPivot.y);
        mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE,
                carouselPrimaryTranslation.value);
        mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
                carouselSecondaryTranslation.value);

        mOrientationState.getOrientationHandler().setPrimary(
                mMatrix, MATRIX_POST_TRANSLATE, recentsViewScroll.value);

@@ -420,15 +501,18 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
            return;
        }
        Log.d(TAG, "progress: " + fullScreenProgress
                + " carouselScale: " + carouselScale.value
                + " recentsViewScale: " + recentsViewScale.value
                + " crop: " + mTmpCropRect
                + " radius: " + getCurrentCornerRadius()
                + " taskW: " + taskWidth + " H: " + taskHeight
                + " taskRect: " + mTaskRect
                + " taskPrimaryT: " + taskPrimaryTranslation.value
                + " taskSecondaryT: " + taskSecondaryTranslation.value
                + " carouselPrimaryT: " + carouselPrimaryTranslation.value
                + " carouselSecondaryT: " + carouselSecondaryTranslation.value
                + " recentsPrimaryT: " + recentsViewPrimaryTranslation.value
                + " recentsSecondaryT: " + recentsViewSecondaryTranslation.value
                + " taskSecondaryT: " + taskSecondaryTranslation.value
                + " recentsScroll: " + recentsViewScroll.value
                + " pivot: " + mPivot
        );
Loading