Bottom user education view shown in work tab

Introduced a BottomUserEducationView for similar use case.

Screenshot: https://hsv.googleplex.com/4856820942241792

BUG=69963630

Change-Id: Ia818ee44fa5ce97ad1778f33e6a9a3a36cea5017
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 26024e5..2c6629d 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -42,7 +42,8 @@
             TYPE_WIDGETS_BOTTOM_SHEET,
             TYPE_WIDGET_RESIZE_FRAME,
             TYPE_WIDGETS_FULL_SHEET,
-            TYPE_QUICKSTEP_PREVIEW
+            TYPE_QUICKSTEP_PREVIEW,
+            TYPE_ON_BOARD_POPUP
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface FloatingViewType {}
@@ -52,10 +53,11 @@
     public static final int TYPE_WIDGET_RESIZE_FRAME = 1 << 3;
     public static final int TYPE_WIDGETS_FULL_SHEET = 1 << 4;
     public static final int TYPE_QUICKSTEP_PREVIEW = 1 << 5;
+    public static final int TYPE_ON_BOARD_POPUP = 1 << 6;
 
     public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
             | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
-            | TYPE_QUICKSTEP_PREVIEW;
+            | TYPE_QUICKSTEP_PREVIEW | TYPE_ON_BOARD_POPUP;
 
     protected boolean mIsOpen;
 
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 9ee9514..f4026f2 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -60,6 +60,7 @@
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.TransformingTouchDelegate;
+import com.android.launcher3.views.BottomUserEducationView;
 import com.android.launcher3.views.SlidingTabStrip;
 
 import java.util.HashMap;
@@ -515,6 +516,9 @@
                 if (mAH[pos].recyclerView != null) {
                     mAH[pos].recyclerView.bindFastScrollbar();
                 }
+                if (pos == AdapterHolder.WORK) {
+                    BottomUserEducationView.showIfNeeded(mLauncher);
+                }
             }
 
             @Override
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
new file mode 100644
index 0000000..7c4529d
--- /dev/null
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2017 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.launcher3.views;
+
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Property;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.touch.SwipeDetector;
+
+/**
+ * Extension of AbstractFloatingView with common methods for sliding in from bottom
+ */
+public abstract class AbstractSlideInView extends AbstractFloatingView
+        implements SwipeDetector.Listener {
+
+    protected static Property<AbstractSlideInView, Float> TRANSLATION_SHIFT =
+            new Property<AbstractSlideInView, Float>(Float.class, "translationShift") {
+
+                @Override
+                public Float get(AbstractSlideInView view) {
+                    return view.mTranslationShift;
+                }
+
+                @Override
+                public void set(AbstractSlideInView view, Float value) {
+                    view.setTranslationShift(value);
+                }
+            };
+    protected static final float TRANSLATION_SHIFT_CLOSED = 1f;
+    protected static final float TRANSLATION_SHIFT_OPENED = 0f;
+
+    protected final Launcher mLauncher;
+    protected final SwipeDetector mSwipeDetector;
+    protected final ObjectAnimator mOpenCloseAnimator;
+
+    protected View mContent;
+    protected Interpolator mScrollInterpolator;
+
+    // range [0, 1], 0=> completely open, 1=> completely closed
+    protected float mTranslationShift = TRANSLATION_SHIFT_CLOSED;
+
+    protected boolean mNoIntercept;
+
+    public AbstractSlideInView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = Launcher.getLauncher(context);
+
+        mScrollInterpolator = Interpolators.SCROLL_CUBIC;
+        mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL);
+
+        mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this);
+        mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mSwipeDetector.finishedScrolling();
+            }
+        });
+    }
+
+    protected void setTranslationShift(float translationShift) {
+        mTranslationShift = translationShift;
+        mContent.setTranslationY(mTranslationShift * mContent.getHeight());
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (mNoIntercept) {
+            return false;
+        }
+
+        int directionsToDetectScroll = mSwipeDetector.isIdleState() ?
+                SwipeDetector.DIRECTION_NEGATIVE : 0;
+        mSwipeDetector.setDetectableScrollConditions(
+                directionsToDetectScroll, false);
+        mSwipeDetector.onTouchEvent(ev);
+        return mSwipeDetector.isDraggingOrSettling()
+                || !mLauncher.getDragLayer().isEventOverView(mContent, ev);
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        mSwipeDetector.onTouchEvent(ev);
+        if (ev.getAction() == MotionEvent.ACTION_UP && mSwipeDetector.isIdleState()) {
+            // If we got ACTION_UP without ever starting swipe, close the panel.
+            if (!mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
+                close(true);
+            }
+        }
+        return true;
+    }
+
+    /* SwipeDetector.Listener */
+
+    @Override
+    public void onDragStart(boolean start) { }
+
+    @Override
+    public boolean onDrag(float displacement, float velocity) {
+        float range = mContent.getHeight();
+        displacement = Utilities.boundToRange(displacement, 0, range);
+        setTranslationShift(displacement / range);
+        return true;
+    }
+
+    @Override
+    public void onDragEnd(float velocity, boolean fling) {
+        if ((fling && velocity > 0) || mTranslationShift > 0.5f) {
+            mScrollInterpolator = scrollInterpolatorForVelocity(velocity);
+            mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(
+                    velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift));
+            close(true);
+        } else {
+            mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(
+                    TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+            mOpenCloseAnimator.setDuration(
+                    SwipeDetector.calculateDuration(velocity, mTranslationShift))
+                    .setInterpolator(Interpolators.DEACCEL);
+            mOpenCloseAnimator.start();
+        }
+    }
+
+    protected void handleClose(boolean animate, long defaultDuration) {
+        if (mIsOpen && !animate) {
+            mOpenCloseAnimator.cancel();
+            setTranslationShift(TRANSLATION_SHIFT_CLOSED);
+            onCloseComplete();
+            return;
+        }
+        if (!mIsOpen || mOpenCloseAnimator.isRunning()) {
+            return;
+        }
+        mOpenCloseAnimator.setValues(
+                PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_CLOSED));
+        mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                onCloseComplete();
+            }
+        });
+        if (mSwipeDetector.isIdleState()) {
+            mOpenCloseAnimator
+                    .setDuration(defaultDuration)
+                    .setInterpolator(Interpolators.ACCEL);
+        } else {
+            mOpenCloseAnimator.setInterpolator(mScrollInterpolator);
+        }
+        mOpenCloseAnimator.start();
+    }
+
+    protected void onCloseComplete() {
+        mIsOpen = false;
+        mLauncher.getDragLayer().removeView(this);
+    }
+}
diff --git a/src/com/android/launcher3/views/BottomUserEducationView.java b/src/com/android/launcher3/views/BottomUserEducationView.java
new file mode 100644
index 0000000..d79d0ce
--- /dev/null
+++ b/src/com/android/launcher3/views/BottomUserEducationView.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 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.launcher3.views;
+
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.Interpolators;
+
+public class BottomUserEducationView extends AbstractSlideInView implements Insettable {
+
+    private static final String KEY_SHOWED_BOTTOM_USER_EDUCATION = "showed_bottom_user_education";
+
+    private static final int DEFAULT_CLOSE_DURATION = 200;
+
+    private final Rect mInsets = new Rect();
+
+    public BottomUserEducationView(Context context, AttributeSet attr) {
+        this(context, attr, 0);
+    }
+
+    public BottomUserEducationView(Context context, AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mContent = this;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        setTranslationShift(mTranslationShift);
+    }
+
+    @Override
+    public void logActionCommand(int command) {
+        // Since this is on-boarding popup, it is not a user controlled action.
+    }
+
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_ON_BOARD_POPUP) != 0;
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        // Extend behind left, right, and bottom insets.
+        int leftInset = insets.left - mInsets.left;
+        int rightInset = insets.right - mInsets.right;
+        int bottomInset = insets.bottom - mInsets.bottom;
+        mInsets.set(insets);
+        setPadding(getPaddingLeft() + leftInset, getPaddingTop(),
+                getPaddingRight() + rightInset, getPaddingBottom() + bottomInset);
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        handleClose(animate, DEFAULT_CLOSE_DURATION);
+        if (animate) {
+            // We animate only when the user is visible, which is a proxy for an explicit
+            // close action.
+            mLauncher.getSharedPrefs().edit()
+                    .putBoolean(KEY_SHOWED_BOTTOM_USER_EDUCATION, true).apply();
+        }
+    }
+
+    private void open(boolean animate) {
+        if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+            return;
+        }
+        mIsOpen = true;
+        if (animate) {
+            mOpenCloseAnimator.setValues(
+                    PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+            mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+            mOpenCloseAnimator.start();
+        } else {
+            setTranslationShift(TRANSLATION_SHIFT_OPENED);
+        }
+    }
+
+    public static void showIfNeeded(Launcher launcher) {
+        if (launcher.getSharedPrefs().getBoolean(KEY_SHOWED_BOTTOM_USER_EDUCATION, false)) {
+            return;
+        }
+
+        LayoutInflater layoutInflater = LayoutInflater.from(launcher);
+        BottomUserEducationView bottomUserEducationView =
+                (BottomUserEducationView) layoutInflater.inflate(
+                        R.layout.work_tab_bottom_user_education_view, launcher.getDragLayer(),
+                        false);
+        launcher.getDragLayer().addView(bottomUserEducationView);
+        bottomUserEducationView.open(true);
+    }
+}
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index e328759..fa82714 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -15,94 +15,43 @@
  */
 package com.android.launcher3.widget;
 
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.graphics.Point;
 import android.util.AttributeSet;
-import android.util.Property;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnLongClickListener;
-import android.view.animation.Interpolator;
 import android.widget.Toast;
 
-import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.graphics.GradientView;
-import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.AbstractSlideInView;
 
 /**
  * Base class for various widgets popup
  */
-abstract class BaseWidgetSheet extends AbstractFloatingView
-        implements OnClickListener, OnLongClickListener, DragSource, SwipeDetector.Listener {
+abstract class BaseWidgetSheet extends AbstractSlideInView
+        implements OnClickListener, OnLongClickListener, DragSource {
 
 
-    protected static Property<BaseWidgetSheet, Float> TRANSLATION_SHIFT =
-            new Property<BaseWidgetSheet, Float>(Float.class, "translationShift") {
-
-                @Override
-                public Float get(BaseWidgetSheet view) {
-                    return view.mTranslationShift;
-                }
-
-                @Override
-                public void set(BaseWidgetSheet view, Float value) {
-                    view.setTranslationShift(value);
-                }
-            };
-    protected static final float TRANSLATION_SHIFT_CLOSED = 1f;
-    protected static final float TRANSLATION_SHIFT_OPENED = 0f;
-
     /* Touch handling related member variables. */
     private Toast mWidgetInstructionToast;
 
-    protected final Launcher mLauncher;
-    protected final SwipeDetector mSwipeDetector;
-    protected final ObjectAnimator mOpenCloseAnimator;
-
-    protected View mContent;
     protected GradientView mGradientView;
-    protected Interpolator mScrollInterpolator;
-
-    // range [0, 1], 0=> completely open, 1=> completely closed
-    protected float mTranslationShift = TRANSLATION_SHIFT_CLOSED;
-
-    protected boolean mNoIntercept;
 
     public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mLauncher = Launcher.getLauncher(context);
-
-        mScrollInterpolator = Interpolators.SCROLL_CUBIC;
-        mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL);
-
-        mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this);
-        mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mSwipeDetector.finishedScrolling();
-            }
-        });
     }
 
     @Override
@@ -130,9 +79,8 @@
     }
 
     protected void setTranslationShift(float translationShift) {
-        mTranslationShift = translationShift;
+        super.setTranslationShift(translationShift);
         mGradientView.setAlpha(1 - mTranslationShift);
-        mContent.setTranslationY(mTranslationShift * mContent.getHeight());
     }
 
     private boolean beginDraggingWidget(WidgetCell v) {
@@ -163,94 +111,8 @@
     public void onDropCompleted(View target, DragObject d, boolean success) { }
 
 
-    @Override
-    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_UP && !mNoIntercept) {
-            // If we got ACTION_UP without ever returning true on intercept,
-            // the user never started dragging the bottom sheet.
-            if (!mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
-                close(true);
-                return false;
-            }
-        }
-
-        if (mNoIntercept) {
-            return false;
-        }
-
-        int directionsToDetectScroll = mSwipeDetector.isIdleState() ?
-                SwipeDetector.DIRECTION_NEGATIVE : 0;
-        mSwipeDetector.setDetectableScrollConditions(
-                directionsToDetectScroll, false);
-        mSwipeDetector.onTouchEvent(ev);
-        return mSwipeDetector.isDraggingOrSettling();
-    }
-
-    @Override
-    public boolean onControllerTouchEvent(MotionEvent ev) {
-        return mSwipeDetector.onTouchEvent(ev);
-    }
-
-    /* SwipeDetector.Listener */
-
-    @Override
-    public void onDragStart(boolean start) { }
-
-    @Override
-    public boolean onDrag(float displacement, float velocity) {
-        float range = mContent.getHeight();
-        displacement = Utilities.boundToRange(displacement, 0, range);
-        setTranslationShift(displacement / range);
-        return true;
-    }
-
-    @Override
-    public void onDragEnd(float velocity, boolean fling) {
-        if ((fling && velocity > 0) || mTranslationShift > 0.5f) {
-            mScrollInterpolator = scrollInterpolatorForVelocity(velocity);
-            mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(
-                    velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift));
-            close(true);
-        } else {
-            mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(
-                    TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
-            mOpenCloseAnimator.setDuration(
-                    SwipeDetector.calculateDuration(velocity, mTranslationShift))
-                    .setInterpolator(Interpolators.DEACCEL);
-            mOpenCloseAnimator.start();
-        }
-    }
-
-    protected void handleClose(boolean animate, long defaultDuration) {
-        if (!mIsOpen || mOpenCloseAnimator.isRunning()) {
-            return;
-        }
-        if (animate) {
-            mOpenCloseAnimator.setValues(
-                    PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_CLOSED));
-            mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    onCloseComplete();
-                }
-            });
-            if (mSwipeDetector.isIdleState()) {
-                mOpenCloseAnimator
-                        .setDuration(defaultDuration)
-                        .setInterpolator(Interpolators.ACCEL);
-            } else {
-                mOpenCloseAnimator.setInterpolator(mScrollInterpolator);
-            }
-            mOpenCloseAnimator.start();
-        } else {
-            setTranslationShift(TRANSLATION_SHIFT_CLOSED);
-            onCloseComplete();
-        }
-    }
-
     protected void onCloseComplete() {
-        mIsOpen = false;
-        mLauncher.getDragLayer().removeView(this);
+        super.onCloseComplete();
         mLauncher.getSystemUiController().updateUiState(
                 SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, 0);
     }