Add all apps education tutorial.

* Added FeatureFlag.ENABLE_ALL_APPS_EDU
* When user swipes up on nav bar three times and goes to hint state
  consecutively, we show the new All Apps education tutorial.
* For now we block interaction while the animation is playing,
  and we remove the view when the animation is done.
* Future CL will leave view up until user successfully reaches All Apps state.

Bug: 151768994
Change-Id: I903e0a3914d0558950ecb8cd714d97ddc10ca06b
diff --git a/quickstep/recents_ui_overrides/res/drawable/all_apps_edu_circle.xml b/quickstep/recents_ui_overrides/res/drawable/all_apps_edu_circle.xml
new file mode 100644
index 0000000..df7cd8e
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/drawable/all_apps_edu_circle.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <solid android:color="?android:colorAccent"/>
+    <size android:height="@dimen/swipe_edu_circle_size"
+        android:width="@dimen/swipe_edu_circle_size" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/layout/all_apps_edu_view.xml b/quickstep/recents_ui_overrides/res/layout/all_apps_edu_view.xml
new file mode 100644
index 0000000..e7ef6e6
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/layout/all_apps_edu_view.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.quickstep.views.AllAppsEduView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="@dimen/swipe_edu_width"
+    android:layout_height="@dimen/swipe_edu_max_height"/>
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
new file mode 100644
index 0000000..df89f74
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.views;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+
+import androidx.core.graphics.ColorUtils;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.util.Themes;
+import com.android.quickstep.util.MultiValueUpdateListener;
+
+/**
+ * View used to educate the user on how to access All Apps when in No Nav Button navigation mode.
+ */
+public class AllAppsEduView extends AbstractFloatingView {
+
+    private Launcher mLauncher;
+
+    private AnimatorSet mAnimation;
+
+    private GradientDrawable mCircle;
+    private GradientDrawable mGradient;
+
+    private int mCircleSizePx;
+    private int mPaddingPx;
+    private int mWidthPx;
+    private int mMaxHeightPx;
+
+    public AllAppsEduView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mCircle = (GradientDrawable) context.getDrawable(R.drawable.all_apps_edu_circle);
+        mCircleSizePx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_circle_size);
+        mPaddingPx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_padding);
+        mWidthPx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_width);
+        mMaxHeightPx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_max_height);
+        setWillNotDraw(false);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        mGradient.draw(canvas);
+        mCircle.draw(canvas);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mIsOpen = true;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mIsOpen = false;
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        mLauncher.getDragLayer().removeView(this);
+    }
+
+    @Override
+    public void logActionCommand(int command) {
+        // TODO
+    }
+
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_ALL_APPS_EDU) != 0;
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        return mAnimation != null && mAnimation.isRunning();
+    }
+
+    private void playAnimation() {
+        if (mAnimation != null) {
+            return;
+        }
+        mAnimation = new AnimatorSet();
+
+        final Rect circleBoundsOg = new Rect(mCircle.getBounds());
+        final Rect gradientBoundsOg = new Rect(mGradient.getBounds());
+        final Rect temp = new Rect();
+        final float transY = mMaxHeightPx - mCircleSizePx - mPaddingPx;
+
+        // 1st: Circle alpha/scale
+        int firstPart = 600;
+        // 2nd: Circle animates upwards, Gradient alpha fades in, Gradient grows, All Apps hint
+        int secondPart = 1200;
+        int introDuration = firstPart + secondPart;
+
+        StateAnimationConfig config = new StateAnimationConfig();
+        config.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(ACCEL,
+                0, 0.08f));
+        config.duration = secondPart;
+        config.userControlled = false;
+        AnimatorPlaybackController stateAnimationController =
+                mLauncher.getStateManager().createAnimationToNewWorkspace(ALL_APPS, config);
+        float maxAllAppsProgress = 0.15f;
+
+        ValueAnimator intro = ValueAnimator.ofFloat(0, 1f);
+        intro.setInterpolator(LINEAR);
+        intro.setDuration(introDuration);
+        intro.addUpdateListener((new MultiValueUpdateListener() {
+            FloatProp mCircleAlpha = new FloatProp(0, 255, 0, firstPart, LINEAR);
+            FloatProp mCircleScale = new FloatProp(2f, 1f, 0, firstPart, OVERSHOOT_1_7);
+            FloatProp mDeltaY = new FloatProp(0, transY, firstPart, secondPart, FAST_OUT_SLOW_IN);
+            FloatProp mGradientAlpha = new FloatProp(0, 255, firstPart, secondPart * 0.3f, LINEAR);
+
+            @Override
+            public void onUpdate(float progress) {
+                temp.set(circleBoundsOg);
+                temp.offset(0, (int) -mDeltaY.value);
+                Utilities.scaleRectAboutCenter(temp, mCircleScale.value);
+                mCircle.setBounds(temp);
+                mCircle.setAlpha((int) mCircleAlpha.value);
+                mGradient.setAlpha((int) mGradientAlpha.value);
+
+                temp.set(gradientBoundsOg);
+                temp.top -= mDeltaY.value;
+                mGradient.setBounds(temp);
+                invalidate();
+
+                float stateProgress = Utilities.mapToRange(mDeltaY.value, 0, transY, 0,
+                        maxAllAppsProgress, LINEAR);
+                stateAnimationController.setPlayFraction(stateProgress);
+            }
+        }));
+        intro.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mCircle.setAlpha(0);
+                mGradient.setAlpha(0);
+            }
+        });
+        mAnimation.play(intro);
+
+        ValueAnimator closeAllApps = ValueAnimator.ofFloat(maxAllAppsProgress, 0f);
+        closeAllApps.addUpdateListener(valueAnimator -> {
+            stateAnimationController.setPlayFraction((float) valueAnimator.getAnimatedValue());
+        });
+        closeAllApps.setInterpolator(FAST_OUT_SLOW_IN);
+        closeAllApps.setStartDelay(introDuration);
+        closeAllApps.setDuration(250);
+        mAnimation.play(closeAllApps);
+
+        mAnimation.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mAnimation = null;
+                stateAnimationController.dispatchOnCancel();
+                handleClose(false);
+            }
+        });
+        mAnimation.start();
+    }
+
+    private void init(Launcher launcher) {
+        mLauncher = launcher;
+
+        int accentColor = Themes.getColorAccent(launcher);
+        mGradient = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM,
+                Themes.getAttrBoolean(launcher, R.attr.isMainColorDark)
+                        ? new int[] {0xB3FFFFFF, 0x00FFFFFF}
+                        : new int[] {ColorUtils.setAlphaComponent(accentColor, 127),
+                                ColorUtils.setAlphaComponent(accentColor, 0)});
+        float r = mWidthPx / 2f;
+        mGradient.setCornerRadii(new float[] {r, r, r, r, 0, 0, 0, 0});
+
+        int top = mMaxHeightPx - mCircleSizePx + mPaddingPx;
+        mCircle.setBounds(mPaddingPx, top, mPaddingPx + mCircleSizePx, top + mCircleSizePx);
+        mGradient.setBounds(0, mMaxHeightPx - mCircleSizePx, mWidthPx, mMaxHeightPx);
+
+        DeviceProfile grid = launcher.getDeviceProfile();
+        DragLayer.LayoutParams lp = new DragLayer.LayoutParams(mWidthPx, mMaxHeightPx);
+        lp.ignoreInsets = true;
+        lp.leftMargin = (grid.widthPx - mWidthPx) / 2;
+        lp.topMargin = grid.heightPx - grid.hotseatBarSizePx - mMaxHeightPx;
+        setLayoutParams(lp);
+    }
+
+    /**
+     * Shows the All Apps education view and plays the animation.
+     */
+    public static void show(Launcher launcher) {
+        final DragLayer dragLayer = launcher.getDragLayer();
+        ViewGroup parent = (ViewGroup) dragLayer.getParent();
+        AllAppsEduView view = launcher.getViewCache().getView(R.layout.all_apps_edu_view,
+                launcher, parent);
+        view.init(launcher);
+        launcher.getDragLayer().addView(view);
+        view.requestLayout();
+        view.playAnimation();
+    }
+}
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index f1ea6bb..9d70316 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -92,4 +92,10 @@
     <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>
+
+    <!-- All Apps Education tutorial -->
+    <dimen name="swipe_edu_padding">8dp</dimen>
+    <dimen name="swipe_edu_circle_size">64dp</dimen>
+    <dimen name="swipe_edu_width">80dp</dimen>
+    <dimen name="swipe_edu_max_height">184dp</dimen>
 </resources>
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
index 2d8bba2..7e8222c 100644
--- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -15,20 +15,27 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
+import static com.android.launcher3.AbstractFloatingView.getOpenView;
 import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.HINT_STATE;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import android.content.SharedPreferences;
 
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.views.AllAppsEduView;
 
 /**
  * Extends {@link OnboardingPrefs} for quickstep-specific onboarding data.
@@ -92,5 +99,51 @@
                 }
             });
         }
+
+        if (SysUINavigationMode.getMode(launcher) == NO_BUTTON
+                && FeatureFlags.ENABLE_ALL_APPS_EDU.get()) {
+            stateManager.addStateListener(new StateListener<LauncherState>() {
+                private static final int MAX_NUM_SWIPES_TO_TRIGGER_EDU = 3;
+
+                // Counts the number of consecutive swipes on nav bar without moving screens.
+                private int mCount = 0;
+                private boolean mShouldIncreaseCount;
+
+                @Override
+                public void onStateTransitionStart(LauncherState toState) {
+                    if (toState == NORMAL) {
+                        return;
+                    }
+                    mShouldIncreaseCount = toState == HINT_STATE
+                            && launcher.getWorkspace().getNextPage() == Workspace.DEFAULT_PAGE;
+                }
+
+                @Override
+                public void onStateTransitionComplete(LauncherState finalState) {
+                    if (finalState == NORMAL) {
+                        if (mCount == MAX_NUM_SWIPES_TO_TRIGGER_EDU) {
+                            if (getOpenView(mLauncher, TYPE_ALL_APPS_EDU) == null) {
+                                AllAppsEduView.show(launcher);
+                            }
+                            mCount = 0;
+                        }
+                        return;
+                    }
+
+                    if (mShouldIncreaseCount && finalState == HINT_STATE) {
+                        mCount++;
+                    } else {
+                        mCount = 0;
+                    }
+
+                    if (finalState == ALL_APPS) {
+                        AllAppsEduView view = getOpenView(mLauncher, TYPE_ALL_APPS_EDU);
+                        if (view != null) {
+                            view.close(false);
+                        }
+                    }
+                }
+            });
+        }
     }
 }
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index bed8278..1aa3144 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -59,6 +59,7 @@
             TYPE_DISCOVERY_BOUNCE,
             TYPE_SNACKBAR,
             TYPE_LISTENER,
+            TYPE_ALL_APPS_EDU,
 
             TYPE_TASK_MENU,
             TYPE_OPTIONS_POPUP
@@ -74,25 +75,28 @@
     public static final int TYPE_DISCOVERY_BOUNCE = 1 << 6;
     public static final int TYPE_SNACKBAR = 1 << 7;
     public static final int TYPE_LISTENER = 1 << 8;
+    public static final int TYPE_ALL_APPS_EDU = 1 << 9;
 
     // Popups related to quickstep UI
-    public static final int TYPE_TASK_MENU = 1 << 9;
-    public static final int TYPE_OPTIONS_POPUP = 1 << 10;
+    public static final int TYPE_TASK_MENU = 1 << 10;
+    public static final int TYPE_OPTIONS_POPUP = 1 << 11;
 
     public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
             | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
             | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU
-            | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER;
+            | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU;
 
     // Type of popups which should be kept open during launcher rebind
     public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
-            | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE;
+            | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
+            | TYPE_ALL_APPS_EDU;
 
     // Usually we show the back button when a floating view is open. Instead, hide for these types.
     public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
             | TYPE_SNACKBAR | TYPE_WIDGET_RESIZE_FRAME | TYPE_LISTENER;
 
-    public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER;
+    public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER
+            & ~TYPE_ALL_APPS_EDU;
 
     // These view all have particular operation associated with swipe down interaction.
     public static final int TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW = TYPE_WIDGETS_BOTTOM_SHEET |
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 6919339..869dbbc 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -164,6 +164,10 @@
             "ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", false,
             "Always use hardware optimization for folder animations.");
 
+    public static final BooleanFlag ENABLE_ALL_APPS_EDU = getDebugFlag(
+            "ENABLE_ALL_APPS_EDU", true,
+            "Shows user a tutorial on how to get to All Apps after X amount of attempts.");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {