Adds Overview Sandbox tutorial.
Extracts fake task view animation logic to
SwipeUpGestureTutorialController, which
Home and Overview controllers extend.
Fixes: 157945497
Change-Id: Ibd1c129c57e9bee8db62baae455ca1bd9df114c7
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index be1d47b..77345a0 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -87,7 +87,6 @@
<!-- content description for hotseat items -->
<string name="hotseat_prediction_content_description">Predicted app: <xliff:g id="title" example="Chrome">%1$s</xliff:g></string>
-
<!-- Title shown during interactive part of Back gesture tutorial for right edge. [CHAR LIMIT=30] -->
<string name="back_gesture_tutorial_playground_title_swipe_inward_right_edge" translatable="false">Try the back gesture</string>
<!-- Subtitle shown during interactive parts of Back gesture tutorial for right edge. [CHAR LIMIT=60] -->
@@ -111,7 +110,6 @@
<!-- Subtitle shown on the confirmation screen after successful gesture. [CHAR LIMIT=60] -->
<string name="back_gesture_tutorial_confirm_subtitle" translatable="false">To change the sensitivity of the back gesture, go to Settings</string>
-
<!-- Title shown during interactive part of Home gesture tutorial. [CHAR LIMIT=30] -->
<string name="home_gesture_tutorial_playground_title" translatable="false">Tutorial: Go Home</string>
<!-- Subtitle shown during interactive parts of Home gesture tutorial. [CHAR LIMIT=60] -->
@@ -123,6 +121,17 @@
<!-- Feedback shown during interactive parts of Home gesture tutorial when the gesture is horizontal instead of vertical. [CHAR LIMIT=100] -->
<string name="home_gesture_feedback_wrong_swipe_direction" translatable="false">Make sure you swipe straight up</string>
+ <!-- Title shown during interactive part of Overview gesture tutorial. [CHAR LIMIT=30] -->
+ <string name="overview_gesture_tutorial_playground_title" translatable="false">Tutorial: Switch Apps</string>
+ <!-- Subtitle shown during interactive parts of Overview gesture tutorial. [CHAR LIMIT=60] -->
+ <string name="overview_gesture_tutorial_playground_subtitle" translatable="false">Swipe up from the bottom of the screen and hold</string>
+ <!-- Feedback shown during interactive parts of Overview gesture tutorial when the gesture is started too far from the edge. [CHAR LIMIT=100] -->
+ <string name="overview_gesture_feedback_swipe_too_far_from_edge" translatable="false">Make sure you swipe from the bottom edge of the screen</string>
+ <!-- Feedback shown during interactive parts of Overview gesture tutorial when the Home gesture is detected. [CHAR LIMIT=100] -->
+ <string name="overview_gesture_feedback_home_detected" translatable="false">Try holding the window for longer before releasing</string>
+ <!-- Feedback shown during interactive parts of Overview gesture tutorial when the gesture is horizontal instead of vertical. [CHAR LIMIT=100] -->
+ <string name="overview_gesture_feedback_wrong_swipe_direction" translatable="false">Make sure you swipe straight up and pause</string>
+
<!-- Title shown on the confirmation screen after successful gesture. [CHAR LIMIT=30] -->
<string name="gesture_tutorial_confirm_title" translatable="false">All set</string>
<!-- Button text shown on a button on the confirm screen to leave the tutorial. [CHAR LIMIT=14] -->
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index 41e86e0..1f398fc 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -35,14 +35,6 @@
}
@Override
- void transitToController() {
- super.transitToController();
- if (mTutorialType != BACK_NAVIGATION_COMPLETE) {
- showHandCoachingAnimation();
- }
- }
-
- @Override
Integer getTitleStringId() {
switch (mTutorialType) {
case RIGHT_EDGE_BACK_NAVIGATION:
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index 65f41a4..0edabd4 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 @@
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 @@
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 @@
}
}
- @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 0000000..c636eba
--- /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 0000000..3357b70
--- /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<? extends TutorialController> 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 0000000..14e00dc
--- /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 f27d500..511c8b6 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -140,6 +140,9 @@
void onActionTextButtonClicked(View button) {}
void showHandCoachingAnimation() {
+ if (isComplete()) {
+ return;
+ }
mHandCoachingAnimation.startLoopedAnimation(mTutorialType);
}
@@ -153,6 +156,12 @@
hideFeedback();
updateTitles();
updateActionButtons();
+
+ if (isComplete()) {
+ hideHandCoachingAnimation();
+ } else {
+ showHandCoachingAnimation();
+ }
}
private void updateTitles() {
@@ -190,12 +199,20 @@
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 a3881cf..da6815d 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -68,6 +68,9 @@
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 5bf9173..e80ee2c 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -237,6 +237,15 @@
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) {