Merge "Swipe up on nav bar to go home from -1 and widgets" into ub-launcher3-qt-dev
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 673beff..21ddfc0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -15,74 +15,131 @@
  */
 package com.android.launcher3.uioverrides.touchcontrollers;
 
+import static android.view.View.TRANSLATION_X;
+
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 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.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Command;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.util.TouchController;
 import com.android.quickstep.views.RecentsView;
 
 /**
- * Handles swiping up on the nav bar to go home from overview or all apps.
+ * Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps.
  */
-public class NavBarToHomeTouchController extends AbstractStateChangeTouchController {
+public class NavBarToHomeTouchController implements TouchController, SwipeDetector.Listener {
 
     private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3;
 
+    private final Launcher mLauncher;
+    private final SwipeDetector mSwipeDetector;
+    private final float mPullbackDistance;
+
+    private boolean mNoIntercept;
+    private LauncherState mStartState;
+    private LauncherState mEndState = NORMAL;
+    private AnimatorPlaybackController mCurrentAnimation;
+
     public NavBarToHomeTouchController(Launcher launcher) {
-        super(launcher, SwipeDetector.VERTICAL);
+        mLauncher = launcher;
+        mSwipeDetector = new SwipeDetector(mLauncher, this, SwipeDetector.VERTICAL);
+        mPullbackDistance = mLauncher.getResources().getDimension(R.dimen.home_pullback_distance);
     }
 
     @Override
-    protected boolean canInterceptTouch(MotionEvent ev) {
-        boolean cameFromNavBar = (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0;
-        return cameFromNavBar && (mLauncher.isInState(OVERVIEW) || mLauncher.isInState(ALL_APPS));
-    }
-
-    @Override
-    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
-        return isDragTowardPositive ? NORMAL : fromState;
-    }
-
-    @Override
-    protected float initCurrentAnimation(int animComponents) {
-        long accuracy = (long) (getShiftRange() * 2);
-        final AnimatorSet anim;
-        if (mFromState == OVERVIEW) {
-            anim = new AnimatorSet();
-            RecentsView recentsView = mLauncher.getOverviewPanel();
-            float pullbackDistance = recentsView.getPaddingStart() / 2;
-            if (!recentsView.isRtl()) {
-                pullbackDistance = -pullbackDistance;
+    public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mStartState = mLauncher.getStateManager().getState();
+            mNoIntercept = !canInterceptTouch(ev);
+            if (mNoIntercept) {
+                return false;
             }
-            anim.play(ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_X, pullbackDistance));
-            anim.setInterpolator(PULLBACK_INTERPOLATOR);
-        } else { // if (mFromState == ALL_APPS)
+            mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false);
+        }
+
+        if (mNoIntercept) {
+            return false;
+        }
+
+        onControllerTouchEvent(ev);
+        return mSwipeDetector.isDraggingOrSettling();
+    }
+
+    private boolean canInterceptTouch(MotionEvent ev) {
+        boolean cameFromNavBar = (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0;
+        if (!cameFromNavBar) {
+            return false;
+        }
+        if (mStartState == OVERVIEW || mStartState == ALL_APPS) {
+            return true;
+        }
+        if (!mLauncher.hasWindowFocus()) {
+            return true;
+        }
+        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public final boolean onControllerTouchEvent(MotionEvent ev) {
+        return mSwipeDetector.onTouchEvent(ev);
+    }
+
+    private float getShiftRange() {
+        return mLauncher.getDeviceProfile().heightPx;
+    }
+
+    @Override
+    public void onDragStart(boolean start) {
+        initCurrentAnimation();
+    }
+
+    private void initCurrentAnimation() {
+        long accuracy = (long) (getShiftRange() * 2);
+        final AnimatorSet anim = new AnimatorSet();
+        if (mStartState == OVERVIEW) {
+            RecentsView recentsView = mLauncher.getOverviewPanel();
+            float pullbackDist = mPullbackDistance;
+            if (!recentsView.isRtl()) {
+                pullbackDist = -pullbackDist;
+            }
+            Animator pullback = ObjectAnimator.ofFloat(recentsView, TRANSLATION_X, pullbackDist);
+            pullback.setInterpolator(PULLBACK_INTERPOLATOR);
+            anim.play(pullback);
+        } else if (mStartState == ALL_APPS) {
             AnimatorSetBuilder builder = new AnimatorSetBuilder();
             AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
-            final float pullbackDistance = mLauncher.getDeviceProfile().allAppsIconSizePx / 2;
             Animator allAppsProgress = ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS,
-                    -pullbackDistance / allAppsController.getShiftRange());
+                    -mPullbackDistance / allAppsController.getShiftRange());
             allAppsProgress.setInterpolator(PULLBACK_INTERPOLATOR);
             builder.play(allAppsProgress);
             // Slightly fade out all apps content to further distinguish from scrolling.
@@ -90,52 +147,79 @@
                     .mapToProgress(PULLBACK_INTERPOLATOR, 0, 0.5f));
             AnimationConfig config = new AnimationConfig();
             config.duration = accuracy;
-            allAppsController.setAlphas(mToState.getVisibleElements(mLauncher), config, builder);
-            anim = builder.build();
+            allAppsController.setAlphas(mEndState.getVisibleElements(mLauncher), config, builder);
+            anim.play(builder.build());
+        }
+        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher);
+        if (topView != null) {
+            Animator hintCloseAnim = topView.createHintCloseAnim(mPullbackDistance);
+            if (hintCloseAnim != null) {
+                hintCloseAnim.setInterpolator(PULLBACK_INTERPOLATOR);
+                anim.play(hintCloseAnim);
+            }
         }
         anim.setDuration(accuracy);
         mCurrentAnimation = AnimatorPlaybackController.wrap(anim, accuracy, this::clearState);
-        return -1 / getShiftRange();
+    }
+
+    private void clearState() {
+        mCurrentAnimation = null;
+        mSwipeDetector.finishedScrolling();
+        mSwipeDetector.setDetectableScrollConditions(0, false);
     }
 
     @Override
-    public void onDragStart(boolean start) {
-        super.onDragStart(start);
-        mStartContainerType = LauncherLogProto.ContainerType.NAVBAR;
+    public boolean onDrag(float displacement) {
+        // Only allow swipe up.
+        displacement = Math.min(0, displacement);
+        float progress = Utilities.getProgress(displacement, 0, getShiftRange());
+        mCurrentAnimation.setPlayFraction(progress);
+        return true;
     }
 
     @Override
     public void onDragEnd(float velocity, boolean fling) {
         final int logAction = fling ? Touch.FLING : Touch.SWIPE;
-        float interpolatedProgress = PULLBACK_INTERPOLATOR.getInterpolation(
-                mCurrentAnimation.getProgressFraction());
-        if (interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS || velocity < 0 && fling) {
-            mLauncher.getStateManager().goToState(mToState, true,
-                    () -> onSwipeInteractionCompleted(mToState, logAction));
+        float progress = mCurrentAnimation.getProgressFraction();
+        float interpolatedProgress = PULLBACK_INTERPOLATOR.getInterpolation(progress);
+        boolean success = interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS
+                || (velocity < 0 && fling);
+        if (success) {
+            mLauncher.getStateManager().goToState(mEndState, true,
+                    () -> onSwipeInteractionCompleted(mEndState));
+            if (mStartState != mEndState) {
+                logStateChange(mStartState.containerType, logAction);
+            }
+            AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(mLauncher);
+            if (topOpenView != null) {
+                AbstractFloatingView.closeAllOpenViews(mLauncher);
+                logStateChange(topOpenView.getLogContainerType(), logAction);
+            }
         } else {
             // Quickly return to the state we came from (we didn't move far).
-            AnimatorPlaybackController anim = mLauncher.getStateManager()
-                    .createAnimationToNewWorkspace(mFromState, 80);
-            anim.setEndAction(() -> onSwipeInteractionCompleted(mFromState, logAction));
-            anim.start();
+            ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
+            anim.setFloatValues(progress, 0);
+            anim.addListener(new AnimationSuccessListener() {
+                @Override
+                public void onAnimationSuccess(Animator animator) {
+                    onSwipeInteractionCompleted(mStartState);
+                }
+            });
+            anim.setDuration(80).start();
         }
-        mCurrentAnimation.dispatchOnCancel();
     }
 
-    @Override
-    protected int getDirectionForLog() {
-        return LauncherLogProto.Action.Direction.UP;
+    private void onSwipeInteractionCompleted(LauncherState targetState) {
+        clearState();
+        mLauncher.getStateManager().goToState(targetState, false /* animated */);
     }
 
-    @Override
-    protected boolean goingBetweenNormalAndOverview(LauncherState fromState,
-            LauncherState toState) {
-        // We don't want to create an atomic animation to/from overview.
-        return false;
-    }
-
-    @Override
-    protected int getLogContainerTypeForNormalState() {
-        return LauncherLogProto.ContainerType.NAVBAR;
+    private void logStateChange(int startContainerType, int logAction) {
+        mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
+                LauncherLogProto.Action.Direction.UP,
+                LauncherLogProto.ContainerType.NAVBAR,
+                startContainerType,
+                mEndState.containerType,
+                mLauncher.getWorkspace().getCurrentPage());
     }
 }
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 6cbadda..6ec3bf6 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -65,4 +65,7 @@
     <!-- Assistant Gestures -->
     <dimen name="gestures_assistant_size">28dp</dimen>
     <dimen name="gestures_assistant_drag_threshold">70dp</dimen>
+
+    <!-- Distance to move elements when swiping up to go home from launcher -->
+    <dimen name="home_pullback_distance">28dp</dimen>
 </resources>
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 599a353..e7d7a69 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -19,9 +19,11 @@
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 
+import android.animation.Animator;
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.util.AttributeSet;
@@ -30,7 +32,11 @@
 import android.view.View;
 import android.widget.LinearLayout;
 
+import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.BaseDragLayer;
@@ -38,8 +44,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-import androidx.annotation.IntDef;
-
 /**
  * Base class for a View which shows a floating UI on top of the launcher UI.
  */
@@ -124,8 +128,20 @@
 
     protected abstract void handleClose(boolean animate);
 
+    /**
+     * Creates a user-controlled animation to hint that the view will be closed if completed.
+     * @param distanceToMove The max distance that elements should move from their starting point.
+     */
+    public @Nullable Animator createHintCloseAnim(float distanceToMove) {
+        return null;
+    }
+
     public abstract void logActionCommand(int command);
 
+    public int getLogContainerType() {
+        return ContainerType.DEFAULT_CONTAINERTYPE;
+    }
+
     public final boolean isOpen() {
         return mIsOpen;
     }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index bcddd03..2ce6634 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -1406,7 +1406,12 @@
     @Override
     public void logActionCommand(int command) {
         mLauncher.getUserEventDispatcher().logActionCommand(
-                command, getFolderIcon(), ContainerType.FOLDER);
+                command, getFolderIcon(), getLogContainerType());
+    }
+
+    @Override
+    public int getLogContainerType() {
+        return ContainerType.FOLDER;
     }
 
     @Override
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 080a0cb..593dbd4 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -157,7 +157,12 @@
     @Override
     public void logActionCommand(int command) {
         mLauncher.getUserEventDispatcher().logActionCommand(
-                command, mOriginalIcon, ContainerType.DEEPSHORTCUTS);
+                command, mOriginalIcon, getLogContainerType());
+    }
+
+    @Override
+    public int getLogContainerType() {
+        return ContainerType.DEEPSHORTCUTS;
     }
 
     public OnClickListener getItemClickListener() {
diff --git a/src/com/android/launcher3/views/BottomUserEducationView.java b/src/com/android/launcher3/views/BottomUserEducationView.java
index a291fc6..bdc69af 100644
--- a/src/com/android/launcher3/views/BottomUserEducationView.java
+++ b/src/com/android/launcher3/views/BottomUserEducationView.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.views;
 
+import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
+
 import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.graphics.Rect;
@@ -28,8 +30,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
-
-import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 public class BottomUserEducationView extends AbstractSlideInView implements Insettable {
 
@@ -71,6 +72,11 @@
     }
 
     @Override
+    public int getLogContainerType() {
+        return ContainerType.TIP;
+    }
+
+    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_ON_BOARD_POPUP) != 0;
     }
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index df82661..72cddc7 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -162,11 +162,16 @@
 
     @Override
     public final void logActionCommand(int command) {
-        Target target = newContainerTarget(ContainerType.WIDGETS);
+        Target target = newContainerTarget(getLogContainerType());
         target.cardinality = getElementsRowCount();
         mLauncher.getUserEventDispatcher().logActionCommand(command, target);
     }
 
+    @Override
+    public int getLogContainerType() {
+        return ContainerType.WIDGETS;
+    }
+
     protected abstract int getElementsRowCount();
 
     protected SystemUiController getSystemUiController() {
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 05368fa..3e2c0ae 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -16,10 +16,13 @@
 
 package com.android.launcher3.widget;
 
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.util.IntProperty;
 import android.util.Pair;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -27,6 +30,8 @@
 import android.view.ViewGroup;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.Insettable;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
@@ -43,6 +48,20 @@
  */
 public class WidgetsBottomSheet extends BaseWidgetSheet implements Insettable {
 
+    private static final IntProperty<View> PADDING_BOTTOM =
+            new IntProperty<View>("paddingBottom") {
+                @Override
+                public void setValue(View view, int paddingBottom) {
+                    view.setPadding(view.getPaddingLeft(), view.getPaddingTop(),
+                            view.getPaddingRight(), paddingBottom);
+                }
+
+                @Override
+                public Integer get(View view) {
+                    return view.getPaddingBottom();
+                }
+            };
+
     private static final int DEFAULT_CLOSE_DURATION = 200;
     private ItemInfo mOriginalItemInfo;
     private Rect mInsets;
@@ -158,8 +177,7 @@
         int rightInset = insets.right - mInsets.right;
         int bottomInset = insets.bottom - mInsets.bottom;
         mInsets.set(insets);
-        setPadding(getPaddingLeft() + leftInset, getPaddingTop(),
-                getPaddingRight() + rightInset, getPaddingBottom() + bottomInset);
+        setPadding(leftInset, getPaddingTop(), rightInset, bottomInset);
     }
 
     @Override
@@ -172,4 +190,10 @@
         return Pair.create(findViewById(R.id.title),  getContext().getString(
                 mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed));
     }
+
+    @Nullable
+    @Override
+    public Animator createHintCloseAnim(float distanceToMove) {
+        return ObjectAnimator.ofInt(this, PADDING_BOTTOM, (int) (distanceToMove + mInsets.bottom));
+    }
 }
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index ec06d1e..521f511 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -17,6 +17,8 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.graphics.Rect;
@@ -27,6 +29,9 @@
 import android.view.View;
 import android.view.animation.AnimationUtils;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
@@ -35,8 +40,6 @@
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.TopRoundedCornerView;
 
-import androidx.annotation.VisibleForTesting;
-
 /**
  * Popup for showing the full list of available widgets
  */
@@ -235,4 +238,13 @@
     protected int getElementsRowCount() {
         return mAdapter.getItemCount();
     }
+
+    @Nullable
+    @Override
+    public Animator createHintCloseAnim(float distanceToMove) {
+        AnimatorSet anim = new AnimatorSet();
+        anim.play(ObjectAnimator.ofFloat(mRecyclerView, TRANSLATION_Y, -distanceToMove));
+        anim.play(ObjectAnimator.ofFloat(mRecyclerView, ALPHA, 0.5f));
+        return anim;
+    }
 }