diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java index 085b9b3af9..5ccc1e8681 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java @@ -17,6 +17,7 @@ package com.android.launcher3.uioverrides; import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE; import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA; import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; import static com.android.quickstep.views.RecentsView.TASK_MODALNESS; @@ -57,7 +58,7 @@ public final class RecentsViewStateController extends mRecentsView.updateEmptyMessage(); mRecentsView.resetTaskVisuals(); } - setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state); + setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, new StateAnimationConfig(), state); mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress()); } @@ -75,17 +76,19 @@ public final class RecentsViewStateController extends AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals)); } - setAlphas(builder, toState); + setAlphas(builder, config, toState); builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS, toState.getOverviewFullscreenProgress(), LINEAR); } - private void setAlphas(PropertySetter propertySetter, LauncherState state) { + private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config, + LauncherState state) { float buttonAlpha = (state.getVisibleElements(mLauncher) & OVERVIEW_BUTTONS) != 0 ? 1 : 0; propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA, buttonAlpha, LINEAR); propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(), - MultiValueAlpha.VALUE, buttonAlpha, LINEAR); + MultiValueAlpha.VALUE, buttonAlpha, config.getInterpolator( + ANIM_OVERVIEW_ACTIONS_FADE, LINEAR)); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java index 131fcbfbf1..daa1aadbae 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java @@ -25,6 +25,7 @@ import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_PEEK; import static com.android.launcher3.WorkspaceStateTransitionAnimation.getSpringScaleAnimator; import static com.android.launcher3.anim.Interpolators.ACCEL; +import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7; import static com.android.launcher3.anim.Interpolators.DEACCEL_3; @@ -163,10 +164,15 @@ public class QuickstepAtomicAnimationFactory extends config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL); config.setInterpolator(ANIM_ALL_APPS_FADE, ACCEL); config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f)); - config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL); - config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7); - Workspace workspace = mActivity.getWorkspace(); + config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL_DEACCEL); + if (SysUINavigationMode.getMode(mActivity) == NO_BUTTON) { + config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME); + } else { + config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7); + } + + Workspace workspace = mActivity.getWorkspace(); // Start from a higher workspace scale, but only if we're invisible so we don't jump. boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE; if (isWorkspaceVisible) { @@ -206,8 +212,10 @@ public class QuickstepAtomicAnimationFactory extends config.setInterpolator(ANIM_WORKSPACE_SCALE, fromState == NORMAL ? ACCEL : OVERSHOOT_1_2); config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL); + config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT); } else { config.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2); + config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2); // Scale up the recents, if it is not coming from the side RecentsView overview = mActivity.getOverviewPanel(); @@ -225,7 +233,6 @@ public class QuickstepAtomicAnimationFactory extends : OVERSHOOT_1_7; config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator); config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, translationInterpolator); - config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2); } else if (fromState == HINT_STATE && toState == NORMAL) { config.setInterpolator(ANIM_DEPTH, DEACCEL_3); if (mHintToNormalDuration == -1) { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java index c1a585ea9d..61f6702dcc 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java @@ -19,13 +19,13 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS; import static com.android.launcher3.anim.Interpolators.DEACCEL_3; import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE; import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS; -import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import android.animation.ValueAnimator; @@ -45,6 +45,7 @@ import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.graphics.OverviewScrim; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.testing.TestProtocol; @@ -52,7 +53,9 @@ import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.util.TouchController; +import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.util.AssistantUtilities; +import com.android.quickstep.util.OverviewToHomeAnim; import com.android.quickstep.views.RecentsView; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -63,6 +66,8 @@ public class NavBarToHomeTouchController implements TouchController, SingleAxisSwipeDetector.Listener { private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3; + // How much of the overview scrim we can remove during the transition. + private static final float OVERVIEW_TO_HOME_SCRIM_PROGRESS = 0.5f; private final Launcher mLauncher; private final SingleAxisSwipeDetector mSwipeDetector; @@ -156,8 +161,13 @@ public class NavBarToHomeTouchController implements TouchController, final PendingAnimation builder = new PendingAnimation(accuracy); if (mStartState.overviewUi) { RecentsView recentsView = mLauncher.getOverviewPanel(); - builder.setFloat(recentsView, ADJACENT_PAGE_OFFSET, - -mPullbackDistance / recentsView.getPageOffsetScale(), PULLBACK_INTERPOLATOR); + AnimatorControllerWithResistance.createRecentsResistanceFromOverviewAnim(mLauncher, + builder); + float endScrimAlpha = Utilities.mapRange(OVERVIEW_TO_HOME_SCRIM_PROGRESS, + mStartState.getOverviewScrimAlpha(mLauncher), + mEndState.getOverviewScrimAlpha(mLauncher)); + builder.setFloat(mLauncher.getDragLayer().getOverviewScrim(), + OverviewScrim.SCRIM_PROGRESS, endScrimAlpha, PULLBACK_INTERPOLATOR); if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { builder.addOnFrameCallback( () -> recentsView.redrawLiveTile(false /* mightNeedToRefill */)); @@ -212,8 +222,13 @@ public class NavBarToHomeTouchController implements TouchController, recentsView.switchToScreenshot(null, () -> recentsView.finishRecentsAnimation(true /* toRecents */, null)); } - mLauncher.getStateManager().goToState(mEndState, true, - () -> onSwipeInteractionCompleted(mEndState)); + if (mStartState == OVERVIEW) { + new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState)) + .animateWithVelocity(velocity); + } else { + mLauncher.getStateManager().goToState(mEndState, true, + () -> onSwipeInteractionCompleted(mEndState)); + } if (mStartState != mEndState) { logStateChange(mStartState.containerType, logAction); } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java index 9316938c45..8f0f683d18 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java @@ -21,11 +21,8 @@ import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.Utilities.EDGE_NAV_BAR; import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; -import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK; import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; -import android.animation.Animator; -import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.graphics.PointF; @@ -35,14 +32,15 @@ import android.view.MotionEvent; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.Utilities; -import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.graphics.OverviewScrim; import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.util.VibratorWrapper; -import com.android.quickstep.util.StaggeredWorkspaceAnim; +import com.android.quickstep.util.AnimatorControllerWithResistance; +import com.android.quickstep.util.OverviewToHomeAnim; import com.android.quickstep.views.RecentsView; /** @@ -62,10 +60,10 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo private boolean mDidTouchStartInNavBar; private boolean mReachedOverview; - private boolean mIsOverviewRehidden; - private boolean mIsHomeStaggeredAnimFinished; // The last recorded displacement before we reached overview. private PointF mStartDisplacement = new PointF(); + private float mStartY; + private AnimatorPlaybackController mOverviewResistYAnim; // Normal to Hint animation has flag SKIP_OVERVIEW, so we update this scrim with this animator. private ObjectAnimator mNormalToHintOverviewScrimAnimator; @@ -123,6 +121,7 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo mToState.getOverviewScrimAlpha(mLauncher)); } mReachedOverview = false; + mOverviewResistYAnim = null; } @Override @@ -137,6 +136,11 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo public void onDragEnd(float velocity) { super.onDragEnd(velocity); mNormalToHintOverviewScrimAnimator = null; + if (mLauncher.isInState(OVERVIEW)) { + // Normally we would cleanup the state based on mCurrentAnimation, but since we stop + // using that when we pause to go to Overview, we need to clean up ourselves. + clearState(); + } } @Override @@ -160,6 +164,9 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo mNormalToHintOverviewScrimAnimator = null; mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(() -> { mLauncher.getStateManager().goToState(OVERVIEW, true, () -> { + mOverviewResistYAnim = AnimatorControllerWithResistance + .createRecentsResistanceFromOverviewAnim(mLauncher, null) + .createPlaybackController(); mReachedOverview = true; maybeSwipeInteractionToOverviewComplete(); }); @@ -173,13 +180,6 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo } } - // Used if flinging back to home after reaching overview - private void maybeSwipeInteractionToHomeComplete() { - if (mIsHomeStaggeredAnimFinished && mIsOverviewRehidden) { - onSwipeInteractionCompleted(NORMAL, Touch.FLING); - } - } - @Override protected boolean handlingOverviewAnim() { return mDidTouchStartInNavBar && super.handlingOverviewAnim(); @@ -193,11 +193,17 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo if (mMotionPauseDetector.isPaused()) { if (!mReachedOverview) { mStartDisplacement.set(xDisplacement, yDisplacement); + mStartY = event.getY(); } else { mRecentsView.setTranslationX((xDisplacement - mStartDisplacement.x) * OVERVIEW_MOVEMENT_FACTOR); - mRecentsView.setTranslationY((yDisplacement - mStartDisplacement.y) - * OVERVIEW_MOVEMENT_FACTOR); + float yProgress = (mStartDisplacement.y - yDisplacement) / mStartY; + if (yProgress > 0 && mOverviewResistYAnim != null) { + mOverviewResistYAnim.setPlayFraction(yProgress); + } else { + mRecentsView.setTranslationY((yDisplacement - mStartDisplacement.y) + * OVERVIEW_MOVEMENT_FACTOR); + } } // Stay in Overview. return true; @@ -212,35 +218,8 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo StateManager stateManager = mLauncher.getStateManager(); boolean goToHomeInsteadOfOverview = isFling; if (goToHomeInsteadOfOverview) { - if (velocity > 0) { - stateManager.goToState(NORMAL, true, - () -> onSwipeInteractionCompleted(NORMAL, Touch.FLING)); - } else { - mIsHomeStaggeredAnimFinished = mIsOverviewRehidden = false; - - StaggeredWorkspaceAnim staggeredWorkspaceAnim = new StaggeredWorkspaceAnim( - mLauncher, velocity, false /* animateOverviewScrim */); - staggeredWorkspaceAnim.addAnimatorListener(new AnimationSuccessListener() { - @Override - public void onAnimationSuccess(Animator animator) { - mIsHomeStaggeredAnimFinished = true; - maybeSwipeInteractionToHomeComplete(); - } - }).start(); - - // StaggeredWorkspaceAnim doesn't animate overview, so we handle it here. - stateManager.cancelAnimation(); - StateAnimationConfig config = new StateAnimationConfig(); - config.duration = OVERVIEW.getTransitionDuration(mLauncher); - config.animFlags = PLAY_ATOMIC_OVERVIEW_PEEK; - AnimatorSet anim = stateManager.createAtomicAnimation( - stateManager.getState(), NORMAL, config); - anim.addListener(AnimationSuccessListener.forRunnable(() -> { - mIsOverviewRehidden = true; - maybeSwipeInteractionToHomeComplete(); - })); - anim.start(); - } + new OverviewToHomeAnim(mLauncher, ()-> onSwipeInteractionCompleted(NORMAL, Touch.FLING)) + .animateWithVelocity(velocity); } if (mReachedOverview) { float distanceDp = dpiFromPx(Math.max( @@ -256,6 +235,13 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo .withEndAction(goToHomeInsteadOfOverview ? null : this::maybeSwipeInteractionToOverviewComplete); + if (!goToHomeInsteadOfOverview) { + // Return to normal properties for the overview state. + StateAnimationConfig config = new StateAnimationConfig(); + config.duration = duration; + LauncherState state = mLauncher.getStateManager().getState(); + mLauncher.getStateManager().createAtomicAnimation(state, state, config).start(); + } } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java index 821ada400e..5c9fd4705b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -22,7 +22,6 @@ import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS; import static com.android.launcher3.LauncherState.QUICK_SWITCH; import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD; import static com.android.launcher3.anim.Interpolators.ACCEL_0_75; -import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL_5; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity; @@ -47,6 +46,7 @@ import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK; import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET; import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; +import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import android.animation.Animator; @@ -74,7 +74,9 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.util.TouchController; import com.android.launcher3.util.VibratorWrapper; +import com.android.quickstep.AnimatedFloat; import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.util.MotionPauseDetector; import com.android.quickstep.util.ShelfPeekAnim; @@ -90,16 +92,17 @@ public class NoButtonQuickSwitchTouchController implements TouchController, BothAxesSwipeDetector.Listener, MotionPauseDetector.OnMotionPauseListener { /** The minimum progress of the scale/translationY animation until drag end. */ - private static final float Y_ANIM_MIN_PROGRESS = 0.15f; + private static final float Y_ANIM_MIN_PROGRESS = 0.25f; private static final Interpolator FADE_OUT_INTERPOLATOR = DEACCEL_5; private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCEL_0_75; - private static final Interpolator SCALE_DOWN_INTERPOLATOR = DEACCEL; + private static final Interpolator SCALE_DOWN_INTERPOLATOR = LINEAR; private final BaseQuickstepLauncher mLauncher; private final BothAxesSwipeDetector mSwipeDetector; private final ShelfPeekAnim mShelfPeekAnim; private final float mXRange; private final float mYRange; + private final float mMaxYProgress; private final MotionPauseDetector mMotionPauseDetector; private final float mMotionPauseMinDisplacement; private final LauncherRecentsView mRecentsView; @@ -113,7 +116,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController, // and the other two to set overview properties based on x and y progress. private AnimatorPlaybackController mNonOverviewAnim; private AnimatorPlaybackController mXOverviewAnim; - private AnimatorPlaybackController mYOverviewAnim; + private AnimatedFloat mYOverviewAnim; public NoButtonQuickSwitchTouchController(BaseQuickstepLauncher launcher) { mLauncher = launcher; @@ -123,6 +126,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController, mXRange = mLauncher.getDeviceProfile().widthPx / 2f; mYRange = LayoutUtils.getShelfTrackingDistance( mLauncher, mLauncher.getDeviceProfile(), mRecentsView.getPagedOrientationHandler()); + mMaxYProgress = mLauncher.getDeviceProfile().heightPx / mYRange; mMotionPauseDetector = new MotionPauseDetector(mLauncher); mMotionPauseMinDisplacement = mLauncher.getResources().getDimension( R.dimen.motion_pause_detector_min_displacement_from_app); @@ -270,8 +274,18 @@ public class NoButtonQuickSwitchTouchController implements TouchController, SCALE_DOWN_INTERPOLATOR); yAnim.setFloat(mRecentsView, FULLSCREEN_PROGRESS, toState.getOverviewFullscreenProgress(), SCALE_DOWN_INTERPOLATOR); - mYOverviewAnim = yAnim.createPlaybackController(); - mYOverviewAnim.dispatchOnStart(); + AnimatorPlaybackController yNormalController = yAnim.createPlaybackController(); + AnimatorControllerWithResistance yAnimWithResistance = AnimatorControllerWithResistance + .createForRecents(yNormalController, mLauncher, + mRecentsView.getPagedViewOrientedState(), mLauncher.getDeviceProfile(), + mRecentsView, RECENTS_SCALE_PROPERTY, mRecentsView, + TASK_SECONDARY_TRANSLATION); + mYOverviewAnim = new AnimatedFloat(() -> { + if (mYOverviewAnim != null) { + yAnimWithResistance.setProgress(mYOverviewAnim.value, mMaxYProgress); + } + }); + yNormalController.dispatchOnStart(); } @Override @@ -307,7 +321,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController, mXOverviewAnim.setPlayFraction(xProgress); } if (mYOverviewAnim != null) { - mYOverviewAnim.setPlayFraction(yProgress); + mYOverviewAnim.updateValue(yProgress); } return true; } @@ -354,9 +368,11 @@ public class NoButtonQuickSwitchTouchController implements TouchController, } else if (verticalFling) { targetState = velocity.y > 0 ? QUICK_SWITCH : NORMAL; } else { - // If user isn't flinging, just snap to the closest state based on x progress. + // If user isn't flinging, just snap to the closest state. boolean passedHorizontalThreshold = mXOverviewAnim.getInterpolatedProgress() > 0.5f; - targetState = passedHorizontalThreshold ? QUICK_SWITCH : NORMAL; + boolean passedVerticalThreshold = mYOverviewAnim.value > 1f; + targetState = passedHorizontalThreshold && !passedVerticalThreshold + ? QUICK_SWITCH : NORMAL; } // Animate the various components to the target state. @@ -375,9 +391,9 @@ public class NoButtonQuickSwitchTouchController implements TouchController, boolean flingUpToNormal = verticalFling && velocity.y < 0 && targetState == NORMAL; - float yProgress = mYOverviewAnim.getProgressFraction(); + float yProgress = mYOverviewAnim.value; float startYProgress = Utilities.boundToRange(yProgress - - velocity.y * getSingleFrameMs(mLauncher) / mYRange, 0f, 1f); + - velocity.y * getSingleFrameMs(mLauncher) / mYRange, 0f, mMaxYProgress); final float endYProgress; if (flingUpToNormal) { endYProgress = 1; @@ -387,12 +403,11 @@ public class NoButtonQuickSwitchTouchController implements TouchController, } else { endYProgress = 0; } - long yDuration = BaseSwipeDetector.calculateDuration(velocity.y, - Math.abs(endYProgress - startYProgress)); - ValueAnimator yOverviewAnim = mYOverviewAnim.getAnimationPlayer(); - yOverviewAnim.setFloatValues(startYProgress, endYProgress); + float yDistanceToCover = Math.abs(endYProgress - startYProgress) * mYRange; + long yDuration = (long) (yDistanceToCover / Math.max(1f, Math.abs(velocity.y))); + ValueAnimator yOverviewAnim = mYOverviewAnim.animateToValue(startYProgress, endYProgress); yOverviewAnim.setDuration(yDuration); - mYOverviewAnim.dispatchOnStart(); + mYOverviewAnim.updateValue(startYProgress); ValueAnimator nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer(); if (flingUpToNormal && !mIsHomeScreenVisible) { @@ -457,7 +472,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController, mXOverviewAnim.getAnimationPlayer().cancel(); } if (mYOverviewAnim != null) { - mYOverviewAnim.getAnimationPlayer().cancel(); + mYOverviewAnim.cancelAnimation(); } mShelfPeekAnim.setShelfState(ShelfAnimState.CANCEL, LINEAR, 0); mMotionPauseDetector.clear(); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java index 0ee5d047c6..7bae2e5d86 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java @@ -25,6 +25,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.view.MotionEvent; import android.view.View; +import android.view.animation.Interpolator; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BaseDraggingActivity; @@ -206,14 +207,19 @@ public abstract class TaskViewTouchController long maxDuration = 2 * secondaryLayerDimension; int verticalFactor = orientationHandler.getTaskDragDisplacementFactor(mIsRtl); int secondaryTaskDimension = orientationHandler.getSecondaryDimension(mTaskBeingDragged); + // The interpolator controlling the most prominent visual movement. We use this to determine + // whether we passed SUCCESS_TRANSITION_PROGRESS. + final Interpolator currentInterpolator; if (goingUp) { + currentInterpolator = Interpolators.LINEAR; mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged, true /* animateTaskView */, true /* removeTask */, maxDuration); mEndDisplacement = -secondaryTaskDimension; } else { + currentInterpolator = Interpolators.ZOOM_IN; mPendingAnimation = mRecentsView.createTaskLaunchAnimation( - mTaskBeingDragged, maxDuration, Interpolators.ZOOM_IN); + mTaskBeingDragged, maxDuration, currentInterpolator); // Since the thumbnail is what is filling the screen, based the end displacement on it. View thumbnailView = mTaskBeingDragged.getThumbnail(); @@ -228,6 +234,9 @@ public abstract class TaskViewTouchController } mCurrentAnimation = mPendingAnimation.createPlaybackController() .setOnCancelRunnable(this::clearState); + // Setting this interpolator doesn't affect the visual motion, but is used to determine + // whether we successfully reached the target state in onDragEnd(). + mCurrentAnimation.getTarget().setInterpolator(currentInterpolator); onUserControlledAnimationCreated(mCurrentAnimation); mCurrentAnimation.getTarget().addListener(this); mCurrentAnimation.dispatchOnStart(); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java index 9310685e30..55f542461c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -79,8 +79,8 @@ final class AppToOverviewAnimationProvider> extend BaseActivityInterface.AnimationFactory factory = mActivityInterface.prepareRecentsUI( mDeviceState, wasVisible, (controller) -> { - controller.dispatchOnStart(); - controller.getAnimationPlayer().end(); + controller.getNormalController().dispatchOnStart(); + controller.getNormalController().getAnimationPlayer().end(); }); factory.createActivityInterface(RECENTS_LAUNCH_DURATION); factory.setRecentsAttachedToAppWindow(true, false); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java index cbef67b84b..c90f70125a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java @@ -365,8 +365,7 @@ public abstract class BaseSwipeUpHandler, Q extend */ protected void applyWindowTransform() { if (mWindowTransitionController != null) { - float progress = mCurrentShift.value / mDragLengthFactor; - mWindowTransitionController.setPlayFraction(progress); + mWindowTransitionController.setProgress(mCurrentShift.value, mDragLengthFactor); } if (mRecentsAnimationTargets != null) { if (mRecentsViewScrollLinked) { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java index 758066f8c7..0aa14862a7 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java @@ -21,7 +21,6 @@ import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; -import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE; @@ -45,7 +44,6 @@ import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHO import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME; import android.animation.Animator; -import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.app.ActivityManager; @@ -67,7 +65,6 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimationSuccessListener; -import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.logging.UserEventDispatcher; @@ -80,6 +77,7 @@ import com.android.quickstep.BaseActivityInterface.AnimationFactory; import com.android.quickstep.GestureState.GestureEndTarget; import com.android.quickstep.inputconsumers.OverviewInputConsumer; import com.android.quickstep.util.ActiveGestureLog; +import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.ShelfPeekAnim; import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState; @@ -181,8 +179,7 @@ public abstract class BaseSwipeUpHandlerV2, Q exte private ThumbnailData mTaskSnapshot; // Used to control launcher components throughout the swipe gesture. - private AnimatorPlaybackController mLauncherTransitionController; - private boolean mHasLauncherTransitionControllerStarted; + private AnimatorControllerWithResistance mLauncherTransitionController; private AnimationFactory mAnimationFactory = (t) -> { }; @@ -491,6 +488,20 @@ public abstract class BaseSwipeUpHandlerV2, Q exte recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask; } mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate); + + // Reapply window transform throughout the attach animation, as the animation affects how + // much the window is bound by overscroll (vs moving freely). + if (animate) { + ValueAnimator reapplyWindowTransformAnim = ValueAnimator.ofFloat(0, 1); + reapplyWindowTransformAnim.addUpdateListener(anim -> { + if (mRunningWindowAnim == null) { + applyWindowTransform(); + } + }); + reapplyWindowTransformAnim.setDuration(RECENTS_ATTACH_DURATION).start(); + } else { + applyWindowTransform(); + } } @Override @@ -528,11 +539,11 @@ public abstract class BaseSwipeUpHandlerV2, Q exte /** * We don't want to change mLauncherTransitionController if mGestureState.getEndTarget() == HOME - * (it has its own animation) or if we're already animating the current controller. + * (it has its own animation). * @return Whether we can create the launcher controller or update its progress. */ private boolean canCreateNewOrUpdateExistingLauncherTransitionController() { - return mGestureState.getEndTarget() != HOME && !mHasLauncherTransitionControllerStarted; + return mGestureState.getEndTarget() != HOME; } @Override @@ -542,10 +553,9 @@ public abstract class BaseSwipeUpHandlerV2, Q exte return result; } - private void onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim) { + private void onAnimatorPlaybackControllerCreated(AnimatorControllerWithResistance anim) { mLauncherTransitionController = anim; - mLauncherTransitionController.dispatchSetInterpolator(t -> t * mDragLengthFactor); - mLauncherTransitionController.dispatchOnStart(); + mLauncherTransitionController.getNormalController().dispatchOnStart(); updateLauncherTransitionProgress(); } @@ -582,10 +592,7 @@ public abstract class BaseSwipeUpHandlerV2, Q exte || !canCreateNewOrUpdateExistingLauncherTransitionController()) { return; } - // Normalize the progress to 0 to 1, as the animation controller will clamp it to that - // anyway. The controller mimics the drag length factor by applying it to its interpolators. - float progress = mCurrentShift.value / mDragLengthFactor; - mLauncherTransitionController.setPlayFraction(progress); + mLauncherTransitionController.setProgress(mCurrentShift.value, mDragLengthFactor); } /** @@ -1028,31 +1035,6 @@ public abstract class BaseSwipeUpHandlerV2, Q exte windowAnim.start(); mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim); } - // Always play the entire launcher animation when going home, since it is separate from - // the animation that has been controlled thus far. - if (mGestureState.getEndTarget() == HOME) { - start = 0; - } - - // We want to use the same interpolator as the window, but need to adjust it to - // interpolate over the remaining progress (end - start). - TimeInterpolator adjustedInterpolator = Interpolators.mapToProgress( - interpolator, start, end); - if (mLauncherTransitionController == null) { - return; - } - if (start == end || duration <= 0) { - mLauncherTransitionController.dispatchSetInterpolator(t -> end); - } else { - mLauncherTransitionController.dispatchSetInterpolator(adjustedInterpolator); - } - mLauncherTransitionController.getAnimationPlayer().setDuration(Math.max(0, duration)); - - if (UNSTABLE_SPRINGS.get()) { - mLauncherTransitionController.dispatchOnStart(); - } - mLauncherTransitionController.getAnimationPlayer().start(); - mHasLauncherTransitionControllerStarted = true; } private void computeRecentsScrollIfInvisible() { @@ -1173,10 +1155,6 @@ public abstract class BaseSwipeUpHandlerV2, Q exte private void cancelCurrentAnimation() { mCanceled = true; mCurrentShift.cancelAnimation(); - if (mLauncherTransitionController != null && mLauncherTransitionController - .getAnimationPlayer().isStarted()) { - mLauncherTransitionController.getAnimationPlayer().cancel(); - } } private void invalidateHandler() { @@ -1202,7 +1180,10 @@ public abstract class BaseSwipeUpHandlerV2, Q exte private void endLauncherTransitionController() { setShelfState(ShelfAnimState.CANCEL, LINEAR, 0); if (mLauncherTransitionController != null) { - mLauncherTransitionController.getAnimationPlayer().end(); + // End the animation, but stay at the same visual progress. + mLauncherTransitionController.getNormalController().dispatchSetInterpolator( + t -> Utilities.boundToRange(mCurrentShift.value, 0, 1)); + mLauncherTransitionController.getNormalController().getAnimationPlayer().end(); mLauncherTransitionController = null; } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java index d1da0c1c10..3898f0b0ca 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java @@ -27,11 +27,11 @@ import androidx.annotation.Nullable; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; -import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.quickstep.fallback.RecentsState; import com.android.quickstep.util.ActivityInitListener; +import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.views.RecentsView; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -84,7 +84,7 @@ public final class FallbackActivityInterface extends /** 6 */ @Override public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState, - boolean activityVisible, Consumer callback) { + boolean activityVisible, Consumer callback) { DefaultAnimationFactory factory = new DefaultAnimationFactory(callback); factory.initUI(); return factory; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java index 7cd49fe18d..cd4be1895b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java @@ -39,7 +39,6 @@ import com.android.launcher3.LauncherInitListener; import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.allapps.DiscoveryBounce; -import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.statehandlers.DepthController; @@ -50,6 +49,7 @@ import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.util.ActivityInitListener; +import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState; import com.android.quickstep.views.RecentsView; @@ -119,7 +119,7 @@ public final class LauncherActivityInterface extends @Override public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState, - boolean activityVisible, Consumer callback) { + boolean activityVisible, Consumer callback) { notifyRecentsOfOrientation(deviceState.getRotationTouchHelper()); DefaultAnimationFactory factory = new DefaultAnimationFactory(callback) { @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverscrollPluginFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverscrollPluginFactory.java new file mode 100644 index 0000000000..4c261abc7a --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverscrollPluginFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * 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. + */ +package com.android.quickstep; + +import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; + +import com.android.launcher3.R; +import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.launcher3.util.ResourceBasedOverride; +import com.android.systemui.plugins.OverscrollPlugin; + +/** + * Resource overrideable factory for forcing a local overscroll plugin. + * Override {@link R.string#overscroll_plugin_factory_class} to set a different class. + */ +public class OverscrollPluginFactory implements ResourceBasedOverride { + public static final MainThreadInitializedObject INSTANCE = forOverride( + OverscrollPluginFactory.class, + R.string.overscroll_plugin_factory_class); + + /** + * Get the plugin that is defined locally in launcher, as opposed to a dynamic side loaded one. + */ + public OverscrollPlugin getLocalOverscrollPlugin() { + return null; + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java index 6f4d34c8c7..5026f36e08 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java @@ -58,6 +58,12 @@ public class QuickstepTestInformationHandler extends TestInformationHandler { FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()); return response; } + + case TestProtocol.REQUEST_OVERVIEW_SHARE_ENABLED: { + response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, + FeatureFlags.ENABLE_OVERVIEW_SHARE.get()); + return response; + } } return super.call(method); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java index e54a21c073..2963b6ce18 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java @@ -16,7 +16,7 @@ package com.android.quickstep; import static com.android.launcher3.anim.Interpolators.ACCEL_1_5; -import static com.android.launcher3.anim.Interpolators.DEACCEL; +import static com.android.launcher3.anim.Interpolators.LINEAR; import android.animation.Animator; import android.content.Context; @@ -24,7 +24,6 @@ import android.graphics.Matrix; import android.graphics.Matrix.ScaleToFit; import android.graphics.Rect; import android.graphics.RectF; -import android.view.animation.Interpolator; import androidx.annotation.NonNull; import androidx.annotation.UiThread; @@ -35,6 +34,7 @@ import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.touch.PagedOrientationHandler; +import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TransformParams; @@ -45,7 +45,6 @@ import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat. public abstract class SwipeUpAnimationLogic { protected static final Rect TEMP_RECT = new Rect(); - private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL; protected DeviceProfile mDp; @@ -66,12 +65,8 @@ public abstract class SwipeUpAnimationLogic { protected int mTransitionDragLength; // How much further we can drag past recents, as a factor of mTransitionDragLength. protected float mDragLengthFactor = 1; - // Start resisting when swiping past this factor of mTransitionDragLength. - private float mDragLengthFactorStartPullback = 1f; - // This is how far down we can scale down, where 0f is full screen and 1f is recents. - private float mDragLengthFactorMaxPullback = 1f; - protected AnimatorPlaybackController mWindowTransitionController; + protected AnimatorControllerWithResistance mWindowTransitionController; public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState, GestureState gestureState, TransformParams transformParams) { @@ -97,19 +92,17 @@ public abstract class SwipeUpAnimationLogic { if (mDeviceState.isFullyGesturalNavMode()) { // We can drag all the way to the top of the screen. mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength; - - float startScale = mTaskViewSimulator.getFullScreenScale(); - // Start pulling back when RecentsView scale is 0.75f, and let it go down to 0.5f. - mDragLengthFactorStartPullback = (0.75f - startScale) / (1 - startScale); - mDragLengthFactorMaxPullback = (0.5f - startScale) / (1 - startScale); } else { - mDragLengthFactor = 1; - mDragLengthFactorStartPullback = mDragLengthFactorMaxPullback = 1; + mDragLengthFactor = 1 + AnimatorControllerWithResistance.TWO_BUTTON_EXTRA_DRAG_FACTOR; } PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2); - mTaskViewSimulator.addAppToOverviewAnim(pa, t -> t * mDragLengthFactor); - mWindowTransitionController = pa.createPlaybackController(); + mTaskViewSimulator.addAppToOverviewAnim(pa, LINEAR); + AnimatorPlaybackController normalController = pa.createPlaybackController(); + mWindowTransitionController = AnimatorControllerWithResistance.createForRecents( + normalController, mContext, mTaskViewSimulator.getOrientationState(), + mDp, mTaskViewSimulator.recentsViewScale, AnimatedFloat.VALUE, + mTaskViewSimulator.recentsViewSecondaryTranslation, AnimatedFloat.VALUE); } @UiThread @@ -122,13 +115,6 @@ public abstract class SwipeUpAnimationLogic { } else { float translation = Math.max(displacement, 0); shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength; - if (shift > mDragLengthFactorStartPullback) { - float pullbackProgress = Utilities.getProgress(shift, - mDragLengthFactorStartPullback, mDragLengthFactor); - pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress); - shift = mDragLengthFactorStartPullback + pullbackProgress - * (mDragLengthFactorMaxPullback - mDragLengthFactorStartPullback); - } } mCurrentShift.updateValue(shift); @@ -183,7 +169,7 @@ public abstract class SwipeUpAnimationLogic { HomeAnimationFactory homeAnimationFactory) { final RectF targetRect = homeAnimationFactory.getWindowTargetRect(); - mWindowTransitionController.setPlayFraction(startProgress / mDragLengthFactor); + mCurrentShift.updateValue(startProgress); mTaskViewSimulator.apply(mTransformParams.setProgress(startProgress)); RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect()); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java index db512fa0f9..36579ec736 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java @@ -18,7 +18,6 @@ package com.android.quickstep; import static android.view.Surface.ROTATION_0; -import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL; import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED; @@ -39,13 +38,11 @@ import com.android.launcher3.R; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.SystemShortcut; -import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.ResourceBasedOverride; import com.android.quickstep.util.RecentsOrientedState; import com.android.quickstep.views.OverviewActionsView; import com.android.quickstep.views.TaskThumbnailView; import com.android.quickstep.views.TaskView; -import com.android.systemui.plugins.OverscrollPlugin; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -91,20 +88,22 @@ public class TaskOverlayFactory implements ResourceBasedOverride { return shortcuts; } - public static final MainThreadInitializedObject INSTANCE = - forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class); - - /** - * @return a launcher-provided OverscrollPlugin if available, otherwise null - */ - public OverscrollPlugin getLocalOverscrollPlugin() { - return null; - } - public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) { return new TaskOverlay(thumbnailView); } + /** + * Subclasses can attach any system listeners in this method, must be paired with + * {@link #removeListeners()} + */ + public void initListeners() { } + + /** + * Subclasses should remove any system listeners in this method, must be paired with + * {@link #initListeners()} + */ + public void removeListeners() { } + /** Note that these will be shown in order from top to bottom, if available for the task. */ private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{ TaskShortcutFactory.APP_INFO, diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index 13adff53f3..7b91001c5f 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -597,9 +597,8 @@ public class TouchInteractionService extends Service implements PluginListener { return new float[] { NO_SCALE, NO_OFFSET }; } - private static class ModalState extends RecentsState { public ModalState(int id, int flags) { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/OverviewToHomeAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/OverviewToHomeAnim.java new file mode 100644 index 0000000000..6278e147c0 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/OverviewToHomeAnim.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2020 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * 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. + */ +package com.android.quickstep.util; + +import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.anim.Interpolators.DEACCEL; +import static com.android.launcher3.anim.Interpolators.FINAL_FRAME; +import static com.android.launcher3.anim.Interpolators.INSTANT; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y; +import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.util.Log; +import android.view.animation.Interpolator; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherState; +import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.statemanager.StateManager; +import com.android.launcher3.states.StateAnimationConfig; +import com.android.quickstep.views.RecentsView; + +/** + * Runs an animation from overview to home. Currently, this animation is just a wrapper around the + * normal state transition, in order to keep RecentsView at the same scale and translationY that + * it started out at as it translates offscreen. It also scrolls RecentsView to page 0 and may play + * a {@link StaggeredWorkspaceAnim} if we're starting from an upward fling. + */ +public class OverviewToHomeAnim { + + private static final String TAG = "OverviewToHomeAnim"; + + // Constants to specify how to scroll RecentsView to the default page if it's not already there. + private static final int DEFAULT_PAGE = 0; + private static final int PER_PAGE_SCROLL_DURATION = 150; + private static final int MAX_PAGE_SCROLL_DURATION = 750; + + private final Launcher mLauncher; + private final Runnable mOnReachedHome; + + // Only run mOnReachedHome when both of these are true. + private boolean mIsHomeStaggeredAnimFinished; + private boolean mIsOverviewHidden; + + public OverviewToHomeAnim(Launcher launcher, Runnable onReachedHome) { + mLauncher = launcher; + mOnReachedHome = onReachedHome; + } + + /** + * Starts the animation. If velocity < 0 (i.e. upwards), also plays a + * {@link StaggeredWorkspaceAnim}. + */ + public void animateWithVelocity(float velocity) { + StateManager stateManager = mLauncher.getStateManager(); + LauncherState startState = stateManager.getState(); + if (startState != OVERVIEW) { + Log.e(TAG, "animateFromOverviewToHome: unexpected start state " + startState); + } + AnimatorSet anim = new AnimatorSet(); + + boolean playStaggeredWorkspaceAnim = velocity < 0; + if (playStaggeredWorkspaceAnim) { + StaggeredWorkspaceAnim staggeredWorkspaceAnim = new StaggeredWorkspaceAnim( + mLauncher, velocity, false /* animateOverviewScrim */); + staggeredWorkspaceAnim.addAnimatorListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + mIsHomeStaggeredAnimFinished = true; + maybeOverviewToHomeAnimComplete(); + } + }); + anim.play(staggeredWorkspaceAnim.getAnimators()); + } else { + mIsHomeStaggeredAnimFinished = true; + } + + RecentsView recentsView = mLauncher.getOverviewPanel(); + int numPagesToScroll = recentsView.getNextPage() - DEFAULT_PAGE; + int scrollDuration = Math.min(MAX_PAGE_SCROLL_DURATION, + numPagesToScroll * PER_PAGE_SCROLL_DURATION); + int duration = Math.max(scrollDuration, startState.getTransitionDuration(mLauncher)); + + StateAnimationConfig config = new UseFirstInterpolatorStateAnimConfig(); + config.duration = duration; + config.animFlags = playStaggeredWorkspaceAnim + // StaggeredWorkspaceAnim doesn't animate overview, so we handle it here. + ? PLAY_ATOMIC_OVERVIEW_PEEK + : ANIM_ALL_COMPONENTS; + config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, DEACCEL); + config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, FINAL_FRAME); + config.setInterpolator(ANIM_OVERVIEW_SCALE, FINAL_FRAME); + config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, INSTANT); + AnimatorSet stateAnim = stateManager.createAtomicAnimation( + startState, NORMAL, config); + stateAnim.addListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + mIsOverviewHidden = true; + maybeOverviewToHomeAnimComplete(); + } + }); + anim.play(stateAnim); + stateManager.setCurrentAnimation(anim, NORMAL); + anim.start(); + recentsView.snapToPage(DEFAULT_PAGE, duration); + } + + private void maybeOverviewToHomeAnimComplete() { + if (mIsHomeStaggeredAnimFinished && mIsOverviewHidden) { + mOnReachedHome.run(); + } + } + + /** + * Wrapper around StateAnimationConfig that doesn't allow interpolators to be set if they are + * already set. This ensures they aren't overridden before being used. + */ + private static class UseFirstInterpolatorStateAnimConfig extends StateAnimationConfig { + @Override + public void setInterpolator(int animId, Interpolator interpolator) { + if (mInterpolators[animId] == null || interpolator == null) { + super.setInterpolator(animId, interpolator); + } + } + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java index c5918fed84..db64464a61 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java @@ -90,6 +90,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { // RecentsView properties public final AnimatedFloat recentsViewScale = new AnimatedFloat(); public final AnimatedFloat fullScreenProgress = new AnimatedFloat(); + public final AnimatedFloat recentsViewSecondaryTranslation = new AnimatedFloat(); private final ScrollState mScrollState = new ScrollState(); // Cached calculations @@ -274,8 +275,10 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { mOrientationState.getOrientationHandler().set( mMatrix, MATRIX_POST_TRANSLATE, mScrollState.scroll); - // Apply recensView matrix + // Apply RecentsView matrix mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y); + mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE, + recentsViewSecondaryTranslation.value); applyWindowToHomeRotation(mMatrix); // Crop rect is the inverse of thumbnail matrix diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java index 79028d1cc2..b934c29d2f 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java @@ -51,6 +51,7 @@ import com.android.launcher3.util.TraceHelper; import com.android.launcher3.views.ScrimView; import com.android.quickstep.LauncherActivityInterface; import com.android.quickstep.SysUINavigationMode; +import com.android.quickstep.util.OverviewToHomeAnim; import com.android.quickstep.util.TransformParams; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.RecentsExtraCard; @@ -105,12 +106,14 @@ public class LauncherRecentsView extends RecentsView @Override public void startHome() { + Runnable onReachedHome = () -> mActivity.getStateManager().goToState(NORMAL, false); + OverviewToHomeAnim overviewToHomeAnim = new OverviewToHomeAnim(mActivity, onReachedHome); if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { switchToScreenshot(null, () -> finishRecentsAnimation(true /* toRecents */, - () -> mActivity.getStateManager().goToState(NORMAL))); + () -> overviewToHomeAnim.animateWithVelocity(0))); } else { - mActivity.getStateManager().goToState(NORMAL); + overviewToHomeAnim.animateWithVelocity(0); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java index 79d57c55a2..1bf2fbf81f 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java @@ -105,6 +105,7 @@ public class OverviewActionsView extends FrameLayo public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr, 0); mMultiValueAlpha = new MultiValueAlpha(this, 4); + mMultiValueAlpha.setUpdateVisibility(true); } @Override @@ -168,7 +169,6 @@ public class OverviewActionsView extends FrameLayo } boolean isHidden = mHiddenFlags != 0; mMultiValueAlpha.getProperty(INDEX_HIDDEN_FLAGS_ALPHA).setValue(isHidden ? 0 : 1); - setVisibility(isHidden ? INVISIBLE : VISIBLE); } /** diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index 857a020698..1d17f5af70 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -24,6 +24,7 @@ import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAG import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS; import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; import static com.android.launcher3.LauncherState.BACKGROUND_APP; +import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK; import static com.android.launcher3.Utilities.EDGE_NAV_BAR; import static com.android.launcher3.Utilities.mapToRange; import static com.android.launcher3.Utilities.squaredHypot; @@ -93,6 +94,7 @@ import com.android.launcher3.BaseActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Insettable; import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.LauncherState; import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -115,6 +117,7 @@ import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.DynamicResource; import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.OverScroller; +import com.android.launcher3.util.ResourceBasedOverride.Overrides; import com.android.launcher3.util.Themes; import com.android.launcher3.util.ViewPool; import com.android.quickstep.BaseActivityInterface; @@ -123,6 +126,7 @@ import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.RecentsModel; import com.android.quickstep.RecentsModel.TaskVisualsChangeListener; import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.TaskOverlayFactory; import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskUtils; import com.android.quickstep.ViewUtils; @@ -210,6 +214,19 @@ public abstract class RecentsView extends PagedView } }; + public static final FloatProperty TASK_SECONDARY_TRANSLATION = + new FloatProperty("taskSecondaryTranslation") { + @Override + public void setValue(RecentsView recentsView, float v) { + recentsView.setTaskViewsSecondaryTranslation(v); + } + + @Override + public Float get(RecentsView recentsView) { + return recentsView.mTaskViewsSecondaryTranslation; + } + }; + /** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */ public static final FloatProperty RECENTS_SCALE_PROPERTY = new FloatProperty("recentsScale") { @@ -219,6 +236,7 @@ public abstract class RecentsView extends PagedView view.setScaleY(scale); view.mLastComputedTaskPushOutDistance = null; view.updatePageOffsets(); + view.setTaskViewsSecondaryTranslation(view.mTaskViewsSecondaryTranslation); } @Override @@ -263,12 +281,15 @@ public abstract class RecentsView extends PagedView private final ViewPool mTaskViewPool; + private final TaskOverlayFactory mTaskOverlayFactory; + private boolean mDwbToastShown; protected boolean mDisallowScrollToClearAll; private boolean mOverlayEnabled; protected boolean mFreezeViewVisibility; private float mAdjacentPageOffset = 0; + private float mTaskViewsSecondaryTranslation = 0; /** * TODO: Call reloadIdNeeded in onTaskStackChanged. @@ -449,6 +470,11 @@ public abstract class RecentsView extends PagedView updateEmptyMessage(); mOrientationHandler = mOrientationState.getOrientationHandler(); + mTaskOverlayFactory = Overrides.getObject( + TaskOverlayFactory.class, + context.getApplicationContext(), + R.string.task_overlay_factory_class); + // Initialize quickstep specific cache params here, as this is constructed only once mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5); } @@ -542,6 +568,7 @@ public abstract class RecentsView extends PagedView mIPinnedStackAnimationListener); mOrientationState.initListeners(); SplitScreenBounds.INSTANCE.addOnChangeListener(this); + mTaskOverlayFactory.initListeners(); } @Override @@ -558,6 +585,7 @@ public abstract class RecentsView extends PagedView SplitScreenBounds.INSTANCE.removeOnChangeListener(this); mIPinnedStackAnimationListener.setActivity(null); mOrientationState.destroyListeners(); + mTaskOverlayFactory.removeListeners(); } @Override @@ -1391,7 +1419,7 @@ public abstract class RecentsView extends PagedView FloatProperty secondaryViewTranslate = mOrientationHandler.getSecondaryViewTranslate(); int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView); - int verticalFactor = mOrientationHandler.getTaskDismissDirectionFactor(); + int verticalFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor(); ResourceProvider rp = DynamicResource.provider(mActivity); SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_START) @@ -1697,6 +1725,12 @@ public abstract class RecentsView extends PagedView if (mOrientationState.setRecentsRotation(mActivity.getDisplay().getRotation())) { updateOrientationHandler(); } + // If overview is in modal state when rotate, reset it to overview state without running + // animation. + if (mActivity.isInState(OVERVIEW_MODAL_TASK)) { + mActivity.getStateManager().goToState(LauncherState.OVERVIEW, false); + resetModalVisuals(); + } } public void setLayoutRotation(int touchRotation, int displayRotation) { @@ -1853,7 +1887,9 @@ public abstract class RecentsView extends PagedView : i < modalMidpoint ? modalLeftOffsetSize : modalRightOffsetSize; - getChildAt(i).setTranslationX(translation + modalTranslation); + float totalTranslation = translation + modalTranslation; + mOrientationHandler.getPrimaryViewTranslate().set(getChildAt(i), + totalTranslation * mOrientationHandler.getPrimaryTranslationDirectionFactor()); } updateCurveProperties(); } @@ -1906,6 +1942,14 @@ public abstract class RecentsView extends PagedView return distanceToOffscreen * offsetProgress; } + private void setTaskViewsSecondaryTranslation(float translation) { + mTaskViewsSecondaryTranslation = translation; + for (int i = 0; i < getTaskViewCount(); i++) { + TaskView task = getTaskViewAt(i); + mOrientationHandler.getSecondaryViewTranslate().set(task, translation / getScaleY()); + } + } + /** * TODO: Do not assume motion across X axis for adjacent page */ @@ -2290,7 +2334,14 @@ public abstract class RecentsView extends PagedView if (pageIndex == -1) { return 0; } - return getScrollForPage(pageIndex) - mOrientationHandler.getPrimaryScroll(this); + // Unbound the scroll (due to overscroll) if the adjacent tasks are offset away from it. + // This allows the page to move freely, given there's no visual indication why it shouldn't. + int boundedScroll = mOrientationHandler.getPrimaryScroll(this); + int unboundedScroll = getUnboundedScroll(); + float unboundedProgress = mAdjacentPageOffset; + int scroll = Math.round(unboundedScroll * unboundedProgress + + boundedScroll * (1 - unboundedProgress)); + return getScrollForPage(pageIndex) - scroll; } public Consumer getEventDispatcher(float navbarRotation) { @@ -2392,6 +2443,10 @@ public abstract class RecentsView extends PagedView */ public void setModalStateEnabled(boolean isModalState) { } + public TaskOverlayFactory getTaskOverlayFactory() { + return mTaskOverlayFactory; + } + public BaseActivityInterface getSizeStrategy() { return mSizeStrategy; } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java index a8d6442135..607672a751 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java @@ -52,7 +52,6 @@ import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.SystemUiController; import com.android.launcher3.util.Themes; -import com.android.quickstep.TaskOverlayFactory; import com.android.quickstep.TaskOverlayFactory.TaskOverlay; import com.android.quickstep.views.TaskView.FullscreenDrawParams; import com.android.systemui.plugins.OverviewScreenshotActions; @@ -85,7 +84,7 @@ public class TaskThumbnailView extends View implements PluginListener + + com.android.launcher3/com.android.quickstep.interaction.GestureSandboxActivity diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java index f42b124fd6..cc7b712842 100644 --- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java +++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java @@ -43,9 +43,11 @@ import android.util.ArrayMap; import android.util.Log; import androidx.annotation.MainThread; +import androidx.annotation.WorkerThread; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.popup.RemoteActionShortcut; import com.android.launcher3.popup.SystemShortcut; @@ -57,6 +59,7 @@ import com.android.launcher3.util.SimpleBroadcastReceiver; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.function.Consumer; /** * Data model for digital wellbeing status of apps. @@ -72,6 +75,9 @@ public final class WellbeingModel { private static final int MSG_FULL_REFRESH = 3; // Welbeing contract + private static final String PATH_ACTIONS = "actions"; + private static final String PATH_MINIMAL_DEVICE = "minimal_device"; + private static final String METHOD_GET_MINIMAL_DEVICE_CONFIG = "get_minimal_device_config"; private static final String METHOD_GET_ACTIONS = "get_actions"; private static final String EXTRA_ACTIONS = "actions"; private static final String EXTRA_ACTION = "action"; @@ -104,15 +110,22 @@ public final class WellbeingModel { mContentObserver = new ContentObserver(MAIN_EXECUTOR.getHandler()) { @Override public void onChange(boolean selfChange, Uri uri) { - // Wellbeing reports that app actions have changed. if (DEBUG || mIsInTest) { - Log.d(TAG, "ContentObserver.onChange() called with: selfChange = [" + selfChange - + "], uri = [" + uri + "]"); + Log.d(TAG, "ContentObserver.onChange() called with: selfChange = [" + + selfChange + "], uri = [" + uri + "]"); } Preconditions.assertUIThread(); - updateWellbeingData(); + + if (uri.getPath().contains(PATH_ACTIONS)) { + // Wellbeing reports that app actions have changed. + updateWellbeingData(); + } else if (uri.getPath().contains(PATH_MINIMAL_DEVICE)) { + // Wellbeing reports that minimal device state or config is changed. + updateLauncherModel(); + } } }; + FeatureFlags.ENABLE_MINIMAL_DEVICE.addChangeListener(mContext, this::updateLauncherModel); if (!TextUtils.isEmpty(mWellbeingProviderPkg)) { context.registerReceiver( @@ -146,14 +159,18 @@ public final class WellbeingModel { private void restartObserver() { final ContentResolver resolver = mContext.getContentResolver(); resolver.unregisterContentObserver(mContentObserver); - Uri actionsUri = apiBuilder().path("actions").build(); + Uri actionsUri = apiBuilder().path(PATH_ACTIONS).build(); + Uri minimalDeviceUri = apiBuilder().path(PATH_MINIMAL_DEVICE).build(); try { resolver.registerContentObserver( actionsUri, true /* notifyForDescendants */, mContentObserver); + resolver.registerContentObserver( + minimalDeviceUri, true /* notifyForDescendants */, mContentObserver); } catch (Exception e) { Log.e(TAG, "Failed to register content observer for " + actionsUri + ": " + e); if (mIsInTest) throw new RuntimeException(e); } + updateWellbeingData(); } @@ -191,12 +208,42 @@ public final class WellbeingModel { mWorkerHandler.sendEmptyMessage(MSG_FULL_REFRESH); } + private void updateLauncherModel() { + if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) return; + + // TODO: init Launcher in minimal device / normal mode + } + private Uri.Builder apiBuilder() { return new Uri.Builder() .scheme(SCHEME_CONTENT) .authority(mWellbeingProviderPkg + ".api"); } + /** + * Fetch most up-to-date minimal device config. + */ + @WorkerThread + private void runWithMinimalDeviceConfigs(Consumer consumer) { + if (DEBUG || mIsInTest) { + Log.d(TAG, "runWithMinimalDeviceConfigs() called"); + } + Preconditions.assertNonUiThread(); + + final Uri contentUri = apiBuilder().build(); + final Bundle remoteBundle; + try (ContentProviderClient client = mContext.getContentResolver() + .acquireUnstableContentProviderClient(contentUri)) { + remoteBundle = client.call( + METHOD_GET_MINIMAL_DEVICE_CONFIG, null /* args */, null /* extras */); + consumer.accept(remoteBundle); + } catch (Exception e) { + Log.e(TAG, "Failed to retrieve data from " + contentUri + ": " + e); + if (mIsInTest) throw new RuntimeException(e); + } + if (DEBUG || mIsInTest) Log.i(TAG, "runWithMinimalDeviceConfigs(): finished"); + } + private boolean updateActions(String... packageNames) { if (packageNames.length == 0) { return true; diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java index 6e120e8bd8..1b8e244ac1 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java @@ -24,11 +24,13 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MO import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCRIM_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y; import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK; import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE; import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW; import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET; import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; +import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION; import android.util.FloatProperty; @@ -63,6 +65,7 @@ public abstract class BaseRecentsViewStateController float[] scaleAndOffset = state.getOverviewScaleAndOffset(mLauncher); RECENTS_SCALE_PROPERTY.set(mRecentsView, scaleAndOffset[0]); ADJACENT_PAGE_OFFSET.set(mRecentsView, scaleAndOffset[1]); + TASK_SECONDARY_TRANSLATION.set(mRecentsView, 0f); getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0); OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim(); @@ -97,6 +100,8 @@ public abstract class BaseRecentsViewStateController config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR)); setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1], config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR)); + setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f, + config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR)); setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0, config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT)); diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java index e7cd39381d..29a6be0779 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java @@ -18,6 +18,7 @@ package com.android.launcher3.uioverrides.states; import static com.android.launcher3.anim.Interpolators.DEACCEL_2; import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; +import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; import android.content.Context; @@ -92,7 +93,8 @@ public class AllAppsState extends LauncherState { @Override public float[] getOverviewScaleAndOffset(Launcher launcher) { - return new float[] {0.9f, 0}; + float offset = ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(launcher) ? 1 : 0; + return new float[] {0.9f, offset}; } @Override diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java index 20ee61db9b..a684b9d7fb 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java @@ -142,6 +142,10 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "PortraitStatesTouchController.getTargetState 1"); } + if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(mLauncher)) { + // Don't allow swiping down to overview. + return NORMAL; + } return TouchInteractionService.isConnected() ? mLauncher.getStateManager().getLastState() : NORMAL; } else if (fromState == OVERVIEW) { diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index df8f31d2c0..bdf525dcd0 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -28,6 +28,7 @@ import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_REC import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET; import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; +import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION; import android.animation.Animator; import android.annotation.TargetApi; @@ -52,6 +53,7 @@ import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.WindowBounds; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.util.ActivityInitListener; +import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.util.ShelfPeekAnim; import com.android.quickstep.util.SplitScreenBounds; import com.android.quickstep.views.RecentsView; @@ -106,7 +108,7 @@ public abstract class BaseActivityInterface callback); + boolean activityVisible, Consumer callback); public abstract ActivityInitListener createActivityInitListener( Predicate onInitListener); @@ -319,11 +321,11 @@ public abstract class BaseActivityInterface mCallback; + private final Consumer mCallback; private boolean mIsAttachedToWindow; - DefaultAnimationFactory(Consumer callback) { + DefaultAnimationFactory(Consumer callback) { mCallback = callback; mActivity = getCreatedActivity(); @@ -351,7 +353,14 @@ public abstract class BaseActivityInterface mActivity.getStateManager().goToState( controller.getInterpolatedProgress() > 0.5 ? mOverviewState : mBackgroundState, false)); - mCallback.accept(controller); + + RecentsView recentsView = mActivity.getOverviewPanel(); + AnimatorControllerWithResistance controllerWithResistance = + AnimatorControllerWithResistance.createForRecents(controller, mActivity, + recentsView.getPagedViewOrientedState(), mActivity.getDeviceProfile(), + recentsView, RECENTS_SCALE_PROPERTY, recentsView, + TASK_SECONDARY_TRANSLATION); + mCallback.accept(controllerWithResistance); // Creating the activity controller animation sometimes reapplies the launcher state // (because we set the animation as the current state animation), so we reapply the diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java index 4110b33d24..a15731400e 100644 --- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java @@ -184,8 +184,7 @@ abstract class SwipeUpGestureTutorialController extends TutorialController { @Override public void updateFinalShift() { - float progress = mCurrentShift.value / mDragLengthFactor; - mWindowTransitionController.setPlayFraction(progress); + mWindowTransitionController.setProgress(mCurrentShift.value, mDragLengthFactor); mTaskViewSimulator.apply(mTransformParams); } diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java new file mode 100644 index 0000000000..a19a67c8fc --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2020 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * 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. + */ +package com.android.quickstep.util; + +import static com.android.launcher3.anim.Interpolators.DEACCEL; +import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS; +import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; +import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION; + +import android.animation.TimeInterpolator; +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.util.FloatProperty; + +import androidx.annotation.Nullable; + +import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.Utilities; +import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.PendingAnimation; +import com.android.quickstep.LauncherActivityInterface; +import com.android.quickstep.SysUINavigationMode; +import com.android.quickstep.views.RecentsView; + +/** + * Controls an animation that can go beyond progress = 1, at which point resistance should be + * applied. Internally, this is a wrapper around 2 {@link AnimatorPlaybackController}s, one that + * runs from progress 0 to 1 like normal, then one that seamlessly continues that animation but + * starts applying resistance as well. + */ +public class AnimatorControllerWithResistance { + + /** + * How much farther we can drag past overview in 2-button mode, as a factor of the distance + * it takes to drag from an app to overview. + */ + public static final float TWO_BUTTON_EXTRA_DRAG_FACTOR = 0.25f; + + private enum RecentsParams { + FROM_APP(0.75f, 0.5f, 1f), + FROM_OVERVIEW(1f, 0.75f, 0.5f); + + RecentsParams(float scaleStartResist, float scaleMaxResist, float translationFactor) { + this.scaleStartResist = scaleStartResist; + this.scaleMaxResist = scaleMaxResist; + this.translationFactor = translationFactor; + } + + /** + * Start slowing down the rate of scaling down when recents view is smaller than this scale. + */ + public final float scaleStartResist; + + /** + * Recents view will reach this scale at the very end of the drag. + */ + public final float scaleMaxResist; + + /** + * How much translation to apply to RecentsView when the drag reaches the top of the screen, + * where 0 will keep it centered and 1 will have it barely touch the top of the screen. + */ + public final float translationFactor; + } + + private static final TimeInterpolator RECENTS_SCALE_RESIST_INTERPOLATOR = DEACCEL; + private static final TimeInterpolator RECENTS_TRANSLATE_RESIST_INTERPOLATOR = LINEAR; + + private final AnimatorPlaybackController mNormalController; + private final AnimatorPlaybackController mResistanceController; + + // Initialize to -1 so the first 0 gets applied. + private float mLastNormalProgress = -1; + private float mLastResistProgress; + + public AnimatorControllerWithResistance(AnimatorPlaybackController normalController, + AnimatorPlaybackController resistanceController) { + mNormalController = normalController; + mResistanceController = resistanceController; + } + + public AnimatorPlaybackController getNormalController() { + return mNormalController; + } + + /** + * Applies the current progress of the animation. + * @param progress From 0 to maxProgress, where 1 is the target we are animating towards. + * @param maxProgress > 1, this is where the resistance will be applied. + */ + public void setProgress(float progress, float maxProgress) { + float normalProgress = Utilities.boundToRange(progress, 0, 1); + if (normalProgress != mLastNormalProgress) { + mLastNormalProgress = normalProgress; + mNormalController.setPlayFraction(normalProgress); + } + if (maxProgress <= 1) { + return; + } + float resistProgress = progress <= 1 ? 0 : Utilities.getProgress(progress, 1, maxProgress); + if (resistProgress != mLastResistProgress) { + mLastResistProgress = resistProgress; + mResistanceController.setPlayFraction(resistProgress); + } + } + + /** + * Applies resistance to recents when swiping up past its target position. + * @param normalController The controller to run from 0 to 1 before this resistance applies. + * @param context Used to compute start and end values. + * @param recentsOrientedState Used to compute start and end values. + * @param dp Used to compute start and end values. + * @param scaleTarget The target for the scaleProperty. + * @param scaleProperty Animate the value to change the scale of the window/recents view. + * @param translationTarget The target for the translationProperty. + * @param translationProperty Animate the value to change the translation of the recents view. + */ + public static AnimatorControllerWithResistance createForRecents( + AnimatorPlaybackController normalController, Context context, + RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget, + FloatProperty scaleProperty, TRANSLATION translationTarget, + FloatProperty translationProperty) { + + PendingAnimation resistAnim = createRecentsResistanceAnim(null, context, + recentsOrientedState, dp, scaleTarget, scaleProperty, translationTarget, + translationProperty, RecentsParams.FROM_APP); + + AnimatorPlaybackController resistanceController = resistAnim.createPlaybackController(); + return new AnimatorControllerWithResistance(normalController, resistanceController); + } + + /** + * Creates the resistance animation for {@link #createForRecents}, or can be used separately + * when starting from recents, i.e. {@link #createRecentsResistanceFromOverviewAnim}. + */ + public static PendingAnimation createRecentsResistanceAnim( + @Nullable PendingAnimation resistAnim, Context context, + RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget, + FloatProperty scaleProperty, TRANSLATION translationTarget, + FloatProperty translationProperty, RecentsParams params) { + Rect startRect = new Rect(); + LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, startRect, + recentsOrientedState.getOrientationHandler()); + long distanceToCover = startRect.bottom; + boolean isTwoButtonMode = SysUINavigationMode.getMode(context) == TWO_BUTTONS; + if (isTwoButtonMode) { + // We can only drag a small distance past overview, not to the top of the screen. + distanceToCover = (long) + ((dp.heightPx - startRect.bottom) * TWO_BUTTON_EXTRA_DRAG_FACTOR); + } + if (resistAnim == null) { + resistAnim = new PendingAnimation(distanceToCover * 2); + } + + PointF pivot = new PointF(); + float fullscreenScale = recentsOrientedState.getFullScreenScaleAndPivot( + startRect, dp, pivot); + float startScale = 1; + float prevScaleRate = (fullscreenScale - startScale) / (dp.heightPx - startRect.bottom); + // This is what the scale would be at the end of the drag if we didn't apply resistance. + float endScale = startScale - prevScaleRate * distanceToCover; + final TimeInterpolator scaleInterpolator; + if (isTwoButtonMode) { + // We are bounded by the distance of the drag, so we don't need to apply resistance. + scaleInterpolator = LINEAR; + } else { + // Create an interpolator that resists the scale so the scale doesn't get smaller than + // RECENTS_SCALE_MAX_RESIST. + float startResist = Utilities.getProgress(params.scaleStartResist , startScale, + endScale); + float maxResist = Utilities.getProgress(params.scaleMaxResist, startScale, endScale); + scaleInterpolator = t -> { + if (t < startResist) { + return t; + } + float resistProgress = Utilities.getProgress(t, startResist, 1); + resistProgress = RECENTS_SCALE_RESIST_INTERPOLATOR.getInterpolation(resistProgress); + return startResist + resistProgress * (maxResist - startResist); + }; + } + resistAnim.addFloat(scaleTarget, scaleProperty, startScale, endScale, + scaleInterpolator); + + if (!isTwoButtonMode) { + // Compute where the task view would be based on the end scale, if we didn't translate. + RectF endRectF = new RectF(startRect); + Matrix temp = new Matrix(); + temp.setScale(params.scaleMaxResist, params.scaleMaxResist, pivot.x, pivot.y); + temp.mapRect(endRectF); + // Translate such that the task view touches the top of the screen when drag does. + float endTranslation = endRectF.top * recentsOrientedState.getOrientationHandler() + .getSecondaryTranslationDirectionFactor() * params.translationFactor; + resistAnim.addFloat(translationTarget, translationProperty, 0, endTranslation, + RECENTS_TRANSLATE_RESIST_INTERPOLATOR); + } + + return resistAnim; + } + + /** + * Helper method to update or create a PendingAnimation suitable for animating + * a RecentsView interaction that started from the overview state. + */ + public static PendingAnimation createRecentsResistanceFromOverviewAnim( + BaseDraggingActivity activity, @Nullable PendingAnimation resistanceAnim) { + RecentsView recentsView = activity.getOverviewPanel(); + return createRecentsResistanceAnim(resistanceAnim, activity, + recentsView.getPagedViewOrientedState(), activity.getDeviceProfile(), + recentsView, RECENTS_SCALE_PROPERTY, recentsView, TASK_SECONDARY_TRANSLATION, + RecentsParams.FROM_OVERVIEW); + } +} diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java index bf093fdabf..ecd4e2bd67 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java @@ -36,6 +36,7 @@ import com.android.launcher3.tapl.AllAppsFromOverview; import com.android.launcher3.tapl.Background; import com.android.launcher3.tapl.LauncherInstrumentation.NavigationModel; import com.android.launcher3.tapl.Overview; +import com.android.launcher3.tapl.OverviewActions; import com.android.launcher3.tapl.OverviewTask; import com.android.launcher3.tapl.TestHelpers; import com.android.launcher3.ui.TaplTestsLauncher3; @@ -68,11 +69,14 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { }); } - private void startTestApps() throws Exception { + public static void startTestApps() throws Exception { startAppFast(getAppPackageName()); startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); startTestActivity(2); + } + private void startTestAppsWithCheck() throws Exception { + startTestApps(); executeOnLauncher(launcher -> assertTrue( "Launcher activity is the top activity; expecting another activity to be the top " + "one", @@ -105,7 +109,7 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { @Test @PortraitLandscape public void testOverview() throws Exception { - startTestApps(); + startTestAppsWithCheck(); // mLauncher.pressHome() also tests an important case of pressing home while in background. Overview overview = mLauncher.pressHome().switchToOverview(); assertTrue("Launcher internal state didn't switch to Overview", @@ -189,6 +193,22 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { 0, getTaskCount(launcher))); } + /** + * Smoke test for action buttons: Presses all the buttons and makes sure no crashes occur. + */ + @Test + @NavigationModeSwitch + @PortraitLandscape + public void testOverviewActions() throws Exception { + if (mLauncher.getNavigationModel() != NavigationModel.TWO_BUTTON) { + startTestAppsWithCheck(); + OverviewActions actionsView = + mLauncher.pressHome().switchToOverview().getOverviewActions(); + actionsView.clickAndDismissScreenshot(); + actionsView.clickAndDismissShare(); + } + } + private int getCurrentOverviewPage(Launcher launcher) { return launcher.getOverviewPanel().getCurrentPage(); } diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java index f2a5c656ff..833ce1501d 100644 --- a/src/com/android/launcher3/BaseDraggingActivity.java +++ b/src/com/android/launcher3/BaseDraggingActivity.java @@ -42,6 +42,7 @@ import android.widget.Toast; import androidx.annotation.Nullable; +import com.android.launcher3.Launcher.OnResumeCallback; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.InstanceIdSequence; @@ -108,10 +109,20 @@ public abstract class BaseDraggingActivity extends BaseActivity private void updateTheme() { if (mThemeRes != Themes.getActivityThemeRes(this)) { - recreate(); + // Workaround (b/162812884): The system currently doesn't allow recreating an activity + // when it is not resumed, in such a case defer recreation until it is possible + if (hasBeenResumed()) { + recreate(); + } else { + addOnResumeCallback(this::recreate); + } } } + protected void addOnResumeCallback(OnResumeCallback callback) { + // To be overridden + } + @Override public void onActionModeStarted(ActionMode mode) { super.onActionModeStarted(mode); diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 4675362f64..03e4b06f8f 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1943,6 +1943,7 @@ public class Launcher extends StatefulActivity implements Launche return result; } + @Override public void addOnResumeCallback(OnResumeCallback callback) { mOnResumeCallbacks.add(callback); } diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java index 860ccebef4..6ad43ea1e4 100644 --- a/src/com/android/launcher3/anim/Interpolators.java +++ b/src/com/android/launcher3/anim/Interpolators.java @@ -198,6 +198,7 @@ public class Interpolators { public OvershootParams(float startProgress, float overshootPastProgress, float endProgress, float velocityPxPerMs, int totalDistancePx, Context context) { velocityPxPerMs = Math.abs(velocityPxPerMs); + overshootPastProgress = Math.max(overshootPastProgress, startProgress); start = startProgress; int startPx = (int) (start * totalDistancePx); // Overshoot by about half a frame. diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 45116b6b27..88a9abaf8d 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -92,7 +92,7 @@ public final class FeatureFlags { // Keep as DeviceFlag to allow remote disable in emergency. public static final BooleanFlag ENABLE_SUGGESTED_ACTIONS_OVERVIEW = new DeviceFlag( - "ENABLE_SUGGESTED_ACTIONS_OVERVIEW", false, "Show chip hints on the overview screen"); + "ENABLE_SUGGESTED_ACTIONS_OVERVIEW", true, "Show chip hints on the overview screen"); public static final BooleanFlag FOLDER_NAME_SUGGEST = new DeviceFlag( "FOLDER_NAME_SUGGEST", true, diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java index 2abde02a73..e754618a10 100644 --- a/src/com/android/launcher3/logging/StatsLogManager.java +++ b/src/com/android/launcher3/logging/StatsLogManager.java @@ -316,7 +316,10 @@ public class StatsLogManager implements ResourceBasedOverride { LAUNCHER_NAVIGATION_MODE_2_BUTTON(624), @UiEvent(doc = "System navigation mode is 0 button mode/gesture navigation mode .") - LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON(625); + LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON(625), + + @UiEvent(doc = "User tapped on image content in Overview Select mode.") + LAUNCHER_SELECT_MODE_IMAGE(627); // ADD MORE diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java index 60b87d984b..27923089fb 100644 --- a/src/com/android/launcher3/statemanager/StateManager.java +++ b/src/com/android/launcher3/statemanager/StateManager.java @@ -311,7 +311,13 @@ public class StateManager> { handler.setStateWithAnimation(state, mConfig, builder); } } - builder.addListener(new AnimationSuccessListener() { + builder.addListener(createStateAnimationListener(state)); + mConfig.setAnimation(builder.buildAnim(), state); + return builder; + } + + private AnimatorListener createStateAnimationListener(STATE_TYPE state) { + return new AnimationSuccessListener() { @Override public void onAnimationStart(Animator animation) { @@ -326,9 +332,7 @@ public class StateManager> { } onStateTransitionEnd(state); } - }); - mConfig.setAnimation(builder.buildAnim(), state); - return builder; + }; } private void onStateTransitionStart(STATE_TYPE state) { @@ -395,6 +399,19 @@ public class StateManager> { mConfig.playbackController = controller; } + /** + * @see #setCurrentAnimation(AnimatorSet, Animator...). Using this method tells the StateManager + * that this is a custom animation to the given state, and thus the StateManager will add an + * animation listener to call {@link #onStateTransitionStart} and {@link #onStateTransitionEnd}. + * @param anim The custom animation to the given state. + * @param toState The state we are animating towards. + */ + public void setCurrentAnimation(AnimatorSet anim, STATE_TYPE toState) { + cancelAnimation(); + setCurrentAnimation(anim); + anim.addListener(createStateAnimationListener(toState)); + } + /** * Sets the animation as the current state animation, i.e., canceled when * starting another animation and may block some launcher interactions while running. diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java index f90ad3cd0b..8b7217709f 100644 --- a/src/com/android/launcher3/states/StateAnimationConfig.java +++ b/src/com/android/launcher3/states/StateAnimationConfig.java @@ -71,6 +71,7 @@ public class StateAnimationConfig { ANIM_ALL_APPS_HEADER_FADE, ANIM_OVERVIEW_MODAL, ANIM_DEPTH, + ANIM_OVERVIEW_ACTIONS_FADE, }) @Retention(RetentionPolicy.SOURCE) public @interface AnimType {} @@ -89,10 +90,11 @@ public class StateAnimationConfig { public static final int ANIM_ALL_APPS_HEADER_FADE = 12; // e.g. predictions public static final int ANIM_OVERVIEW_MODAL = 13; public static final int ANIM_DEPTH = 14; + public static final int ANIM_OVERVIEW_ACTIONS_FADE = 15; - private static final int ANIM_TYPES_COUNT = 15; + private static final int ANIM_TYPES_COUNT = 16; - private final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT]; + protected final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT]; public StateAnimationConfig() { } diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java index 626d9dd966..58bff090e8 100644 --- a/src/com/android/launcher3/testing/TestProtocol.java +++ b/src/com/android/launcher3/testing/TestProtocol.java @@ -98,6 +98,7 @@ public final class TestProtocol { public static final String REQUEST_DISABLE_DEBUG_TRACING = "disable-debug-tracing"; public static final String REQUEST_OVERVIEW_ACTIONS_ENABLED = "overview-actions-enabled"; + public static final String REQUEST_OVERVIEW_SHARE_ENABLED = "overview-share-enabled"; public static boolean sDisableSensorRotation; public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation"; diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java index 1aaa608657..4fd163360b 100644 --- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java +++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java @@ -107,6 +107,11 @@ public class LandscapePagedViewHandler implements PagedOrientationHandler { action.call(target, 0, param); } + @Override + public void setSecondary(T target, Float2DAction action, float param) { + action.call(target, param, 0); + } + @Override public float getPrimaryDirection(MotionEvent event, int pointerIndex) { return event.getY(pointerIndex); @@ -215,7 +220,11 @@ public class LandscapePagedViewHandler implements PagedOrientationHandler { } @Override - public int getTaskDismissDirectionFactor() { + public int getPrimaryTranslationDirectionFactor() { + return -1; + } + + public int getSecondaryTranslationDirectionFactor() { return 1; } diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java index f88cdb3b0d..c0d488bb45 100644 --- a/src/com/android/launcher3/touch/PagedOrientationHandler.java +++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java @@ -57,6 +57,7 @@ public interface PagedOrientationHandler { void set(T target, Int2DAction action, int param); void set(T target, Float2DAction action, float param); + void setSecondary(T target, Float2DAction action, float param); float getPrimaryDirection(MotionEvent event, int pointerIndex); float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId); int getMeasuredSize(View view); @@ -74,7 +75,8 @@ public interface PagedOrientationHandler { int getScrollOffsetStart(View view, Rect insets); int getScrollOffsetEnd(View view, Rect insets); SingleAxisSwipeDetector.Direction getOppositeSwipeDirection(); - int getTaskDismissDirectionFactor(); + int getPrimaryTranslationDirectionFactor(); + int getSecondaryTranslationDirectionFactor(); int getTaskDragDisplacementFactor(boolean isRtl); ChildBounds getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild); void setMaxScroll(AccessibilityEvent event, int maxScroll); diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java index f18b109515..9e53e5f8ec 100644 --- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java +++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java @@ -104,6 +104,11 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { action.call(target, param, 0); } + @Override + public void setSecondary(T target, Float2DAction action, float param) { + action.call(target, 0, param); + } + @Override public float getPrimaryDirection(MotionEvent event, int pointerIndex) { return event.getX(pointerIndex); @@ -212,7 +217,11 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { } @Override - public int getTaskDismissDirectionFactor() { + public int getPrimaryTranslationDirectionFactor() { + return 1; + } + + public int getSecondaryTranslationDirectionFactor() { return -1; } diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java index e91f16d71a..7153452f23 100644 --- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java +++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java @@ -28,7 +28,7 @@ import com.android.launcher3.Utilities; public class SeascapePagedViewHandler extends LandscapePagedViewHandler { @Override - public int getTaskDismissDirectionFactor() { + public int getSecondaryTranslationDirectionFactor() { return -1; } diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java index a8642b05c0..5be95292b4 100644 --- a/src/com/android/launcher3/util/MultiValueAlpha.java +++ b/src/com/android/launcher3/util/MultiValueAlpha.java @@ -19,6 +19,8 @@ package com.android.launcher3.util; import android.util.FloatProperty; import android.view.View; +import com.android.launcher3.anim.AlphaUpdateListener; + import java.util.Arrays; /** @@ -44,6 +46,8 @@ public class MultiValueAlpha { private final AlphaProperty[] mMyProperties; private int mValidMask; + // Whether we should change from INVISIBLE to VISIBLE and vice versa at low alpha values. + private boolean mUpdateVisibility; public MultiValueAlpha(View view, int size) { mView = view; @@ -66,6 +70,11 @@ public class MultiValueAlpha { return mMyProperties[index]; } + /** Sets whether we should update between INVISIBLE and VISIBLE based on alpha. */ + public void setUpdateVisibility(boolean updateVisibility) { + mUpdateVisibility = updateVisibility; + } + public class AlphaProperty { private final int mMyMask; @@ -99,6 +108,9 @@ public class MultiValueAlpha { mValue = value; mView.setAlpha(mOthers * mValue); + if (mUpdateVisibility) { + AlphaUpdateListener.updateVisibility(mView); + } } public float getValue() { diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java index 223ae298b9..588b6b8f07 100644 --- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java +++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java @@ -27,7 +27,7 @@ import java.util.Collections; import java.util.List; /** - * Common overview pane for both Launcher and fallback recents + * Common overview panel for both Launcher and fallback recents */ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { private static final int FLINGS_FOR_DISMISS_LIMIT = 40; @@ -135,4 +135,19 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { public boolean hasTasks() { return getTasks().size() > 0; } + + /** + * Gets Overview Actions. + * + * @return The Overview Actions + */ + @NonNull + public OverviewActions getOverviewActions() { + try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "want to get overview actions")) { + verifyActiveContainer(); + UiObject2 overviewActions = mLauncher.waitForLauncherObject("action_buttons"); + return new OverviewActions(overviewActions, mLauncher); + } + } } \ No newline at end of file diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 68f3963a4e..be3cb4e6e0 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -154,6 +154,7 @@ public final class LauncherInstrumentation { private static final String CONTEXT_MENU_RES_ID = "deep_shortcuts_container"; public static final int WAIT_TIME_MS = 10000; private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; + private static final String ANDROID_PACKAGE = "android"; private static WeakReference sActiveContainer = new WeakReference<>(null); @@ -926,6 +927,14 @@ public final class LauncherInstrumentation { return waitForObjectBySelector(getOverviewObjectSelector(resName)); } + @NonNull + UiObject2 waitForAndroidObject(String resId) { + final UiObject2 object = mDevice.wait( + Until.findObject(By.res(ANDROID_PACKAGE, resId)), WAIT_TIME_MS); + assertNotNull("Can't find a android object with id: " + resId, object); + return object; + } + private UiObject2 waitForObjectBySelector(BySelector selector) { final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS); assertNotNull("Can't find a view in Launcher, selector: " + selector, object); @@ -1302,6 +1311,11 @@ public final class LauncherInstrumentation { TestProtocol.TEST_INFO_RESPONSE_FIELD); } + boolean overviewShareEnabled() { + return getTestInfo(TestProtocol.REQUEST_OVERVIEW_SHARE_ENABLED).getBoolean( + TestProtocol.TEST_INFO_RESPONSE_FIELD); + } + private void disableSensorRotation() { getTestInfo(TestProtocol.REQUEST_MOCK_SENSOR_ROTATION); } diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewActions.java b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java new file mode 100644 index 0000000000..a30a404764 --- /dev/null +++ b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2020 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * 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. + */ + +package com.android.launcher3.tapl; + +import androidx.annotation.NonNull; +import androidx.test.uiautomator.UiObject2; + +import com.android.launcher3.testing.TestProtocol; + +/** + * View containing overview actions + */ +public class OverviewActions { + private final UiObject2 mOverviewActions; + private final LauncherInstrumentation mLauncher; + + OverviewActions(UiObject2 overviewActions, LauncherInstrumentation launcherInstrumentation) { + this.mOverviewActions = overviewActions; + this.mLauncher = launcherInstrumentation; + } + + /** + * Clicks screenshot button and closes screenshot ui. + */ + @NonNull + public Overview clickAndDismissScreenshot() { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "want to click screenshot button and exit screenshot ui")) { + UiObject2 screenshot = mLauncher.waitForObjectInContainer(mOverviewActions, + "action_screenshot"); + mLauncher.clickLauncherObject(screenshot); + try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( + "clicked screenshot button")) { + UiObject2 closeScreenshot = mLauncher.waitForSystemUiObject( + "global_screenshot_dismiss_image"); + if (mLauncher.getNavigationModel() + != LauncherInstrumentation.NavigationModel.THREE_BUTTON) { + mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS, + LauncherInstrumentation.EVENT_TOUCH_DOWN_TIS); + mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS, + LauncherInstrumentation.EVENT_TOUCH_UP_TIS); + } + closeScreenshot.click(); + try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer( + "dismissed screenshot")) { + return new Overview(mLauncher); + } + } + } + } + + /** + * Click share button, then drags sharesheet down to remove it. + * + * Share is currently hidden behind flag, test is kept in case share becomes a default feature. + * If share is completely removed then remove this test as well. + */ + @NonNull + public Overview clickAndDismissShare() { + if (mLauncher.overviewShareEnabled()) { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "want to click share button and dismiss sharesheet")) { + UiObject2 share = mLauncher.waitForObjectInContainer(mOverviewActions, + "action_share"); + mLauncher.clickLauncherObject(share); + try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( + "clicked share button")) { + mLauncher.waitForAndroidObject("contentPanel"); + mLauncher.getDevice().pressBack(); + try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer( + "dismissed sharesheet")) { + return new Overview(mLauncher); + } + } + } + } + return new Overview(mLauncher); + } + + /** + * Click select button + * + * @return The select mode buttons that are now shown instead of action buttons. + */ + @NonNull + public SelectModeButtons clickSelect() { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c = + mLauncher.addContextLayer("want to click select button")) { + UiObject2 select = mLauncher.waitForObjectInContainer(mOverviewActions, + "action_select"); + mLauncher.clickLauncherObject(select); + try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( + "clicked select button")) { + return getSelectModeButtons(); + } + + } + } + + /** + * Gets the Select Mode Buttons. + * + * @return The Select Mode Buttons. + */ + @NonNull + private SelectModeButtons getSelectModeButtons() { + try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "want to get select mode buttons")) { + UiObject2 selectModeButtons = mLauncher.waitForLauncherObject("select_mode_buttons"); + return new SelectModeButtons(selectModeButtons, mLauncher); + } + } +} diff --git a/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java b/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java new file mode 100644 index 0000000000..35074184d6 --- /dev/null +++ b/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * 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. + */ + +package com.android.launcher3.tapl; + +import androidx.annotation.NonNull; +import androidx.test.uiautomator.UiObject2; + +/** + * View containing select mode buttons + */ +public class SelectModeButtons { + private final UiObject2 mSelectModeButtons; + private final LauncherInstrumentation mLauncher; + + SelectModeButtons(UiObject2 selectModeButtons, + LauncherInstrumentation launcherInstrumentation) { + mSelectModeButtons = selectModeButtons; + mLauncher = launcherInstrumentation; + } + + /** + * Click close button. + */ + @NonNull + public Overview clickClose() { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c = + mLauncher.addContextLayer("want to click close button")) { + UiObject2 close = mLauncher.waitForObjectInContainer(mSelectModeButtons, "close"); + mLauncher.clickLauncherObject(close); + try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( + "clicked close button")) { + return new Overview(mLauncher); + } + } + } + + /** + * Click feedback button. + */ + @NonNull + public Background clickFeedback() { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c = + mLauncher.addContextLayer("want to click feedback button")) { + UiObject2 feedback = mLauncher.waitForObjectInContainer(mSelectModeButtons, "feedback"); + mLauncher.clickLauncherObject(feedback); + try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( + "clicked feedback button")) { + return new Background(mLauncher); + } + } + } +}