Merge "Use bigger task size in app to overview carousel" into main

This commit is contained in:
Alex Chau
2024-02-12 16:17:30 +00:00
committed by Android (Google) Code Review
9 changed files with 188 additions and 28 deletions
+4 -1
View File
@@ -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 -->
@@ -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 =
@@ -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);
@@ -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,7 +306,9 @@ public class AnimatorControllerWithResistance {
resistanceParams =
recentsOrientedState.getActivityInterface().allowAllAppsFromOverview()
? RecentsResistanceParams.FROM_APP_TO_ALL_APPS_TABLET
: RecentsResistanceParams.FROM_APP_TABLET;
: enableGridOnlyOverview()
? RecentsResistanceParams.FROM_APP_TABLET_GRID_ONLY
: RecentsResistanceParams.FROM_APP_TABLET;
} else {
resistanceParams =
recentsOrientedState.getActivityInterface().allowAllAppsFromOverview()
@@ -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
);
@@ -459,6 +459,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
@Nullable
protected RemoteTargetHandle[] mRemoteTargetHandles;
protected final Rect mLastComputedCarouselTaskSize = new Rect();
protected final Rect mLastComputedTaskSize = new Rect();
protected final Rect mLastComputedGridSize = new Rect();
protected final Rect mLastComputedGridTaskSize = new Rect();
@@ -2108,6 +2109,10 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
mSizeStrategy.calculateDesktopTaskSize(mActivity, mActivity.getDeviceProfile(),
mLastComputedDesktopTaskSize);
}
if (enableGridOnlyOverview()) {
mSizeStrategy.calculateCarouselTaskSize(mActivity, dp, mLastComputedCarouselTaskSize,
getPagedOrientationHandler());
}
mTaskGridVerticalDiff = mLastComputedGridTaskSize.top - mLastComputedTaskSize.top;
mTopBottomRowHeightDiff =
@@ -2137,9 +2142,12 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
}
float accumulatedTranslationX = 0;
float translateXToMiddle = enableGridOnlyOverview() && mActivity.getDeviceProfile().isTablet
? mActivity.getDeviceProfile().widthPx / 2 - mLastComputedGridTaskSize.centerX()
: 0;
float translateXToMiddle = 0;
if (enableGridOnlyOverview() && mActivity.getDeviceProfile().isTablet) {
translateXToMiddle = mIsRtl
? mLastComputedCarouselTaskSize.right - mLastComputedTaskSize.right
: mLastComputedCarouselTaskSize.left - mLastComputedTaskSize.left;
}
for (int i = 0; i < taskCount; i++) {
TaskView taskView = requireTaskViewAt(i);
taskView.updateTaskSize();
@@ -2206,6 +2214,10 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
return mLastComputedDesktopTaskSize;
}
public Rect getLastComputedCarouselTaskSize() {
return mLastComputedCarouselTaskSize;
}
/** Gets the task size for modal state. */
public void getModalTaskSize(Rect outRect) {
mSizeStrategy.calculateModalTaskSize(mActivity, mActivity.getDeviceProfile(), outRect,
@@ -2675,6 +2687,9 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
tvs.taskSecondaryTranslation.value = runningTaskSecondaryGridTranslation;
} else {
animatorSet.play(ObjectAnimator.ofFloat(this, RECENTS_GRID_PROGRESS, 1));
animatorSet.play(tvs.carouselScale.animateToValue(1));
animatorSet.play(tvs.carouselPrimaryTranslation.animateToValue(0));
animatorSet.play(tvs.carouselSecondaryTranslation.animateToValue(0));
animatorSet.play(tvs.taskPrimaryTranslation.animateToValue(
runningTaskPrimaryGridTranslation));
animatorSet.play(tvs.taskSecondaryTranslation.animateToValue(
@@ -4411,12 +4426,13 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
mTempPointF.set(mLastComputedTaskSize.centerX(), mLastComputedTaskSize.bottom);
}
} else {
mTempRect.set(mLastComputedTaskSize);
// Only update pivot when it is tablet and not in grid yet, so the pivot is correct
// for non-current tasks when swiping up to overview
if (enableGridOnlyOverview() && mActivity.getDeviceProfile().isTablet
&& !mOverviewGridEnabled) {
mTempRect.offset(mActivity.getDeviceProfile().widthPx / 2 - mTempRect.centerX(), 0);
mTempRect.set(mLastComputedCarouselTaskSize);
} else {
mTempRect.set(mLastComputedTaskSize);
}
getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect,
mActivity.getDeviceProfile(), mTempPointF);
@@ -5117,7 +5133,12 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
* Returns the scale up required on the view, so that it coves the screen completely
*/
public float getMaxScaleForFullScreen() {
getTaskSize(mTempRect);
if (enableGridOnlyOverview() && mActivity.getDeviceProfile().isTablet
&& !mOverviewGridEnabled) {
mTempRect.set(mLastComputedCarouselTaskSize);
} else {
mTempRect.set(mLastComputedTaskSize);
}
return getPagedViewOrientedState().getFullScreenScaleAndPivot(
mTempRect, mActivity.getDeviceProfile(), mTempPointF);
}
@@ -5545,7 +5566,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
for (int i = 0; i < taskCount; i++) {
TaskView taskView = requireTaskViewAt(i);
float scrollDiff = taskView.getScrollAdjustment(showAsGrid);
int pageScroll = newPageScrolls[i] + (int) scrollDiff;
int pageScroll = newPageScrolls[i] + Math.round(scrollDiff);
if ((mIsRtl && pageScroll < lastTaskScroll)
|| (!mIsRtl && pageScroll > lastTaskScroll)) {
pageScroll = lastTaskScroll;
@@ -23,6 +23,7 @@ import static android.widget.Toast.LENGTH_SHORT;
import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.Flags.enableCursorHoverStates;
import static com.android.launcher3.Flags.enableGridOnlyOverview;
import static com.android.launcher3.Flags.enableOverviewIconMenu;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
@@ -1750,7 +1751,13 @@ public class TaskView extends FrameLayout implements Reusable {
expectedHeight = boxHeight + thumbnailPadding;
// Scale to to fit task Rect.
nonGridScale = taskWidth / (float) boxWidth;
if (enableGridOnlyOverview()) {
final Rect lastComputedCarouselTaskSize =
getRecentsView().getLastComputedCarouselTaskSize();
nonGridScale = lastComputedCarouselTaskSize.width() / (float) taskWidth;
} else {
nonGridScale = taskWidth / (float) boxWidth;
}
// Align to top of task Rect.
boxTranslationY = (expectedHeight - thumbnailPadding - taskHeight) / 2.0f;
@@ -109,6 +109,13 @@ public class AnimatedFloat {
public void cancelAnimation() {
if (mValueAnimator != null) {
mValueAnimator.cancel();
// Clears the property values, so further ObjectAnimator#setCurrentFraction from e.g.
// AnimatorPlaybackController calls would do nothing. The null check is necessary to
// avoid mValueAnimator being set to null in onAnimationEnd.
if (mValueAnimator != null) {
mValueAnimator.setValues();
mValueAnimator = null;
}
}
}
@@ -83,6 +83,20 @@ public class PendingAnimation extends AnimatedPropertySetter {
add(anim);
}
/**
* Add an {@link AnimatedFloat} to the animation.
* <p>
* Different from {@link #addFloat}, this method use animator provided by
* {@link AnimatedFloat#animateToValue}, which tracks the animator inside the AnimatedFloat,
* allowing the animation to be canceled and animate again from AnimatedFloat side.
*/
public void addAnimatedFloat(AnimatedFloat target, float from, float to,
TimeInterpolator interpolator) {
Animator anim = target.animateToValue(from, to);
anim.setInterpolator(interpolator);
add(anim);
}
/** If trace is enabled, add counter to trace animation progress. */
public void logAnimationProgressToTrace(String counterName) {
if (Trace.isEnabled()) {