Adds feedback to Home and Back tutorials.
Back feedback:
- Too far from edge of screen
- Cancelled (reversed gesture, swiped upwards, etc.)
- Inside nav bar region
- Demo: https://drive.google.com/open?id=1pc_hr7i-iZmgF37CN8oijPjhOvvsJoEG
Home feedback:
- Too far from edge of screen
- Paused too long (Overview detected)
- Swiped sideways instead of up
- Demo: https://drive.google.com/open?id=1NGYAlqV2wJtM2DOJ1pZM-r8N1SYVtMos
Bug: 148542211
Test: Manual
Change-Id: I627ed7c6e9b005d35794e4ae568529b5613cbf70
diff --git a/quickstep/res/layout/gesture_tutorial_fragment.xml b/quickstep/res/layout/gesture_tutorial_fragment.xml
index 69481ad..6e36722 100644
--- a/quickstep/res/layout/gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/gesture_tutorial_fragment.xml
@@ -66,6 +66,17 @@
style="@style/TextAppearance.GestureTutorial.Subtitle"/>
</LinearLayout>
+ <TextView
+ android:id="@+id/gesture_tutorial_fragment_feedback_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="10dp"
+ android:layout_centerHorizontal="true"
+ android:layout_above="@id/gesture_tutorial_fragment_action_button"
+ android:layout_marginStart="@dimen/gesture_tutorial_feedback_margin_start_end"
+ android:layout_marginEnd="@dimen/gesture_tutorial_feedback_margin_start_end"
+ style="@style/TextAppearance.GestureTutorial.Feedback"/>
+
<!-- android:stateListAnimator="@null" removes shadow and normal on click behavior (increase
of elevation and shadow) which is replaced by ripple effect in android:foreground -->
<Button
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 18dc19c..b06dc6b 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -83,5 +83,6 @@
<!-- Tips Gesture Tutorial -->
<dimen name="gesture_tutorial_title_margin_start_end">40dp</dimen>
<dimen name="gesture_tutorial_subtitle_margin_start_end">16dp</dimen>
+ <dimen name="gesture_tutorial_feedback_margin_start_end">24dp</dimen>
<dimen name="gesture_tutorial_button_margin_start_end">18dp</dimen>
</resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 61690ae..b474a32 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -98,12 +98,22 @@
<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] -->
<string name="back_gesture_tutorial_engaged_subtitle_swipe_inward_right_edge" translatable="false">Start at the right edge and swipe toward the middle</string>
+ <!-- Feedback shown during interactive parts of Back gesture tutorial for right edge when the gesture is too far from the edge. [CHAR LIMIT=100] -->
+ <string name="back_gesture_feedback_swipe_too_far_from_right_edge" translatable="false">Make sure you swipe from the far right edge</string>
+ <!-- Feedback shown during interactive parts of Back gesture tutorial for right edge when the gesture is cancelled. [CHAR LIMIT=100] -->
+ <string name="back_gesture_feedback_cancelled_right_edge" translatable="false">Make sure you swipe straight to the left and let go</string>
<!-- Title shown during interactive part of Back gesture tutorial for left edge. [CHAR LIMIT=30] -->
<string name="back_gesture_tutorial_playground_title_swipe_inward_left_edge" translatable="false">Try the other side</string>
<!-- Subtitle shown during interactive parts of Back gesture tutorial for left edge. [CHAR LIMIT=60] -->
<string name="back_gesture_tutorial_engaged_subtitle_swipe_inward_left_edge" translatable="false">That\'s it! Now try swiping from the left edge.</string>
+ <!-- Feedback shown during interactive parts of Back gesture tutorial for left edge when the gesture is too far from the edge. [CHAR LIMIT=100] -->
+ <string name="back_gesture_feedback_swipe_too_far_from_left_edge" translatable="false">Make sure you swipe from the far left edge</string>
+ <!-- Feedback shown during interactive parts of Back gesture tutorial for left edge when the gesture is cancelled. [CHAR LIMIT=100] -->
+ <string name="back_gesture_feedback_cancelled_left_edge" translatable="false">Make sure you swipe straight to the right and let go</string>
+ <!-- Feedback shown during interactive parts of Back gesture tutorial when the gesture is within the nav bar region. [CHAR LIMIT=100] -->
+ <string name="back_gesture_feedback_swipe_in_nav_bar" translatable="false">Make sure you don\'t swipe too close to the bottom of the screen</string>
<!-- 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>
@@ -112,6 +122,12 @@
<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] -->
<string name="home_gesture_tutorial_playground_subtitle" translatable="false">Try swiping upward from the bottom edge of the screen</string>
+ <!-- Feedback shown during interactive parts of Home gesture tutorial when the gesture is started too far from the edge. [CHAR LIMIT=100] -->
+ <string name="home_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 Home gesture tutorial when the Overview gesture is detected. [CHAR LIMIT=100] -->
+ <string name="home_gesture_feedback_overview_detected" translatable="false">Make sure you don\'t pause before letting go</string>
+ <!-- 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 on the confirmation screen after successful gesture. [CHAR LIMIT=30] -->
<string name="gesture_tutorial_confirm_title" translatable="false">All set</string>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 4915f5f..3926988 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -47,6 +47,14 @@
<item name="android:textSize">21sp</item>
</style>
+ <style name="TextAppearance.GestureTutorial.Feedback"
+ parent="TextAppearance.GestureTutorial">
+ <item name="android:gravity">center</item>
+ <item name="android:textColor">@color/gesture_tutorial_feedback_color</item>
+ <item name="android:letterSpacing">0.03</item>
+ <item name="android:textSize">21sp</item>
+ </style>
+
<style name="TextAppearance.GestureTutorial.ButtonLabel"
parent="TextAppearance.GestureTutorial.CallToAction">
<item name="android:gravity">center</item>
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index ff98701..91e8f68 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -35,7 +35,7 @@
void transitToController() {
super.transitToController();
if (mTutorialType != BACK_NAVIGATION_COMPLETE) {
- mHandCoachingAnimation.startLoopedAnimation(mTutorialType);
+ showHandCoachingAnimation();
}
}
@@ -96,16 +96,10 @@
public void onBackGestureAttempted(BackGestureResult result) {
switch (mTutorialType) {
case RIGHT_EDGE_BACK_NAVIGATION:
- if (result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) {
- hideHandCoachingAnimation();
- mTutorialFragment.changeController(LEFT_EDGE_BACK_NAVIGATION);
- }
+ handleAttemptFromRight(result);
break;
case LEFT_EDGE_BACK_NAVIGATION:
- if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT) {
- hideHandCoachingAnimation();
- mTutorialFragment.changeController(BACK_NAVIGATION_COMPLETE);
- }
+ handleAttemptFromLeft(result);
break;
case BACK_NAVIGATION_COMPLETE:
if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT
@@ -116,6 +110,46 @@
}
}
+ private void handleAttemptFromRight(BackGestureResult result) {
+ switch (result) {
+ case BACK_COMPLETED_FROM_RIGHT:
+ hideHandCoachingAnimation();
+ mTutorialFragment.changeController(LEFT_EDGE_BACK_NAVIGATION);
+ break;
+ case BACK_CANCELLED_FROM_RIGHT:
+ showFeedback(R.string.back_gesture_feedback_cancelled_right_edge);
+ break;
+ case BACK_COMPLETED_FROM_LEFT:
+ case BACK_CANCELLED_FROM_LEFT:
+ case BACK_NOT_STARTED_TOO_FAR_FROM_EDGE:
+ showFeedback(R.string.back_gesture_feedback_swipe_too_far_from_right_edge);
+ break;
+ case BACK_NOT_STARTED_IN_NAV_BAR_REGION:
+ showFeedback(R.string.back_gesture_feedback_swipe_in_nav_bar);
+ break;
+ }
+ }
+
+ private void handleAttemptFromLeft(BackGestureResult result) {
+ switch (result) {
+ case BACK_COMPLETED_FROM_LEFT:
+ hideHandCoachingAnimation();
+ mTutorialFragment.changeController(BACK_NAVIGATION_COMPLETE);
+ break;
+ case BACK_CANCELLED_FROM_LEFT:
+ showFeedback(R.string.back_gesture_feedback_cancelled_left_edge);
+ break;
+ case BACK_COMPLETED_FROM_RIGHT:
+ case BACK_CANCELLED_FROM_RIGHT:
+ case BACK_NOT_STARTED_TOO_FAR_FROM_EDGE:
+ showFeedback(R.string.back_gesture_feedback_swipe_too_far_from_left_edge);
+ break;
+ case BACK_NOT_STARTED_IN_NAV_BAR_REGION:
+ showFeedback(R.string.back_gesture_feedback_swipe_in_nav_bar);
+ break;
+ }
+ }
+
@Override
public void onNavBarGestureAttempted(NavBarGestureResult result) {
if (mTutorialType == BACK_NAVIGATION_COMPLETE) {
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
index 59067c1..730e3ef 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
@@ -29,4 +29,9 @@
TutorialController createController(TutorialType type) {
return new BackGestureTutorialController(this, type);
}
+
+ @Override
+ Class<? extends TutorialController> getControllerClass() {
+ return BackGestureTutorialController.class;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
index 89c57a0..e4b348e 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
@@ -58,6 +58,7 @@
private final PointF mDownPoint = new PointF();
private boolean mThresholdCrossed = false;
private boolean mAllowGesture = false;
+ private BackGestureResult mDisallowedGestureReason;
private boolean mIsEnabled;
private int mLeftInset;
private int mRightInset;
@@ -145,11 +146,13 @@
private boolean isWithinTouchRegion(int x, int y) {
// Disallow if too far from the edge
if (x > mEdgeWidth + mLeftInset && x < (mDisplaySize.x - mEdgeWidth - mRightInset)) {
+ mDisallowedGestureReason = BackGestureResult.BACK_NOT_STARTED_TOO_FAR_FROM_EDGE;
return false;
}
// Disallow if we are in the bottom gesture area
if (y >= (mDisplaySize.y - mBottomGestureHeight)) {
+ mDisallowedGestureReason = BackGestureResult.BACK_NOT_STARTED_IN_NAV_BAR_REGION;
return false;
}
@@ -169,12 +172,12 @@
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
boolean isOnLeftEdge = ev.getX() <= mEdgeWidth + mLeftInset;
+ mDisallowedGestureReason = BackGestureResult.UNKNOWN;
mAllowGesture = isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
+ mDownPoint.set(ev.getX(), ev.getY());
if (mAllowGesture) {
mEdgeBackPanel.setIsLeftPanel(isOnLeftEdge);
mEdgeBackPanel.onMotionEvent(ev);
-
- mDownPoint.set(ev.getX(), ev.getY());
mThresholdCrossed = false;
}
} else if (mAllowGesture) {
@@ -193,7 +196,6 @@
if (dy > dx && dy > mTouchSlop) {
cancelGesture(ev);
return;
-
} else if (dx > dy && dx > mTouchSlop) {
mThresholdCrossed = true;
}
@@ -206,8 +208,10 @@
}
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
- if (!mAllowGesture && mGestureCallback != null) {
- mGestureCallback.onBackGestureAttempted(BackGestureResult.BACK_NOT_STARTED);
+ float dx = Math.abs(ev.getX() - mDownPoint.x);
+ float dy = Math.abs(ev.getY() - mDownPoint.y);
+ if (dx > dy && dx > mTouchSlop && !mAllowGesture && mGestureCallback != null) {
+ mGestureCallback.onBackGestureAttempted(mDisallowedGestureReason);
}
}
}
@@ -223,7 +227,8 @@
BACK_COMPLETED_FROM_RIGHT,
BACK_CANCELLED_FROM_LEFT,
BACK_CANCELLED_FROM_RIGHT,
- BACK_NOT_STARTED,
+ BACK_NOT_STARTED_TOO_FAR_FROM_EDGE,
+ BACK_NOT_STARTED_IN_NAV_BAR_REGION,
}
/** Callback to let the UI react to attempted back gestures. */
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index 95b3c79..0e45376 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -34,7 +34,7 @@
void transitToController() {
super.transitToController();
if (mTutorialType != HOME_NAVIGATION_COMPLETE) {
- mHandCoachingAnimation.startLoopedAnimation(mTutorialType);
+ showHandCoachingAnimation();
}
}
@@ -88,9 +88,21 @@
public void onNavBarGestureAttempted(NavBarGestureResult result) {
switch (mTutorialType) {
case HOME_NAVIGATION:
- if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) {
- hideHandCoachingAnimation();
- mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE);
+ switch (result) {
+ case HOME_GESTURE_COMPLETED:
+ hideHandCoachingAnimation();
+ mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE);
+ break;
+ case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
+ case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE:
+ showFeedback(R.string.home_gesture_feedback_swipe_too_far_from_edge);
+ break;
+ case OVERVIEW_GESTURE_COMPLETED:
+ showFeedback(R.string.home_gesture_feedback_overview_detected);
+ break;
+ case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION:
+ showFeedback(R.string.home_gesture_feedback_wrong_swipe_direction);
+ break;
}
break;
case HOME_NAVIGATION_COMPLETE:
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
index 613f188..e2a9d12 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
@@ -29,4 +29,9 @@
TutorialController createController(TutorialType type) {
return new HomeGestureTutorialController(this, type);
}
+
+ @Override
+ Class<? extends TutorialController> getControllerClass() {
+ return HomeGestureTutorialController.class;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 69c61ce..8d68c76 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -32,16 +32,21 @@
abstract class TutorialController implements BackGestureAttemptCallback,
NavBarGestureAttemptCallback {
+ private static final int FEEDBACK_VISIBLE_MS = 3000;
+ private static final int FEEDBACK_ANIMATION_MS = 500;
+
final TutorialFragment mTutorialFragment;
- final TutorialType mTutorialType;
+ TutorialType mTutorialType;
final ImageButton mCloseButton;
final TextView mTitleTextView;
final TextView mSubtitleTextView;
+ final TextView mFeedbackView;
final TutorialHandAnimation mHandCoachingAnimation;
final ImageView mHandCoachingView;
final Button mActionTextButton;
final Button mActionButton;
+ private final Runnable mHideFeedbackRunnable;
TutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
mTutorialFragment = tutorialFragment;
@@ -52,12 +57,21 @@
mCloseButton.setOnClickListener(button -> mTutorialFragment.closeTutorial());
mTitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_title_view);
mSubtitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_subtitle_view);
+ mFeedbackView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_view);
mHandCoachingAnimation = tutorialFragment.getHandAnimation();
mHandCoachingView = rootView.findViewById(R.id.gesture_tutorial_fragment_hand_coaching);
mHandCoachingView.bringToFront();
mActionTextButton =
rootView.findViewById(R.id.gesture_tutorial_fragment_action_text_button);
mActionButton = rootView.findViewById(R.id.gesture_tutorial_fragment_action_button);
+
+ mHideFeedbackRunnable =
+ () -> mFeedbackView.animate().alpha(0).setDuration(FEEDBACK_ANIMATION_MS)
+ .withEndAction(this::showHandCoachingAnimation).start();
+ }
+
+ void setTutorialType(TutorialType tutorialType) {
+ mTutorialType = tutorialType;
}
@Nullable
@@ -80,10 +94,29 @@
return null;
}
+ void showFeedback(int resId) {
+ hideHandCoachingAnimation();
+ mFeedbackView.setText(resId);
+ mFeedbackView.animate().alpha(1).setDuration(FEEDBACK_ANIMATION_MS).start();
+ mFeedbackView.removeCallbacks(mHideFeedbackRunnable);
+ mFeedbackView.postDelayed(mHideFeedbackRunnable, FEEDBACK_VISIBLE_MS);
+ }
+
+ void hideFeedback() {
+ mFeedbackView.setText(null);
+ mFeedbackView.removeCallbacks(mHideFeedbackRunnable);
+ mFeedbackView.clearAnimation();
+ mFeedbackView.setAlpha(0);
+ }
+
void onActionButtonClicked(View button) {}
void onActionTextButtonClicked(View button) {}
+ void showHandCoachingAnimation() {
+ mHandCoachingAnimation.startLoopedAnimation(mTutorialType);
+ }
+
void hideHandCoachingAnimation() {
mHandCoachingAnimation.stop();
mHandCoachingView.setVisibility(View.INVISIBLE);
@@ -91,6 +124,7 @@
@CallSuper
void transitToController() {
+ hideFeedback();
updateTitles();
updateActionButtons();
}
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
index 3d02525..3a56b0e 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -86,6 +86,8 @@
abstract TutorialController createController(TutorialType type);
+ abstract Class<? extends TutorialController> getControllerClass();
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -147,7 +149,11 @@
}
void changeController(TutorialType tutorialType) {
- mTutorialController = createController(tutorialType);
+ if (getControllerClass().isInstance(mTutorialController)) {
+ mTutorialController.setTutorialType(tutorialType);
+ } else {
+ mTutorialController = createController(tutorialType);
+ }
mTutorialController.transitToController();
mEdgeBackGestureHandler.registerBackGestureAttemptCallback(mTutorialController);
mNavBarGestureHandler.registerNavBarGestureAttemptCallback(mTutorialController);
diff --git a/res/values/colors.xml b/res/values/colors.xml
index adcff9a..a99c644 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -39,8 +39,9 @@
<color name="all_apps_bg_hand_fill_dark">#9AA0A6</color>
<color name="gesture_tutorial_background_color">#FFFFFFFF</color>
- <color name="gesture_tutorial_subtitle_color">#99000000</color> <!-- 60% black -->
<color name="gesture_tutorial_title_color">#FF000000</color>
+ <color name="gesture_tutorial_subtitle_color">#99000000</color> <!-- 60% black -->
+ <color name="gesture_tutorial_feedback_color">#FF000000</color>
<color name="gesture_tutorial_action_button_label_color">#FFFFFFFF</color>
<color name="gesture_tutorial_primary_color">#1A73E8</color> <!-- Blue -->