diff --git a/quickstep/res/drawable/gesture_tutorial_finger_dot.xml b/quickstep/res/drawable/gesture_tutorial_finger_dot.xml new file mode 100644 index 0000000000..5f8aafd30a --- /dev/null +++ b/quickstep/res/drawable/gesture_tutorial_finger_dot.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/quickstep/res/drawable/gesture_tutorial_motion_back.xml b/quickstep/res/drawable/gesture_tutorial_motion_back.xml deleted file mode 100644 index a6860fac64..0000000000 --- a/quickstep/res/drawable/gesture_tutorial_motion_back.xml +++ /dev/null @@ -1,1233 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/quickstep/res/drawable/gesture_tutorial_motion_home_dark_mode.xml b/quickstep/res/drawable/gesture_tutorial_motion_home_dark_mode.xml deleted file mode 100644 index aff35c1bce..0000000000 --- a/quickstep/res/drawable/gesture_tutorial_motion_home_dark_mode.xml +++ /dev/null @@ -1,1254 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/quickstep/res/drawable/gesture_tutorial_motion_home_light_mode.xml b/quickstep/res/drawable/gesture_tutorial_motion_home_light_mode.xml deleted file mode 100644 index 98d97ad62d..0000000000 --- a/quickstep/res/drawable/gesture_tutorial_motion_home_light_mode.xml +++ /dev/null @@ -1,1254 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/quickstep/res/drawable/gesture_tutorial_motion_overview_dark_mode.xml b/quickstep/res/drawable/gesture_tutorial_motion_overview_dark_mode.xml deleted file mode 100644 index b007d20f34..0000000000 --- a/quickstep/res/drawable/gesture_tutorial_motion_overview_dark_mode.xml +++ /dev/null @@ -1,1623 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/drawable/gesture_tutorial_ripple.xml b/quickstep/res/drawable/gesture_tutorial_ripple.xml similarity index 100% rename from res/drawable/gesture_tutorial_ripple.xml rename to quickstep/res/drawable/gesture_tutorial_ripple.xml diff --git a/quickstep/res/drawable/mock_conversation.xml b/quickstep/res/drawable/mock_conversation.xml deleted file mode 100644 index 272d9ed8a8..0000000000 --- a/quickstep/res/drawable/mock_conversation.xml +++ /dev/null @@ -1,212 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/quickstep/res/drawable/mock_conversations_list.xml b/quickstep/res/drawable/mock_conversations_list.xml deleted file mode 100644 index 2dbc88f0bf..0000000000 --- a/quickstep/res/drawable/mock_conversations_list.xml +++ /dev/null @@ -1,361 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/quickstep/res/drawable/mock_webpage_dark_mode.xml b/quickstep/res/drawable/mock_webpage_dark_mode.xml deleted file mode 100644 index 93b22b7d31..0000000000 --- a/quickstep/res/drawable/mock_webpage_dark_mode.xml +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/quickstep/res/drawable/mock_webpage_light_mode.xml b/quickstep/res/drawable/mock_webpage_light_mode.xml deleted file mode 100644 index 98abb92ab7..0000000000 --- a/quickstep/res/drawable/mock_webpage_light_mode.xml +++ /dev/null @@ -1,263 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/quickstep/res/layout/gesture_tutorial_dialog.xml b/quickstep/res/layout/gesture_tutorial_dialog.xml index 59bf7b965c..db6ec8533f 100644 --- a/quickstep/res/layout/gesture_tutorial_dialog.xml +++ b/quickstep/res/layout/gesture_tutorial_dialog.xml @@ -1,4 +1,18 @@ + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/quickstep/res/layout/gesture_tutorial_mock_conversation_list.xml b/quickstep/res/layout/gesture_tutorial_mock_conversation_list.xml new file mode 100644 index 0000000000..ad6b165019 --- /dev/null +++ b/quickstep/res/layout/gesture_tutorial_mock_conversation_list.xml @@ -0,0 +1,394 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/quickstep/res/layout/gesture_tutorial_mock_webpage.xml b/quickstep/res/layout/gesture_tutorial_mock_webpage.xml new file mode 100644 index 0000000000..ab00a113b4 --- /dev/null +++ b/quickstep/res/layout/gesture_tutorial_mock_webpage.xml @@ -0,0 +1,274 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/quickstep/res/values-night/colors.xml b/quickstep/res/values-night/colors.xml new file mode 100644 index 0000000000..c3b253654e --- /dev/null +++ b/quickstep/res/values-night/colors.xml @@ -0,0 +1,25 @@ + + + + + #99000000 + + #000000 + + #202124 + #3c4043 + + \ No newline at end of file diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml index 17980f0eeb..47552924ef 100644 --- a/quickstep/res/values/colors.xml +++ b/quickstep/res/values/colors.xml @@ -14,8 +14,6 @@ limitations under the License. --> - #FFFFFFFF - #99000000 #fff #39000000 @@ -31,4 +29,40 @@ #EBffffff #99000000 + + + #FFFFFFFF + + #f9f9f9 + #A0C2F9 + #6DA1FF + + #3C4043 + #FF000000 + #B7F29F + + + #f1f3f4 + #e8eaed + #dadce0 + #bdc1c6 + #e8eaed + #dadce0 + #dadce0 + + + #dadce0 + #e8eaed + #f8f9fa + #9aa0a6 + #bdc1c6 + #bdc1c6 + + + #f1f3f4 + #6e7175 + #9a9a9a + #e8eaed + #80868b + #bdc1c6 \ No newline at end of file diff --git a/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialController.java index 957f776ae4..2f3a912fb4 100644 --- a/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialController.java @@ -68,7 +68,6 @@ final class AssistantGestureTutorialController extends TutorialController { showFeedback(R.string.assistant_gesture_feedback_swipe_too_far_from_corner); break; case ASSISTANT_COMPLETED: - hideFeedback(true); showRippleEffect(null); showFeedback(R.string.assistant_gesture_tutorial_playground_subtitle); break; diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java index 3cb22f4ffe..f2402e31de 100644 --- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java @@ -18,10 +18,9 @@ package com.android.quickstep.interaction; import static com.android.quickstep.interaction.TutorialController.TutorialType.BACK_NAVIGATION; import static com.android.quickstep.interaction.TutorialController.TutorialType.BACK_NAVIGATION_COMPLETE; +import android.annotation.LayoutRes; import android.graphics.PointF; -import androidx.appcompat.content.res.AppCompatResources; - import com.android.launcher3.R; import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult; import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult; @@ -44,8 +43,18 @@ final class BackGestureTutorialController extends TutorialController { } @Override - protected int getMockAppTaskThumbnailResId(boolean forDarkMode) { - return R.drawable.mock_conversation; + protected int getMockAppTaskLayoutResId() { + return getMockAppTaskCurrentPageLayoutResId(); + } + + @LayoutRes + int getMockAppTaskCurrentPageLayoutResId() { + return R.layout.gesture_tutorial_mock_conversation; + } + + @LayoutRes + int getMockAppTaskPreviousPageLayoutResId() { + return R.layout.gesture_tutorial_mock_conversation_list; } @Override @@ -70,10 +79,8 @@ final class BackGestureTutorialController extends TutorialController { switch (result) { case BACK_COMPLETED_FROM_LEFT: case BACK_COMPLETED_FROM_RIGHT: - mTutorialFragment.releaseGestureVideoView(); - hideFeedback(true); - mFakeTaskView.setBackground(AppCompatResources.getDrawable(mContext, - R.drawable.mock_conversations_list)); + mTutorialFragment.releaseFeedbackAnimation(); + updateFakeAppTaskViewLayout(getMockAppTaskPreviousPageLayoutResId()); int subtitleResId = mTutorialFragment.isAtFinalStep() ? R.string.back_gesture_feedback_complete_without_follow_up : R.string.back_gesture_feedback_complete_with_overview_follow_up; diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java index 1740f68cc1..f54734d525 100644 --- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java @@ -15,6 +15,10 @@ */ package com.android.quickstep.interaction; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.view.MotionEvent; import android.view.View; @@ -23,18 +27,76 @@ import androidx.annotation.Nullable; import com.android.launcher3.R; import com.android.quickstep.interaction.TutorialController.TutorialType; +import java.util.ArrayList; + /** Shows the Back gesture interactive tutorial. */ public class BackGestureTutorialFragment extends TutorialFragment { + @Nullable @Override - Integer getFeedbackVideoResId(boolean forDarkMode) { - return R.drawable.gesture_tutorial_motion_back; + Integer getEdgeAnimationResId() { + return R.drawable.gesture_tutorial_loop_back; } @Nullable @Override - Integer getGestureVideoResId() { - return R.drawable.gesture_tutorial_loop_back; + protected Animator createGestureAnimation() { + if (!(mTutorialController instanceof BackGestureTutorialController)) { + return null; + } + BackGestureTutorialController controller = + (BackGestureTutorialController) mTutorialController; + float fingerDotStartTranslationX = (float) -(mRootView.getWidth() / 2); + + AnimatorSet fingerDotAppearanceAnimator = controller.createFingerDotAppearanceAnimatorSet(); + fingerDotAppearanceAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + mFingerDotView.setTranslationX(fingerDotStartTranslationX); + } + }); + + ObjectAnimator translationAnimator = ObjectAnimator.ofFloat( + mFingerDotView, View.TRANSLATION_X, fingerDotStartTranslationX, 0); + translationAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + controller.updateFakeAppTaskViewLayout( + controller.getMockAppTaskPreviousPageLayoutResId()); + } + }); + translationAnimator.setDuration(1000); + + Animator animationPause = controller.createAnimationPause(); + animationPause.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + controller.updateFakeAppTaskViewLayout( + controller.getMockAppTaskCurrentPageLayoutResId()); + } + }); + ArrayList animators = new ArrayList<>(); + + animators.add(fingerDotAppearanceAnimator); + animators.add(translationAnimator); + animators.add(controller.createFingerDotDisappearanceAnimatorSet()); + animators.add(animationPause); + + AnimatorSet finalAnimation = new AnimatorSet(); + finalAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + super.onAnimationCancel(animation); + controller.updateFakeAppTaskViewLayout( + controller.getMockAppTaskCurrentPageLayoutResId()); + } + }); + finalAnimation.playSequentially(animators); + + return finalAnimation; } @Override @@ -49,6 +111,7 @@ public class BackGestureTutorialFragment extends TutorialFragment { @Override public boolean onTouch(View view, MotionEvent motionEvent) { + releaseFeedbackAnimation(); if (motionEvent.getAction() == MotionEvent.ACTION_DOWN && mTutorialController != null) { mTutorialController.setRippleHotspot(motionEvent.getX(), motionEvent.getY()); } diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java index 0521db4d1b..7465db3025 100644 --- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java +++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java @@ -282,9 +282,7 @@ public class EdgeBackGesturePanel extends View { .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); int currentNightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - mPaint.setColor(context.getColor(currentNightMode == Configuration.UI_MODE_NIGHT_YES - ? R.color.back_arrow_color_light - : R.color.back_arrow_color_dark)); + mPaint.setColor(context.getColor(R.color.gesture_tutorial_back_arrow_color)); loadDimens(); updateArrowDirection(); diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java index 819c91c55b..307a8fd230 100644 --- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java @@ -42,8 +42,8 @@ final class HomeGestureTutorialController extends SwipeUpGestureTutorialControll } @Override - protected int getMockAppTaskThumbnailResId(boolean forDarkMode) { - return forDarkMode ? R.drawable.mock_webpage_dark_mode : R.drawable.mock_webpage_light_mode; + protected int getMockAppTaskLayoutResId() { + return R.layout.gesture_tutorial_mock_webpage; } @Override @@ -80,7 +80,7 @@ final class HomeGestureTutorialController extends SwipeUpGestureTutorialControll case HOME_NAVIGATION: switch (result) { case HOME_GESTURE_COMPLETED: { - mTutorialFragment.releaseGestureVideoView(); + mTutorialFragment.releaseFeedbackAnimation(); animateFakeTaskViewHome(finalVelocity, null); int subtitleResId = mTutorialFragment.isAtFinalStep() ? R.string.home_gesture_feedback_complete_without_follow_up diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java index 9572637b0f..dcae07d88d 100644 --- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java @@ -15,25 +15,73 @@ */ package com.android.quickstep.interaction; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.view.MotionEvent; +import android.view.View; + import androidx.annotation.Nullable; import com.android.launcher3.R; import com.android.quickstep.interaction.TutorialController.TutorialType; +import java.util.ArrayList; + /** Shows the Home gesture interactive tutorial. */ public class HomeGestureTutorialFragment extends TutorialFragment { + @Nullable @Override - Integer getFeedbackVideoResId(boolean forDarkMode) { - return forDarkMode - ? R.drawable.gesture_tutorial_motion_home_dark_mode - : R.drawable.gesture_tutorial_motion_home_light_mode; + Integer getEdgeAnimationResId() { + return R.drawable.gesture_tutorial_loop_home; } @Nullable @Override - Integer getGestureVideoResId() { - return R.drawable.gesture_tutorial_loop_home; + protected Animator createGestureAnimation() { + if (!(mTutorialController instanceof HomeGestureTutorialController)) { + return null; + } + float fingerDotStartTranslationY = (float) mRootView.getFullscreenHeight() / 2; + HomeGestureTutorialController controller = + (HomeGestureTutorialController) mTutorialController; + + AnimatorSet fingerDotAppearanceAnimator = controller.createFingerDotAppearanceAnimatorSet(); + fingerDotAppearanceAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + mFingerDotView.setTranslationY(fingerDotStartTranslationY); + } + }); + + Animator animationPause = controller.createAnimationPause(); + animationPause.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + controller.resetFakeTaskView(); + } + }); + ArrayList animators = new ArrayList<>(); + + animators.add(fingerDotAppearanceAnimator); + animators.add(controller.createFingerDotHomeSwipeAnimator(fingerDotStartTranslationY)); + animators.add(controller.createFingerDotDisappearanceAnimatorSet()); + animators.add(animationPause); + + AnimatorSet finalAnimation = new AnimatorSet(); + finalAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + super.onAnimationCancel(animation); + controller.resetFakeTaskView(); + } + }); + finalAnimation.playSequentially(animators); + + return finalAnimation; } @Override @@ -45,4 +93,10 @@ public class HomeGestureTutorialFragment extends TutorialFragment { Class getControllerClass() { return HomeGestureTutorialController.class; } + + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + releaseFeedbackAnimation(); + return super.onTouch(view, motionEvent); + } } diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java index 77ddb2b320..b38a641b0e 100644 --- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java @@ -49,8 +49,8 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont } @Override - protected int getMockAppTaskThumbnailResId(boolean forDarkMode) { - return R.drawable.mock_conversations_list; + protected int getMockAppTaskLayoutResId() { + return R.layout.gesture_tutorial_mock_conversation_list; } @Override @@ -98,13 +98,8 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge); break; case OVERVIEW_GESTURE_COMPLETED: - mTutorialFragment.releaseGestureVideoView(); - PendingAnimation anim = new PendingAnimation(300); - anim.setFloat(mTaskViewSwipeUpAnimation - .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL); - AnimatorSet animset = anim.buildAnim(); - animset.start(); - mRunningWindowAnim = SwipeUpAnimationLogic.RunningWindowAnim.wrap(animset); + mTutorialFragment.releaseFeedbackAnimation(); + animateTaskViewToOverview(); onMotionPaused(true /*arbitrary value*/); int subtitleResId = mTutorialFragment.getNumSteps() > 1 && mTutorialFragment.isAtFinalStep() @@ -126,4 +121,13 @@ final class OverviewGestureTutorialController extends SwipeUpGestureTutorialCont break; } } + + public void animateTaskViewToOverview() { + PendingAnimation anim = new PendingAnimation(300); + anim.setFloat(mTaskViewSwipeUpAnimation + .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL); + AnimatorSet animset = anim.buildAnim(); + animset.start(); + mRunningWindowAnim = SwipeUpAnimationLogic.RunningWindowAnim.wrap(animset); + } } diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java index d2ec327c81..968412bedc 100644 --- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java @@ -15,25 +15,96 @@ */ package com.android.quickstep.interaction; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.view.MotionEvent; +import android.view.View; + import androidx.annotation.Nullable; import com.android.launcher3.R; import com.android.quickstep.interaction.TutorialController.TutorialType; +import java.util.ArrayList; + /** Shows the Overview gesture interactive tutorial. */ public class OverviewGestureTutorialFragment extends TutorialFragment { + @Nullable @Override - Integer getFeedbackVideoResId(boolean forDarkMode) { - return forDarkMode - ? R.drawable.gesture_tutorial_motion_overview_dark_mode - : R.drawable.gesture_tutorial_motion_overview_light_mode; + Integer getEdgeAnimationResId() { + return R.drawable.gesture_tutorial_loop_overview; } @Nullable @Override - Integer getGestureVideoResId() { - return R.drawable.gesture_tutorial_loop_overview; + protected Animator createGestureAnimation() { + if (!(mTutorialController instanceof OverviewGestureTutorialController)) { + return null; + } + float fingerDotStartTranslationY = (float) mRootView.getFullscreenHeight() / 2; + OverviewGestureTutorialController controller = + (OverviewGestureTutorialController) mTutorialController; + + AnimatorSet fingerDotAppearanceAnimator = controller.createFingerDotAppearanceAnimatorSet(); + fingerDotAppearanceAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + + mFingerDotView.setTranslationY(fingerDotStartTranslationY); + } + }); + + Animator swipeAnimator = + controller.createFingerDotOverviewSwipeAnimator(fingerDotStartTranslationY); + swipeAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mFakePreviousTaskView.setVisibility(View.VISIBLE); + controller.onMotionPaused(true /*arbitrary value*/); + } + }); + + AnimatorSet fingerDotDisappearanceAnimator = + controller.createFingerDotDisappearanceAnimatorSet(); + fingerDotDisappearanceAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + controller.animateTaskViewToOverview(); + } + }); + + Animator animationPause = controller.createAnimationPause(); + animationPause.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + controller.resetFakeTaskView(); + } + }); + ArrayList animators = new ArrayList<>(); + + animators.add(fingerDotAppearanceAnimator); + animators.add(swipeAnimator); + animators.add(controller.createAnimationPause()); + animators.add(fingerDotDisappearanceAnimator); + animators.add(animationPause); + + AnimatorSet finalAnimation = new AnimatorSet(); + finalAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + super.onAnimationCancel(animation); + controller.resetFakeTaskView(); + } + }); + finalAnimation.playSequentially(animators); + + return finalAnimation; } @Override @@ -45,4 +116,10 @@ public class OverviewGestureTutorialFragment extends TutorialFragment { Class getControllerClass() { return OverviewGestureTutorialController.class; } + + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + releaseFeedbackAnimation(); + return super.onTouch(view, motionEvent); + } } diff --git a/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java b/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java index db1afc2012..ac0c17d72f 100644 --- a/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java +++ b/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java @@ -16,8 +16,10 @@ package com.android.quickstep.interaction; import android.content.Context; +import android.graphics.Insets; import android.util.AttributeSet; import android.view.MotionEvent; +import android.view.WindowInsets; import android.widget.RelativeLayout; import androidx.fragment.app.FragmentManager; @@ -41,4 +43,13 @@ public class RootSandboxLayout extends RelativeLayout { return ((TutorialFragment) FragmentManager.findFragment(this)) .onInterceptTouch(motionEvent); } + + /** + * Returns this view's fullscreen height. This method is agnostic of this view's actual height. + */ + public int getFullscreenHeight() { + Insets insets = getRootWindowInsets().getInsets(WindowInsets.Type.systemBars()); + + return getHeight() + insets.top + insets.bottom; + } } diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java index 04b147cc1a..c2c8f61b19 100644 --- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java @@ -25,6 +25,7 @@ import static com.android.quickstep.interaction.TutorialController.TutorialType. import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; +import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Outline; @@ -32,7 +33,6 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; -import android.util.DisplayMetrics; import android.view.SurfaceControl; import android.view.View; import android.view.ViewOutlineProvider; @@ -63,23 +63,24 @@ abstract class SwipeUpGestureTutorialController extends TutorialController { private static final int FAKE_PREVIOUS_TASK_MARGIN = Utilities.dpToPx(12); + private static final long HOME_SWIPE_ANIMATION_DURATION_MILLIS = 625; + private static final long OVERVIEW_SWIPE_ANIMATION_DURATION_MILLIS = 1000; + final ViewSwipeUpAnimation mTaskViewSwipeUpAnimation; private float mFakeTaskViewRadius; - private Rect mFakeTaskViewRect = new Rect(); + private final Rect mFakeTaskViewRect = new Rect(); RunningWindowAnim mRunningWindowAnim; private boolean mShowTasks = false; private boolean mShowPreviousTasks = false; - private AnimatorListenerAdapter mResetTaskView = new AnimatorListenerAdapter() { + private final AnimatorListenerAdapter mResetTaskView = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mFakeHotseatView.setVisibility(View.INVISIBLE); mFakeIconView.setVisibility(View.INVISIBLE); if (mTutorialFragment.getActivity() != null) { - DisplayMetrics displayMetrics = - mTutorialFragment.getResources().getDisplayMetrics(); - int height = displayMetrics.heightPixels; - int width = displayMetrics.widthPixels; + int height = mTutorialFragment.getRootView().getFullscreenHeight(); + int width = mTutorialFragment.getRootView().getWidth(); mFakeTaskViewRect.set(0, 0, width, height); } mFakeTaskViewRadius = 0; @@ -108,9 +109,8 @@ abstract class SwipeUpGestureTutorialController extends TutorialController { .copy(mContext); mTaskViewSwipeUpAnimation.initDp(dp); - DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); - int height = displayMetrics.heightPixels; - int width = displayMetrics.widthPixels; + int height = mTutorialFragment.getRootView().getFullscreenHeight(); + int width = mTutorialFragment.getRootView().getWidth(); mFakeTaskViewRect.set(0, 0, width, height); mFakeTaskViewRadius = 0; @@ -138,7 +138,6 @@ abstract class SwipeUpGestureTutorialController extends TutorialController { /** Fades the task view, optionally after animating to a fake Overview. */ void fadeOutFakeTaskView(boolean toOverviewFirst, boolean reset, @Nullable Runnable onEndRunnable) { - hideFeedback(true); cancelRunningAnimation(); PendingAnimation anim = new PendingAnimation(300); if (toOverviewFirst) { @@ -184,6 +183,7 @@ abstract class SwipeUpGestureTutorialController extends TutorialController { } void resetFakeTaskView() { + mFakeTaskView.setVisibility(View.VISIBLE); PendingAnimation anim = new PendingAnimation(300); anim.setFloat(mTaskViewSwipeUpAnimation .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL); @@ -195,7 +195,6 @@ abstract class SwipeUpGestureTutorialController extends TutorialController { } void animateFakeTaskViewHome(PointF finalVelocity, @Nullable Runnable onEndRunnable) { - hideFeedback(true); cancelRunningAnimation(); mFakePreviousTaskView.setVisibility(View.INVISIBLE); mFakeHotseatView.setVisibility(View.VISIBLE); @@ -218,9 +217,6 @@ abstract class SwipeUpGestureTutorialController extends TutorialController { if (mGestureCompleted) { return; } - if (displacement != null) { - hideFeedback(true); - } if (mTutorialType == HOME_NAVIGATION_COMPLETE || mTutorialType == OVERVIEW_NAVIGATION_COMPLETE) { mFakeTaskView.setVisibility(View.INVISIBLE); @@ -336,6 +332,44 @@ abstract class SwipeUpGestureTutorialController extends TutorialController { } } + protected Animator createFingerDotHomeSwipeAnimator(float fingerDotStartTranslationY) { + Animator homeSwipeAnimator = createFingerDotSwipeUpAnimator(fingerDotStartTranslationY) + .setDuration(HOME_SWIPE_ANIMATION_DURATION_MILLIS); + + homeSwipeAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + animateFakeTaskViewHome( + new PointF( + 0f, + fingerDotStartTranslationY / HOME_SWIPE_ANIMATION_DURATION_MILLIS), + null); + } + }); + + return homeSwipeAnimator; + } + + protected Animator createFingerDotOverviewSwipeAnimator(float fingerDotStartTranslationY) { + return createFingerDotSwipeUpAnimator(fingerDotStartTranslationY) + .setDuration(OVERVIEW_SWIPE_ANIMATION_DURATION_MILLIS); + } + + + private Animator createFingerDotSwipeUpAnimator(float fingerDotStartTranslationY) { + ValueAnimator swipeAnimator = ValueAnimator.ofFloat(0f, 1f); + + swipeAnimator.addUpdateListener(valueAnimator -> { + float gestureProgress = + -fingerDotStartTranslationY * valueAnimator.getAnimatedFraction(); + setNavBarGestureProgress(gestureProgress); + mFingerDotView.setTranslationY(fingerDotStartTranslationY + gestureProgress); + }); + + return swipeAnimator; + } + private class FakeTransformParams extends TransformParams { @Override diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java index 4b4e7e6753..77bfc31c30 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java @@ -16,25 +16,32 @@ package com.android.quickstep.interaction; import static android.view.View.GONE; +import static android.view.View.NO_ID; +import static android.view.View.inflate; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.content.Context; import android.content.pm.PackageManager; -import android.graphics.drawable.Animatable2; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.widget.Button; +import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import androidx.annotation.CallSuper; import androidx.annotation.DrawableRes; +import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; @@ -48,18 +55,25 @@ import com.android.launcher3.views.ClipIconView; import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback; import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureAttemptCallback; +import java.util.ArrayList; + abstract class TutorialController implements BackGestureAttemptCallback, NavBarGestureAttemptCallback { private static final String TAG = "TutorialController"; + private static final float FINGER_DOT_VISIBLE_ALPHA = 0.6f; + private static final float FINGER_DOT_SMALL_SCALE = 0.7f; + private static final int FINGER_DOT_ANIMATION_DURATION_MILLIS = 500; + private static final String PIXEL_TIPS_APP_PACKAGE_NAME = "com.google.android.apps.tips"; private static final CharSequence DEFAULT_PIXEL_TIPS_APP_NAME = "Pixel Tips"; - private static final int FEEDBACK_ANIMATION_MS = 250; + private static final int FEEDBACK_ANIMATION_MS = 133; private static final int RIPPLE_VISIBLE_MS = 300; private static final int GESTURE_ANIMATION_DELAY_MS = 1500; private static final int ADVANCE_TUTORIAL_TIMEOUT_MS = 4000; + private static final long GESTURE_ANIMATION_PAUSE_DURATION_MILLIS = 1000; final TutorialFragment mTutorialFragment; TutorialType mTutorialType; @@ -68,17 +82,17 @@ abstract class TutorialController implements BackGestureAttemptCallback, final TextView mCloseButton; final ViewGroup mFeedbackView; final TextView mFeedbackTitleView; - final ImageView mFeedbackVideoView; - final ImageView mGestureVideoView; + final ImageView mEdgeGestureVideoView; final RelativeLayout mFakeLauncherView; final ImageView mFakeHotseatView; final ClipIconView mFakeIconView; - final View mFakeTaskView; + final FrameLayout mFakeTaskView; final View mFakePreviousTaskView; final View mRippleView; final RippleDrawable mRippleDrawable; final Button mActionButton; final TutorialStepIndicator mTutorialStepView; + final ImageView mFingerDotView; private final AlertDialog mSkipTutorialDialog; protected boolean mGestureCompleted = false; @@ -87,7 +101,8 @@ abstract class TutorialController implements BackGestureAttemptCallback, // views before posting new callbacks. private final Runnable mTitleViewCallback; @Nullable private Runnable mFeedbackViewCallback; - @Nullable private Runnable mFeedbackVideoViewCallback; + @Nullable private Runnable mFakeTaskViewCallback; + private final Runnable mShowFeedbackRunnable; TutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) { mTutorialFragment = tutorialFragment; @@ -100,8 +115,7 @@ abstract class TutorialController implements BackGestureAttemptCallback, mFeedbackView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_view); mFeedbackTitleView = mFeedbackView.findViewById( R.id.gesture_tutorial_fragment_feedback_title); - mFeedbackVideoView = rootView.findViewById(R.id.gesture_tutorial_feedback_video); - mGestureVideoView = rootView.findViewById(R.id.gesture_tutorial_gesture_video); + mEdgeGestureVideoView = rootView.findViewById(R.id.gesture_tutorial_edge_gesture_video); mFakeLauncherView = rootView.findViewById(R.id.gesture_tutorial_fake_launcher_view); mFakeHotseatView = rootView.findViewById(R.id.gesture_tutorial_fake_hotseat_view); mFakeIconView = rootView.findViewById(R.id.gesture_tutorial_fake_icon_view); @@ -113,10 +127,34 @@ abstract class TutorialController implements BackGestureAttemptCallback, mActionButton = rootView.findViewById(R.id.gesture_tutorial_fragment_action_button); mTutorialStepView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_tutorial_step); + mFingerDotView = rootView.findViewById(R.id.gesture_tutorial_finger_dot); mSkipTutorialDialog = createSkipTutorialDialog(); mTitleViewCallback = () -> mFeedbackTitleView.sendAccessibilityEvent( AccessibilityEvent.TYPE_VIEW_FOCUSED); + mShowFeedbackRunnable = () -> { + mFeedbackView.setAlpha(0f); + mFeedbackView.setScaleX(0.95f); + mFeedbackView.setScaleY(0.95f); + mFeedbackView.setVisibility(View.VISIBLE); + mFeedbackView.animate() + .setDuration(FEEDBACK_ANIMATION_MS) + .alpha(1f) + .scaleX(1f) + .scaleY(1f) + .withEndAction(() -> { + if (mGestureCompleted && !mTutorialFragment.isAtFinalStep()) { + if (mFeedbackViewCallback != null) { + mFeedbackView.removeCallbacks(mFeedbackViewCallback); + } + mFeedbackViewCallback = mTutorialFragment::continueTutorial; + mFeedbackView.postDelayed(mFeedbackViewCallback, + ADVANCE_TUTORIAL_TIMEOUT_MS); + } + }) + .start(); + mFeedbackTitleView.postDelayed(mTitleViewCallback, FEEDBACK_ANIMATION_MS); + }; } private void showSkipTutorialDialog() { @@ -134,9 +172,9 @@ abstract class TutorialController implements BackGestureAttemptCallback, return R.drawable.default_sandbox_mock_launcher; } - @DrawableRes - protected int getMockAppTaskThumbnailResId(boolean forDarkMode) { - return R.drawable.default_sandbox_app_task_thumbnail; + @LayoutRes + protected int getMockAppTaskLayoutResId() { + return View.NO_ID; } @DrawableRes @@ -173,17 +211,10 @@ abstract class TutorialController implements BackGestureAttemptCallback, mFeedbackView.setTranslationY(0); return; } - AnimatedVectorDrawable tutorialAnimation = mTutorialFragment.getTutorialAnimation(); - AnimatedVectorDrawable gestureAnimation = mTutorialFragment.getGestureAnimation(); - - if (tutorialAnimation != null && gestureAnimation != null) { - TextView title = mFeedbackView.findViewById( - R.id.gesture_tutorial_fragment_feedback_title); - - playFeedbackVideo(tutorialAnimation, gestureAnimation, () -> { - mFeedbackView.setTranslationY(0); - title.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); - }, true); + Animator gestureAnimation = mTutorialFragment.getGestureAnimation(); + AnimatedVectorDrawable edgeAnimation = mTutorialFragment.getEdgeAnimation(); + if (gestureAnimation != null && edgeAnimation != null) { + playFeedbackAnimation(gestureAnimation, edgeAnimation, mShowFeedbackRunnable, true); } } @@ -215,8 +246,13 @@ abstract class TutorialController implements BackGestureAttemptCallback, int subtitleResId, boolean isGestureSuccessful, boolean useGestureAnimationDelay) { - mFeedbackTitleView.setText(titleResId); mFeedbackTitleView.removeCallbacks(mTitleViewCallback); + if (mFeedbackViewCallback != null) { + mFeedbackView.removeCallbacks(mFeedbackViewCallback); + mFeedbackViewCallback = null; + } + + mFeedbackTitleView.setText(titleResId); TextView subtitle = mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_feedback_subtitle); subtitle.setText(subtitleResId); @@ -226,77 +262,68 @@ abstract class TutorialController implements BackGestureAttemptCallback, showActionButton(); } - if (mFeedbackVideoViewCallback != null) { - mFeedbackVideoView.removeCallbacks(mFeedbackVideoViewCallback); - mFeedbackVideoViewCallback = null; + if (mFakeTaskViewCallback != null) { + mFakeTaskView.removeCallbacks(mFakeTaskViewCallback); + mFakeTaskViewCallback = null; } } mGestureCompleted = isGestureSuccessful; - AnimatedVectorDrawable tutorialAnimation = mTutorialFragment.getTutorialAnimation(); - AnimatedVectorDrawable gestureAnimation = mTutorialFragment.getGestureAnimation(); - if (tutorialAnimation != null && gestureAnimation != null) { - if (!isGestureSuccessful) { - playFeedbackVideo(tutorialAnimation, gestureAnimation, () -> { - mFeedbackView.setTranslationY( - -mFeedbackView.getHeight() - mFeedbackView.getTop()); - mFeedbackView.setVisibility(View.VISIBLE); - mFeedbackView.animate() - .setDuration(FEEDBACK_ANIMATION_MS) - .translationY(0) - .start(); - mFeedbackTitleView.postDelayed(mTitleViewCallback, FEEDBACK_ANIMATION_MS); - }, useGestureAnimationDelay); - return; - } else { - mTutorialFragment.releaseFeedbackVideoView(); - } + Animator gestureAnimation = mTutorialFragment.getGestureAnimation(); + AnimatedVectorDrawable edgeAnimation = mTutorialFragment.getEdgeAnimation(); + if (!isGestureSuccessful && gestureAnimation != null && edgeAnimation != null) { + playFeedbackAnimation( + gestureAnimation, + edgeAnimation, + mShowFeedbackRunnable, + useGestureAnimationDelay); + return; + } else { + mTutorialFragment.releaseFeedbackAnimation(); } - mFeedbackView.setTranslationY(-mFeedbackView.getHeight() - mFeedbackView.getTop()); - mFeedbackView.setVisibility(View.VISIBLE); - mFeedbackView.animate() - .setDuration(FEEDBACK_ANIMATION_MS) - .translationY(0) - .withEndAction(() -> { - if (isGestureSuccessful && !mTutorialFragment.isAtFinalStep()) { - if (mFeedbackViewCallback != null) { - mFeedbackView.removeCallbacks(mFeedbackViewCallback); - } - mFeedbackViewCallback = mTutorialFragment::continueTutorial; - mFeedbackView.postDelayed(mFeedbackViewCallback, - ADVANCE_TUTORIAL_TIMEOUT_MS); - } - }) - .start(); - mFeedbackTitleView.postDelayed(mTitleViewCallback, FEEDBACK_ANIMATION_MS); + mFeedbackViewCallback = mShowFeedbackRunnable; + + mFeedbackView.post(mFeedbackViewCallback); } - void hideFeedback(boolean releaseFeedbackVideo) { + void hideFeedback() { + cancelQueuedGestureAnimation(); mFeedbackView.clearAnimation(); mFeedbackView.setVisibility(View.INVISIBLE); - if (releaseFeedbackVideo) { - mTutorialFragment.releaseFeedbackVideoView(); - } } - private void playFeedbackVideo( - @NonNull AnimatedVectorDrawable tutorialAnimation, - @NonNull AnimatedVectorDrawable gestureAnimation, + void cancelQueuedGestureAnimation() { + if (mFeedbackViewCallback != null) { + mFeedbackView.removeCallbacks(mFeedbackViewCallback); + mFeedbackViewCallback = null; + } + if (mFakeTaskViewCallback != null) { + mFakeTaskView.removeCallbacks(mFakeTaskViewCallback); + mFakeTaskViewCallback = null; + } + mFeedbackTitleView.removeCallbacks(mTitleViewCallback); + } + + private void playFeedbackAnimation( + @NonNull Animator gestureAnimation, + @NonNull AnimatedVectorDrawable edgeAnimation, @NonNull Runnable onStartRunnable, boolean useGestureAnimationDelay) { - if (tutorialAnimation.isRunning()) { - tutorialAnimation.reset(); + if (gestureAnimation.isRunning()) { + gestureAnimation.cancel(); } - tutorialAnimation.registerAnimationCallback(new Animatable2.AnimationCallback() { - + if (edgeAnimation.isRunning()) { + edgeAnimation.reset(); + } + gestureAnimation.addListener(new AnimatorListenerAdapter() { @Override - public void onAnimationStart(Drawable drawable) { - super.onAnimationStart(drawable); + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); - mGestureVideoView.setVisibility(GONE); - if (gestureAnimation.isRunning()) { - gestureAnimation.stop(); + mEdgeGestureVideoView.setVisibility(GONE); + if (edgeAnimation.isRunning()) { + edgeAnimation.stop(); } if (!useGestureAnimationDelay) { @@ -305,37 +332,25 @@ abstract class TutorialController implements BackGestureAttemptCallback, } @Override - public void onAnimationEnd(Drawable drawable) { - super.onAnimationEnd(drawable); + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); - mGestureVideoView.setVisibility(View.VISIBLE); - gestureAnimation.start(); + mEdgeGestureVideoView.setVisibility(View.VISIBLE); + edgeAnimation.start(); - tutorialAnimation.unregisterAnimationCallback(this); + gestureAnimation.removeListener(this); } }); - if (mFeedbackViewCallback != null) { - mFeedbackVideoView.removeCallbacks(mFeedbackViewCallback); - mFeedbackViewCallback = null; - } - if (mFeedbackVideoViewCallback != null) { - mFeedbackVideoView.removeCallbacks(mFeedbackVideoViewCallback); - mFeedbackVideoViewCallback = null; - } + cancelQueuedGestureAnimation(); if (useGestureAnimationDelay) { mFeedbackViewCallback = onStartRunnable; - mFeedbackVideoViewCallback = () -> { - mFeedbackVideoView.setVisibility(View.VISIBLE); - tutorialAnimation.start(); - }; + mFakeTaskViewCallback = gestureAnimation::start; - mFeedbackVideoView.setVisibility(View.GONE); mFeedbackView.post(mFeedbackViewCallback); - mFeedbackVideoView.postDelayed(mFeedbackVideoViewCallback, GESTURE_ANIMATION_DELAY_MS); + mFakeTaskView.postDelayed(mFakeTaskViewCallback, GESTURE_ANIMATION_DELAY_MS); } else { - mFeedbackVideoView.setVisibility(View.VISIBLE); - tutorialAnimation.start(); + gestureAnimation.start(); } } @@ -360,7 +375,7 @@ abstract class TutorialController implements BackGestureAttemptCallback, @CallSuper void transitToController() { - hideFeedback(false); + hideFeedback(); hideActionButton(); updateSubtext(); updateDrawables(); @@ -395,6 +410,17 @@ abstract class TutorialController implements BackGestureAttemptCallback, mActionButton.setOnClickListener(this::onActionButtonClicked); } + void updateFakeAppTaskViewLayout(@LayoutRes int mockAppTaskLayoutResId) { + mFakeTaskView.removeAllViews(); + if (mockAppTaskLayoutResId != NO_ID) { + mFakeTaskView.addView( + inflate(mContext, mockAppTaskLayoutResId, null), + new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + } + } + private void updateSubtext() { mTutorialStepView.setTutorialProgress( mTutorialFragment.getCurrentStep(), mTutorialFragment.getNumSteps()); @@ -404,15 +430,12 @@ abstract class TutorialController implements BackGestureAttemptCallback, if (mContext != null) { mTutorialFragment.getRootView().setBackground(AppCompatResources.getDrawable( mContext, getMockWallpaperResId())); - mTutorialFragment.updateFeedbackVideo(); + mTutorialFragment.updateFeedbackAnimation(); mFakeLauncherView.setBackgroundColor( - mContext.getColor(Utilities.isDarkTheme(mContext) - ? R.color.fake_wallpaper_color_dark_mode - : R.color.fake_wallpaper_color_light_mode)); + mContext.getColor(R.color.gesture_tutorial_fake_wallpaper_color)); mFakeHotseatView.setImageDrawable(AppCompatResources.getDrawable( mContext, getMockHotseatResId())); - mFakeTaskView.setBackground(AppCompatResources.getDrawable( - mContext, getMockAppTaskThumbnailResId(Utilities.isDarkTheme(mContext)))); + updateFakeAppTaskViewLayout(getMockAppTaskLayoutResId()); mFakeTaskView.animate().alpha(1).setListener( AnimatorListeners.forSuccessCallback(() -> mFakeTaskView.animate().cancel())); mFakePreviousTaskView.setBackground(AppCompatResources.getDrawable( @@ -485,6 +508,52 @@ abstract class TutorialController implements BackGestureAttemptCallback, return null; } + protected AnimatorSet createFingerDotAppearanceAnimatorSet() { + ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat( + mFingerDotView, View.ALPHA, 0f, FINGER_DOT_VISIBLE_ALPHA); + ObjectAnimator yScaleAnimator = ObjectAnimator.ofFloat( + mFingerDotView, View.SCALE_Y, FINGER_DOT_SMALL_SCALE, 1f); + ObjectAnimator xScaleAnimator = ObjectAnimator.ofFloat( + mFingerDotView, View.SCALE_X, FINGER_DOT_SMALL_SCALE, 1f); + ArrayList animators = new ArrayList<>(); + + animators.add(alphaAnimator); + animators.add(xScaleAnimator); + animators.add(yScaleAnimator); + + AnimatorSet appearanceAnimatorSet = new AnimatorSet(); + + appearanceAnimatorSet.playTogether(animators); + appearanceAnimatorSet.setDuration(FINGER_DOT_ANIMATION_DURATION_MILLIS); + + return appearanceAnimatorSet; + } + + protected AnimatorSet createFingerDotDisappearanceAnimatorSet() { + ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat( + mFingerDotView, View.ALPHA, FINGER_DOT_VISIBLE_ALPHA, 0f); + ObjectAnimator yScaleAnimator = ObjectAnimator.ofFloat( + mFingerDotView, View.SCALE_Y, 1f, FINGER_DOT_SMALL_SCALE); + ObjectAnimator xScaleAnimator = ObjectAnimator.ofFloat( + mFingerDotView, View.SCALE_X, 1f, FINGER_DOT_SMALL_SCALE); + ArrayList animators = new ArrayList<>(); + + animators.add(alphaAnimator); + animators.add(xScaleAnimator); + animators.add(yScaleAnimator); + + AnimatorSet appearanceAnimatorSet = new AnimatorSet(); + + appearanceAnimatorSet.playTogether(animators); + appearanceAnimatorSet.setDuration(FINGER_DOT_ANIMATION_DURATION_MILLIS); + + return appearanceAnimatorSet; + } + + protected Animator createAnimationPause() { + return ValueAnimator.ofFloat(0f, 1f).setDuration(GESTURE_ANIMATION_PAUSE_DURATION_MILLIS); + } + /** Denotes the type of the tutorial. */ enum TutorialType { BACK_NAVIGATION, diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java index 7637450a1a..52ec9b39f0 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java @@ -15,6 +15,8 @@ */ package com.android.quickstep.interaction; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -29,6 +31,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.widget.ImageView; @@ -38,7 +41,6 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import com.android.launcher3.R; -import com.android.launcher3.Utilities; import com.android.quickstep.interaction.TutorialController.TutorialType; abstract class TutorialFragment extends Fragment implements OnTouchListener { @@ -49,13 +51,14 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener { TutorialType mTutorialType; @Nullable TutorialController mTutorialController = null; RootSandboxLayout mRootView; + View mFingerDotView; + View mFakePreviousTaskView; EdgeBackGestureHandler mEdgeBackGestureHandler; NavBarGestureHandler mNavBarGestureHandler; - private ImageView mFeedbackVideoView; - private ImageView mGestureVideoView; + private ImageView mEdgeGestureVideoView; - @Nullable private AnimatedVectorDrawable mTutorialAnimation = null; - @Nullable private AnimatedVectorDrawable mGestureAnimation = null; + @Nullable private Animator mGestureAnimation = null; + @Nullable private AnimatedVectorDrawable mEdgeAnimation = null; private boolean mIntroductionShown = false; private boolean mFragmentStopped = false; @@ -96,24 +99,26 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener { return null; } - @Nullable Integer getFeedbackVideoResId(boolean forDarkMode) { - return null; - } - - @Nullable Integer getGestureVideoResId() { + @Nullable Integer getEdgeAnimationResId() { return null; } @Nullable - AnimatedVectorDrawable getTutorialAnimation() { - return mTutorialAnimation; - } - - @Nullable - AnimatedVectorDrawable getGestureAnimation() { + Animator getGestureAnimation() { return mGestureAnimation; } + @Nullable + AnimatedVectorDrawable getEdgeAnimation() { + return mEdgeAnimation; + } + + + @Nullable + protected Animator createGestureAnimation() { + return null; + } + abstract TutorialController createController(TutorialType type); abstract Class getControllerClass(); @@ -147,21 +152,22 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener { return insets; }); mRootView.setOnTouchListener(this); - mFeedbackVideoView = mRootView.findViewById(R.id.gesture_tutorial_feedback_video); - mGestureVideoView = mRootView.findViewById(R.id.gesture_tutorial_gesture_video); + mEdgeGestureVideoView = mRootView.findViewById(R.id.gesture_tutorial_edge_gesture_video); + mFingerDotView = mRootView.findViewById(R.id.gesture_tutorial_finger_dot); + mFakePreviousTaskView = mRootView.findViewById( + R.id.gesture_tutorial_fake_previous_task_view); return mRootView; } @Override public void onStop() { super.onStop(); - releaseFeedbackVideoView(); - releaseGestureVideoView(); + releaseFeedbackAnimation(); mFragmentStopped = true; } void initializeFeedbackVideoView() { - if (!updateFeedbackVideo()) { + if (!updateFeedbackAnimation()) { return; } @@ -176,87 +182,90 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener { } } - boolean updateFeedbackVideo() { - if (getContext() == null) { + boolean updateFeedbackAnimation() { + if (!updateEdgeAnimation()) { return false; } - Integer feedbackVideoResId = getFeedbackVideoResId(Utilities.isDarkTheme(getContext())); - - if (feedbackVideoResId == null || !updateGestureVideo()) { - return false; - } - mTutorialAnimation = (AnimatedVectorDrawable) getContext().getDrawable(feedbackVideoResId); - - if (mTutorialAnimation != null) { - mTutorialAnimation.registerAnimationCallback(new Animatable2.AnimationCallback() { - - @Override - public void onAnimationStart(Drawable drawable) { - super.onAnimationStart(drawable); - - mFeedbackVideoView.setVisibility(View.VISIBLE); - } - - @Override - public void onAnimationEnd(Drawable drawable) { - super.onAnimationEnd(drawable); - - releaseFeedbackVideoView(); - } - }); - } - mFeedbackVideoView.setImageDrawable(mTutorialAnimation); - - return true; - } - - boolean updateGestureVideo() { - Integer gestureVideoResId = getGestureVideoResId(); - if (gestureVideoResId == null || getContext() == null) { - return false; - } - mGestureAnimation = (AnimatedVectorDrawable) getContext().getDrawable(gestureVideoResId); + mGestureAnimation = createGestureAnimation(); if (mGestureAnimation != null) { - mGestureAnimation.registerAnimationCallback(new Animatable2.AnimationCallback() { + mGestureAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + mFingerDotView.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationCancel(Animator animation) { + super.onAnimationCancel(animation); + mFingerDotView.setVisibility(View.GONE); + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mFingerDotView.setVisibility(View.GONE); + } + }); + } + + return mGestureAnimation != null; + } + + boolean updateEdgeAnimation() { + Integer edgeAnimationResId = getEdgeAnimationResId(); + if (edgeAnimationResId == null || getContext() == null) { + return false; + } + mEdgeAnimation = (AnimatedVectorDrawable) getContext().getDrawable(edgeAnimationResId); + + if (mEdgeAnimation != null) { + mEdgeAnimation.registerAnimationCallback(new Animatable2.AnimationCallback() { @Override public void onAnimationEnd(Drawable drawable) { super.onAnimationEnd(drawable); - mGestureAnimation.start(); + mEdgeAnimation.start(); } }); } - mGestureVideoView.setImageDrawable(mGestureAnimation); + mEdgeGestureVideoView.setImageDrawable(mEdgeAnimation); - return true; + return mEdgeAnimation != null; } - void releaseFeedbackVideoView() { - if (mTutorialAnimation != null && mTutorialAnimation.isRunning()) { - mTutorialAnimation.stop(); + void releaseFeedbackAnimation() { + if (mTutorialController != null) { + mTutorialController.cancelQueuedGestureAnimation(); } - - mFeedbackVideoView.setVisibility(View.GONE); - } - - void releaseGestureVideoView() { if (mGestureAnimation != null && mGestureAnimation.isRunning()) { - mGestureAnimation.stop(); + mGestureAnimation.cancel(); + } + if (mEdgeAnimation != null && mEdgeAnimation.isRunning()) { + mEdgeAnimation.stop(); } - mGestureVideoView.setVisibility(View.GONE); + mEdgeGestureVideoView.setVisibility(View.GONE); } @Override public void onResume() { super.onResume(); + releaseFeedbackAnimation(); if (mFragmentStopped && mTutorialController != null) { mTutorialController.showFeedback(); mFragmentStopped = false; } else { - changeController(mTutorialType); + mRootView.getViewTreeObserver().addOnGlobalLayoutListener( + new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + changeController(mTutorialType); + mRootView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + }); } } @@ -292,6 +301,7 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener { mEdgeBackGestureHandler.registerBackGestureAttemptCallback(mTutorialController); mNavBarGestureHandler.registerNavBarGestureAttemptCallback(mTutorialController); mTutorialType = tutorialType; + initializeFeedbackVideoView(); } diff --git a/res/drawable/gesture_tutorial_motion_overview_light_mode.xml b/res/drawable/gesture_tutorial_motion_overview_light_mode.xml deleted file mode 100644 index 75887c9352..0000000000 --- a/res/drawable/gesture_tutorial_motion_overview_light_mode.xml +++ /dev/null @@ -1,1587 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/values/colors.xml b/res/values/colors.xml index cc5c5a39d3..5020127440 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -36,15 +36,6 @@ #E0E0E0 - #A0C2F9 - #6DA1FF - #000000 - #f9f9f9 - - #3C4043 - #FF000000 - #B7F29F - #FFF #F1F3F4 #E0E0E0