diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml index be1d47b9f2..77345a0468 100644 --- a/quickstep/res/values/strings.xml +++ b/quickstep/res/values/strings.xml @@ -87,7 +87,6 @@ Predicted app: %1$s - Try the back gesture @@ -111,7 +110,6 @@ To change the sensitivity of the back gesture, go to Settings - Tutorial: Go Home @@ -123,6 +121,17 @@ Make sure you swipe straight up + + Tutorial: Switch Apps + + Swipe up from the bottom of the screen and hold + + Make sure you swipe from the bottom edge of the screen + + Try holding the window for longer before releasing + + Make sure you swipe straight up and pause + All set diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java index 41e86e00b6..1f398fc5ea 100644 --- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java @@ -34,14 +34,6 @@ final class BackGestureTutorialController extends TutorialController { super(fragment, tutorialType); } - @Override - void transitToController() { - super.transitToController(); - if (mTutorialType != BACK_NAVIGATION_COMPLETE) { - showHandCoachingAnimation(); - } - } - @Override Integer getTitleStringId() { switch (mTutorialType) { diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java index 65f41a4a6c..0edabd45c5 100644 --- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java @@ -15,143 +15,23 @@ */ package com.android.quickstep.interaction; -import static com.android.launcher3.anim.Interpolators.ACCEL; -import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs; -import static com.android.quickstep.BaseSwipeUpHandlerV2.MAX_SWIPE_DURATION; import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.Insets; -import android.graphics.Outline; import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.RectF; import android.os.Build; -import android.view.SurfaceControl; import android.view.View; -import android.view.ViewOutlineProvider; -import android.view.WindowInsets.Type; -import android.view.WindowManager; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.InvariantDeviceProfile; 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.PendingAnimation; -import com.android.quickstep.AnimatedFloat; -import com.android.quickstep.GestureState; -import com.android.quickstep.OverviewComponentObserver; -import com.android.quickstep.RecentsAnimationDeviceState; -import com.android.quickstep.SwipeUpAnimationLogic; -import com.android.quickstep.SwipeUpAnimationLogic.RunningWindowAnim; import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult; import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult; -import com.android.quickstep.util.RectFSpringAnim; -import com.android.quickstep.util.TransformParams; -import com.android.systemui.shared.system.QuickStepContract; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; /** A {@link TutorialController} for the Home tutorial. */ @TargetApi(Build.VERSION_CODES.R) -final class HomeGestureTutorialController extends TutorialController { - - private float mFakeTaskViewRadius; - private Rect mFakeTaskViewRect = new Rect(); - - private final ViewSwipeUpAnimation mViewSwipeUpAnimation; - private RunningWindowAnim mRunningWindowAnim; +final class HomeGestureTutorialController extends SwipeUpGestureTutorialController { HomeGestureTutorialController(HomeGestureTutorialFragment fragment, TutorialType tutorialType) { super(fragment, tutorialType); - - RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(mContext); - OverviewComponentObserver observer = new OverviewComponentObserver(mContext, deviceState); - mViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, deviceState, - new GestureState(observer, -1)); - observer.onDestroy(); - deviceState.destroy(); - - DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext) - .getDeviceProfile(mContext) - .copy(mContext); - Insets insets = mContext.getSystemService(WindowManager.class) - .getCurrentWindowMetrics() - .getWindowInsets() - .getInsets(Type.systemBars()); - dp.updateInsets(new Rect(insets.left, insets.top, insets.right, insets.bottom)); - mViewSwipeUpAnimation.initDp(dp); - - mFakeTaskViewRadius = QuickStepContract.getWindowCornerRadius(mContext.getResources()); - - mFakeTaskView.setClipToOutline(true); - mFakeTaskView.setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - outline.setRoundRect(mFakeTaskViewRect, mFakeTaskViewRadius); - } - }); - } - - private void cancelRunningAnimation() { - if (mRunningWindowAnim != null) { - mRunningWindowAnim.cancel(); - } - mRunningWindowAnim = null; - } - - /** Fades the task view, optionally after animating to a fake Overview. */ - private void fadeOutFakeTaskView(boolean toOverviewFirst, @Nullable Runnable onEndRunnable) { - cancelRunningAnimation(); - PendingAnimation anim = new PendingAnimation(300); - AnimatorListenerAdapter resetTaskView = new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation, boolean isReverse) { - mFakeTaskView.setVisibility(View.INVISIBLE); - mFakeTaskView.setAlpha(1); - mRunningWindowAnim = null; - } - }; - if (toOverviewFirst) { - anim.setFloat(mViewSwipeUpAnimation.getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation, boolean isReverse) { - PendingAnimation fadeAnim = new PendingAnimation(300); - fadeAnim.setViewAlpha(mFakeTaskView, 0, ACCEL); - fadeAnim.addListener(resetTaskView); - AnimatorSet animset = fadeAnim.buildAnim(); - animset.setStartDelay(100); - animset.start(); - mRunningWindowAnim = RunningWindowAnim.wrap(animset); - } - }); - } else { - anim.setViewAlpha(mFakeTaskView, 0, ACCEL); - anim.addListener(resetTaskView); - } - if (onEndRunnable != null) { - anim.addListener(AnimationSuccessListener.forRunnable(onEndRunnable)); - } - AnimatorSet animset = anim.buildAnim(); - animset.start(); - mRunningWindowAnim = RunningWindowAnim.wrap(animset); - } - - @Override - void transitToController() { - super.transitToController(); - if (mTutorialType != HOME_NAVIGATION_COMPLETE) { - showHandCoachingAnimation(); - } } @Override @@ -190,6 +70,14 @@ final class HomeGestureTutorialController extends TutorialController { public void onBackGestureAttempted(BackGestureResult result) { switch (mTutorialType) { case HOME_NAVIGATION: + switch (result) { + case BACK_COMPLETED_FROM_LEFT: + case BACK_COMPLETED_FROM_RIGHT: + case BACK_CANCELLED_FROM_LEFT: + case BACK_CANCELLED_FROM_RIGHT: + showFeedback(R.string.home_gesture_feedback_swipe_too_far_from_edge); + break; + } break; case HOME_NAVIGATION_COMPLETE: if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT @@ -206,17 +94,8 @@ final class HomeGestureTutorialController extends TutorialController { case HOME_NAVIGATION: switch (result) { case HOME_GESTURE_COMPLETED: { - hideFeedback(); - cancelRunningAnimation(); - hideHandCoachingAnimation(); - RectFSpringAnim rectAnim = - mViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity); - // After home animation finishes, fade out and then move to the next screen. - rectAnim.addAnimatorListener(AnimationSuccessListener.forRunnable( - () -> fadeOutFakeTaskView(false, - () -> mTutorialFragment.changeController( - HOME_NAVIGATION_COMPLETE)))); - mRunningWindowAnim = RunningWindowAnim.wrap(rectAnim); + animateFakeTaskViewHome(finalVelocity, () -> + mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE)); break; } case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE: @@ -242,93 +121,4 @@ final class HomeGestureTutorialController extends TutorialController { } } - @Override - public void setNavBarGestureProgress(@Nullable Float displacement) { - if (displacement == null || mTutorialType == HOME_NAVIGATION_COMPLETE) { - mFakeTaskView.setVisibility(View.INVISIBLE); - } else { - mFakeTaskView.setVisibility(View.VISIBLE); - if (mRunningWindowAnim == null) { - mViewSwipeUpAnimation.updateDisplacement(displacement); - } - } - } - - private class ViewSwipeUpAnimation extends SwipeUpAnimationLogic { - - ViewSwipeUpAnimation(Context context, RecentsAnimationDeviceState deviceState, - GestureState gestureState) { - super(context, deviceState, gestureState, new FakeTransformParams()); - } - - void initDp(DeviceProfile dp) { - initTransitionEndpoints(dp); - mTaskViewSimulator.setPreviewBounds( - new Rect(0, 0, dp.widthPx, dp.heightPx), dp.getInsets()); - } - - @Override - public void updateFinalShift() { - float progress = mCurrentShift.value / mDragLengthFactor; - mWindowTransitionController.setPlayFraction(progress); - mTaskViewSimulator.apply(mTransformParams); - } - - AnimatedFloat getCurrentShift() { - return mCurrentShift; - } - - RectFSpringAnim handleSwipeUpToHome(PointF velocity) { - PointF velocityPxPerMs = new PointF(velocity.x, velocity.y); - float currentShift = mCurrentShift.value; - final float startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y - * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor); - float distanceToTravel = (1 - currentShift) * mTransitionDragLength; - - // we want the page's snap velocity to approximately match the velocity at - // which the user flings, so we scale the duration by a value near to the - // derivative of the scroll interpolator at zero, ie. 2. - long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y)); - long duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration); - HomeAnimationFactory homeAnimFactory = new HomeAnimationFactory(null) { - @Override - public AnimatorPlaybackController createActivityAnimationToHome() { - return AnimatorPlaybackController.wrap(new AnimatorSet(), duration); - } - - @NonNull - @Override - public RectF getWindowTargetRect() { - int fakeHomeIconSizePx = mDp.allAppsIconSizePx; - int fakeHomeIconLeft = (mDp.widthPx - fakeHomeIconSizePx) / 2; - int fakeHomeIconTop = mDp.heightPx - (mDp.allAppsCellHeightPx * 3); - return new RectF(fakeHomeIconLeft, fakeHomeIconTop, - fakeHomeIconLeft + fakeHomeIconSizePx, - fakeHomeIconTop + fakeHomeIconSizePx); - } - }; - RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift, homeAnimFactory); - windowAnim.start(mContext, velocityPxPerMs); - return windowAnim; - } - } - - private class FakeTransformParams extends TransformParams { - - @Override - public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) { - SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null); - proxy.onBuildTargetParams(builder, null, this); - return new SurfaceParams[] {builder.build()}; - } - - @Override - public void applySurfaceParams(SurfaceParams[] params) { - SurfaceParams p = params[0]; - mFakeTaskView.setAnimationMatrix(p.matrix); - mFakeTaskViewRect.set(p.windowCrop); - mFakeTaskViewRadius = p.cornerRadius; - mFakeTaskView.invalidateOutline(); - } - } } diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java new file mode 100644 index 0000000000..c636ebaea9 --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java @@ -0,0 +1,124 @@ +/* + * 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.interaction; + +import static com.android.quickstep.interaction.TutorialController.TutorialType.OVERVIEW_NAVIGATION_COMPLETE; + +import android.annotation.TargetApi; +import android.graphics.PointF; +import android.os.Build; +import android.view.View; + +import com.android.launcher3.R; +import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult; +import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult; + +/** A {@link TutorialController} for the Overview tutorial. */ +@TargetApi(Build.VERSION_CODES.R) +final class OverviewGestureTutorialController extends SwipeUpGestureTutorialController { + + OverviewGestureTutorialController(OverviewGestureTutorialFragment fragment, + TutorialType tutorialType) { + super(fragment, tutorialType); + } + + @Override + Integer getTitleStringId() { + switch (mTutorialType) { + case OVERVIEW_NAVIGATION: + return R.string.overview_gesture_tutorial_playground_title; + case OVERVIEW_NAVIGATION_COMPLETE: + return R.string.gesture_tutorial_confirm_title; + } + return null; + } + + @Override + Integer getSubtitleStringId() { + if (mTutorialType == TutorialType.OVERVIEW_NAVIGATION) { + return R.string.overview_gesture_tutorial_playground_subtitle; + } + return null; + } + + @Override + Integer getActionButtonStringId() { + if (mTutorialType == OVERVIEW_NAVIGATION_COMPLETE) { + return R.string.gesture_tutorial_action_button_label_done; + } + return null; + } + + @Override + void onActionButtonClicked(View button) { + mTutorialFragment.closeTutorial(); + } + + @Override + public void onBackGestureAttempted(BackGestureResult result) { + switch (mTutorialType) { + case OVERVIEW_NAVIGATION: + switch (result) { + case BACK_COMPLETED_FROM_LEFT: + case BACK_COMPLETED_FROM_RIGHT: + case BACK_CANCELLED_FROM_LEFT: + case BACK_CANCELLED_FROM_RIGHT: + showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge); + break; + } + break; + case OVERVIEW_NAVIGATION_COMPLETE: + if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT + || result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) { + mTutorialFragment.closeTutorial(); + } + break; + } + } + + @Override + public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) { + switch (mTutorialType) { + case OVERVIEW_NAVIGATION: + switch (result) { + case HOME_GESTURE_COMPLETED: { + animateFakeTaskViewHome(finalVelocity, () -> + showFeedback(R.string.overview_gesture_feedback_home_detected)); + break; + } + case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE: + case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE: + showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge); + break; + case OVERVIEW_GESTURE_COMPLETED: + fadeOutFakeTaskView(true, () -> + mTutorialFragment.changeController(OVERVIEW_NAVIGATION_COMPLETE)); + break; + case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION: + case HOME_OR_OVERVIEW_CANCELLED: + fadeOutFakeTaskView(false, null); + showFeedback(R.string.overview_gesture_feedback_wrong_swipe_direction); + break; + } + break; + case OVERVIEW_NAVIGATION_COMPLETE: + if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) { + mTutorialFragment.closeTutorial(); + } + break; + } + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java new file mode 100644 index 0000000000..3357b70497 --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java @@ -0,0 +1,37 @@ +/* + * 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.interaction; + +import com.android.launcher3.R; +import com.android.quickstep.interaction.TutorialController.TutorialType; + +/** Shows the Overview gesture interactive tutorial. */ +public class OverviewGestureTutorialFragment extends TutorialFragment { + @Override + int getHandAnimationResId() { + return R.drawable.overview_gesture; + } + + @Override + TutorialController createController(TutorialType type) { + return new OverviewGestureTutorialController(this, type); + } + + @Override + Class getControllerClass() { + return OverviewGestureTutorialController.class; + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java new file mode 100644 index 0000000000..14e00dce7a --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java @@ -0,0 +1,246 @@ +/* + * 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.interaction; + +import static com.android.launcher3.anim.Interpolators.ACCEL; +import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs; +import static com.android.quickstep.BaseSwipeUpHandlerV2.MAX_SWIPE_DURATION; +import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE; +import static com.android.quickstep.interaction.TutorialController.TutorialType.OVERVIEW_NAVIGATION_COMPLETE; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Insets; +import android.graphics.Outline; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.os.Build; +import android.view.SurfaceControl; +import android.view.View; +import android.view.ViewOutlineProvider; +import android.view.WindowInsets; +import android.view.WindowManager; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.Utilities; +import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.PendingAnimation; +import com.android.quickstep.AnimatedFloat; +import com.android.quickstep.GestureState; +import com.android.quickstep.OverviewComponentObserver; +import com.android.quickstep.RecentsAnimationDeviceState; +import com.android.quickstep.SwipeUpAnimationLogic; +import com.android.quickstep.SwipeUpAnimationLogic.RunningWindowAnim; +import com.android.quickstep.util.RectFSpringAnim; +import com.android.quickstep.util.TransformParams; +import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; + +@TargetApi(Build.VERSION_CODES.R) +abstract class SwipeUpGestureTutorialController extends TutorialController { + private final ViewSwipeUpAnimation mViewSwipeUpAnimation; + private float mFakeTaskViewRadius; + private Rect mFakeTaskViewRect = new Rect(); + private RunningWindowAnim mRunningWindowAnim; + + SwipeUpGestureTutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) { + super(tutorialFragment, tutorialType); + RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(mContext); + OverviewComponentObserver observer = new OverviewComponentObserver(mContext, deviceState); + mViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, deviceState, + new GestureState(observer, -1)); + observer.onDestroy(); + deviceState.destroy(); + + DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext) + .getDeviceProfile(mContext) + .copy(mContext); + Insets insets = mContext.getSystemService(WindowManager.class) + .getCurrentWindowMetrics() + .getWindowInsets() + .getInsets(WindowInsets.Type.systemBars()); + dp.updateInsets(new Rect(insets.left, insets.top, insets.right, insets.bottom)); + mViewSwipeUpAnimation.initDp(dp); + + mFakeTaskViewRadius = QuickStepContract.getWindowCornerRadius(mContext.getResources()); + mFakeTaskView.setClipToOutline(true); + mFakeTaskView.setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(mFakeTaskViewRect, mFakeTaskViewRadius); + } + }); + } + + private void cancelRunningAnimation() { + if (mRunningWindowAnim != null) { + mRunningWindowAnim.cancel(); + } + mRunningWindowAnim = null; + } + + /** Fades the task view, optionally after animating to a fake Overview. */ + void fadeOutFakeTaskView(boolean toOverviewFirst, @Nullable Runnable onEndRunnable) { + hideFeedback(); + hideHandCoachingAnimation(); + cancelRunningAnimation(); + PendingAnimation anim = new PendingAnimation(300); + AnimatorListenerAdapter resetTaskView = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation, boolean isReverse) { + mFakeTaskView.setVisibility(View.INVISIBLE); + mFakeTaskView.setAlpha(1); + mRunningWindowAnim = null; + } + }; + if (toOverviewFirst) { + anim.setFloat(mViewSwipeUpAnimation.getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation, boolean isReverse) { + PendingAnimation fadeAnim = new PendingAnimation(300); + fadeAnim.setViewAlpha(mFakeTaskView, 0, ACCEL); + fadeAnim.addListener(resetTaskView); + AnimatorSet animset = fadeAnim.buildAnim(); + animset.setStartDelay(100); + animset.start(); + mRunningWindowAnim = RunningWindowAnim.wrap(animset); + } + }); + } else { + anim.setViewAlpha(mFakeTaskView, 0, ACCEL); + anim.addListener(resetTaskView); + } + if (onEndRunnable != null) { + anim.addListener(AnimationSuccessListener.forRunnable(onEndRunnable)); + } + AnimatorSet animset = anim.buildAnim(); + animset.start(); + mRunningWindowAnim = RunningWindowAnim.wrap(animset); + } + + void animateFakeTaskViewHome(PointF finalVelocity, @Nullable Runnable onEndRunnable) { + hideFeedback(); + hideHandCoachingAnimation(); + cancelRunningAnimation(); + RectFSpringAnim rectAnim = + mViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity); + // After home animation finishes, fade out and run onEndRunnable. + rectAnim.addAnimatorListener(AnimationSuccessListener.forRunnable( + () -> fadeOutFakeTaskView(false, onEndRunnable))); + mRunningWindowAnim = RunningWindowAnim.wrap(rectAnim); + } + + @Override + public void setNavBarGestureProgress(@Nullable Float displacement) { + if (displacement == null || mTutorialType == HOME_NAVIGATION_COMPLETE + || mTutorialType == OVERVIEW_NAVIGATION_COMPLETE) { + mFakeTaskView.setVisibility(View.INVISIBLE); + } else { + mFakeTaskView.setVisibility(View.VISIBLE); + if (mRunningWindowAnim == null) { + mViewSwipeUpAnimation.updateDisplacement(displacement); + } + } + } + + class ViewSwipeUpAnimation extends SwipeUpAnimationLogic { + + ViewSwipeUpAnimation(Context context, RecentsAnimationDeviceState deviceState, + GestureState gestureState) { + super(context, deviceState, gestureState, new FakeTransformParams()); + } + + void initDp(DeviceProfile dp) { + initTransitionEndpoints(dp); + mTaskViewSimulator.setPreviewBounds( + new Rect(0, 0, dp.widthPx, dp.heightPx), dp.getInsets()); + } + + @Override + public void updateFinalShift() { + float progress = mCurrentShift.value / mDragLengthFactor; + mWindowTransitionController.setPlayFraction(progress); + mTaskViewSimulator.apply(mTransformParams); + } + + AnimatedFloat getCurrentShift() { + return mCurrentShift; + } + + RectFSpringAnim handleSwipeUpToHome(PointF velocity) { + PointF velocityPxPerMs = new PointF(velocity.x, velocity.y); + float currentShift = mCurrentShift.value; + final float startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y + * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor); + float distanceToTravel = (1 - currentShift) * mTransitionDragLength; + + // we want the page's snap velocity to approximately match the velocity at + // which the user flings, so we scale the duration by a value near to the + // derivative of the scroll interpolator at zero, ie. 2. + long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y)); + long duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration); + HomeAnimationFactory homeAnimFactory = new HomeAnimationFactory(null) { + @Override + public AnimatorPlaybackController createActivityAnimationToHome() { + return AnimatorPlaybackController.wrap(new AnimatorSet(), duration); + } + + @NonNull + @Override + public RectF getWindowTargetRect() { + int fakeHomeIconSizePx = mDp.allAppsIconSizePx; + int fakeHomeIconLeft = (mDp.widthPx - fakeHomeIconSizePx) / 2; + int fakeHomeIconTop = mDp.heightPx - (mDp.allAppsCellHeightPx * 3); + return new RectF(fakeHomeIconLeft, fakeHomeIconTop, + fakeHomeIconLeft + fakeHomeIconSizePx, + fakeHomeIconTop + fakeHomeIconSizePx); + } + }; + RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift, homeAnimFactory); + windowAnim.start(mContext, velocityPxPerMs); + return windowAnim; + } + } + + private class FakeTransformParams extends TransformParams { + + @Override + public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) { + SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null); + proxy.onBuildTargetParams(builder, null, this); + return new SurfaceParams[] {builder.build()}; + } + + @Override + public void applySurfaceParams(SurfaceParams[] params) { + SurfaceParams p = params[0]; + mFakeTaskView.setAnimationMatrix(p.matrix); + mFakeTaskViewRect.set(p.windowCrop); + mFakeTaskViewRadius = p.cornerRadius; + mFakeTaskView.invalidateOutline(); + } + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java index f27d500eff..511c8b6dc6 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java @@ -140,6 +140,9 @@ abstract class TutorialController implements BackGestureAttemptCallback, void onActionTextButtonClicked(View button) {} void showHandCoachingAnimation() { + if (isComplete()) { + return; + } mHandCoachingAnimation.startLoopedAnimation(mTutorialType); } @@ -153,6 +156,12 @@ abstract class TutorialController implements BackGestureAttemptCallback, hideFeedback(); updateTitles(); updateActionButtons(); + + if (isComplete()) { + hideHandCoachingAnimation(); + } else { + showHandCoachingAnimation(); + } } private void updateTitles() { @@ -190,12 +199,20 @@ abstract class TutorialController implements BackGestureAttemptCallback, button.setOnClickListener(listener); } + private boolean isComplete() { + return mTutorialType == TutorialType.BACK_NAVIGATION_COMPLETE + || mTutorialType == TutorialType.HOME_NAVIGATION_COMPLETE + || mTutorialType == TutorialType.OVERVIEW_NAVIGATION_COMPLETE; + } + /** Denotes the type of the tutorial. */ enum TutorialType { RIGHT_EDGE_BACK_NAVIGATION, LEFT_EDGE_BACK_NAVIGATION, BACK_NAVIGATION_COMPLETE, HOME_NAVIGATION, - HOME_NAVIGATION_COMPLETE + HOME_NAVIGATION_COMPLETE, + OVERVIEW_NAVIGATION, + OVERVIEW_NAVIGATION_COMPLETE } } diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java index a3881cffb0..da6815d776 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java @@ -68,6 +68,9 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener { case HOME_NAVIGATION: case HOME_NAVIGATION_COMPLETE: return new HomeGestureTutorialFragment(); + case OVERVIEW_NAVIGATION: + case OVERVIEW_NAVIGATION_COMPLETE: + return new OverviewGestureTutorialFragment(); default: Log.e(LOG_TAG, "Failed to find an appropriate fragment for " + tutorialType.name()); } diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java index 5bf91737c1..e80ee2cfe9 100644 --- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java +++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java @@ -237,6 +237,15 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat { return true; }); sandboxCategory.addPreference(launchHomeTutorialPreference); + Preference launchOverviewTutorialPreference = new Preference(context); + launchOverviewTutorialPreference.setKey("launchOverviewTutorial"); + launchOverviewTutorialPreference.setTitle("Launch Overview Tutorial"); + launchOverviewTutorialPreference.setSummary("Learn how to use the Overview gesture"); + launchOverviewTutorialPreference.setOnPreferenceClickListener(preference -> { + startActivity(launchSandboxIntent.putExtra("tutorial_type", "OVERVIEW_NAVIGATION")); + return true; + }); + sandboxCategory.addPreference(launchOverviewTutorialPreference); } private String toName(String action) {