Merge "Creating default implementation for state lisnter method" into ub-launcher3-rvc-dev
diff --git a/quickstep/res/layout/gesture_tutorial_fragment.xml b/quickstep/res/layout/gesture_tutorial_fragment.xml
index 69481ad..190290e 100644
--- a/quickstep/res/layout/gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/gesture_tutorial_fragment.xml
@@ -18,6 +18,12 @@
     android:layout_height="match_parent"
     android:background="@color/gesture_tutorial_background_color">
 
+    <View
+        android:id="@+id/gesture_tutorial_ripple_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@drawable/gesture_tutorial_ripple"/>
+
     <ImageView
         android:id="@+id/gesture_tutorial_fragment_hand_coaching"
         android:layout_width="match_parent"
@@ -66,6 +72,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/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 2e99500..2a9f32d 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -99,6 +99,10 @@
             return;
         }
         this.mMode = newMode;
+        // Swipe touch regions are independent of nav mode, so we have to clear them explicitly
+        // here to avoid, for ex, a nav region for 2-button rotation 0 being used for 3-button mode
+        // It tries to cache and reuse swipe regions whenever possible based only on rotation
+        mSwipeTouchRegions.clear();
         resetSwipeRegions(info);
     }
 
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index ff98701..fe95e83 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,50 @@
         }
     }
 
+    private void handleAttemptFromRight(BackGestureResult result) {
+        switch (result) {
+            case BACK_COMPLETED_FROM_RIGHT:
+                hideFeedback();
+                hideHandCoachingAnimation();
+                showRippleEffect(
+                        () -> 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:
+                hideFeedback();
+                hideHandCoachingAnimation();
+                showRippleEffect(
+                        () -> 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..bef50ea 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
@@ -15,6 +15,9 @@
  */
 package com.android.quickstep.interaction;
 
+import android.view.MotionEvent;
+import android.view.View;
+
 import com.android.launcher3.R;
 import com.android.quickstep.interaction.TutorialController.TutorialType;
 
@@ -29,4 +32,17 @@
     TutorialController createController(TutorialType type) {
         return new BackGestureTutorialController(this, type);
     }
+
+    @Override
+    Class<? extends TutorialController> getControllerClass() {
+        return BackGestureTutorialController.class;
+    }
+
+    @Override
+    public boolean onTouch(View view, MotionEvent motionEvent) {
+        if (motionEvent.getAction() == MotionEvent.ACTION_DOWN && mTutorialController != null) {
+            mTutorialController.setRippleHotspot(motionEvent.getX(), motionEvent.getY());
+        }
+        return super.onTouch(view, motionEvent);
+    }
 }
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..1e29f44 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep.interaction;
 
+import android.graphics.drawable.RippleDrawable;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
@@ -32,16 +33,24 @@
 abstract class TutorialController implements BackGestureAttemptCallback,
         NavBarGestureAttemptCallback {
 
+    private static final int FEEDBACK_VISIBLE_MS = 3000;
+    private static final int FEEDBACK_ANIMATION_MS = 500;
+    private static final int RIPPLE_VISIBLE_MS = 300;
+
     final TutorialFragment mTutorialFragment;
-    final TutorialType mTutorialType;
+    TutorialType mTutorialType;
 
     final ImageButton mCloseButton;
     final TextView mTitleTextView;
     final TextView mSubtitleTextView;
+    final TextView mFeedbackView;
+    final View mRippleView;
+    final RippleDrawable mRippleDrawable;
     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 +61,23 @@
         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);
+        mRippleView = rootView.findViewById(R.id.gesture_tutorial_ripple_view);
+        mRippleDrawable = (RippleDrawable) mRippleView.getBackground();
         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 +100,44 @@
         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 setRippleHotspot(float x, float y) {
+        mRippleDrawable.setHotspot(x, y);
+    }
+
+    void showRippleEffect(@Nullable Runnable onCompleteRunnable) {
+        mRippleDrawable.setState(
+                new int[] {android.R.attr.state_pressed, android.R.attr.state_enabled});
+        mRippleView.postDelayed(() -> {
+            mRippleDrawable.setState(new int[] {});
+            if (onCompleteRunnable != null) {
+                onCompleteRunnable.run();
+            }
+        }, RIPPLE_VISIBLE_MS);
+    }
+
     void onActionButtonClicked(View button) {}
 
     void onActionTextButtonClicked(View button) {}
 
+    void showHandCoachingAnimation() {
+        mHandCoachingAnimation.startLoopedAnimation(mTutorialType);
+    }
+
     void hideHandCoachingAnimation() {
         mHandCoachingAnimation.stop();
         mHandCoachingView.setVisibility(View.INVISIBLE);
@@ -91,6 +145,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/drawable/gesture_tutorial_ripple.xml b/res/drawable/gesture_tutorial_ripple.xml
new file mode 100644
index 0000000..ca45662
--- /dev/null
+++ b/res/drawable/gesture_tutorial_ripple.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ripple android:color="@color/gesture_tutorial_ripple_color"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@android:id/mask"
+        android:drawable="@color/gesture_tutorial_background_color" />
+</ripple>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index adcff9a..c9c893e 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -39,8 +39,10 @@
     <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_ripple_color">#A0C2F9</color> <!-- Light Blue -->
     <color name="gesture_tutorial_action_button_label_color">#FFFFFFFF</color>
     <color name="gesture_tutorial_primary_color">#1A73E8</color> <!-- Blue -->