Introducing Clear-all button on Overview

It’s an initial implementation, i.e. no fancy effects.
It shares a parent (LauncherRecentsViewContainer) with RecentsView.
The button is centered in clear_all_button_container, which gets
positioned programmatically to the right of the last task. (RTL polish
will be a separate CL as well).

Bug: 72222505
Change-Id: Ia912908a93a30c2f51450ccf0f97c7495e7916d5
Test: Manual
diff --git a/quickstep/res/layout/fallback_recents_activity.xml b/quickstep/res/layout/fallback_recents_activity.xml
index 22f8b55..7ecab32 100644
--- a/quickstep/res/layout/fallback_recents_activity.xml
+++ b/quickstep/res/layout/fallback_recents_activity.xml
@@ -20,13 +20,23 @@
     android:layout_height="match_parent"
     android:fitsSystemWindows="true">
 
-    <com.android.quickstep.fallback.FallbackRecentsView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/overview_panel"
+    <com.android.quickstep.views.RecentsViewContainer
+        android:id="@+id/overview_panel_container"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:clipChildren="false"
-        android:clipToPadding="false"
-        android:theme="@style/HomeScreenElementTheme" />
+    >
+        <include layout="@layout/overview_clear_all_button"/>
 
-</com.android.quickstep.fallback.RecentsRootView>
\ No newline at end of file
+        <com.android.quickstep.fallback.FallbackRecentsView
+            android:id="@id/overview_panel"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:focusableInTouchMode="true"
+            android:theme="@style/HomeScreenElementTheme"
+        >
+
+        </com.android.quickstep.fallback.FallbackRecentsView>
+    </com.android.quickstep.views.RecentsViewContainer>
+</com.android.quickstep.fallback.RecentsRootView>
diff --git a/quickstep/res/layout/overview_clear_all_button.xml b/quickstep/res/layout/overview_clear_all_button.xml
new file mode 100644
index 0000000..8632f8b
--- /dev/null
+++ b/quickstep/res/layout/overview_clear_all_button.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/clear_all_button"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="end|top"
+    android:fontFamily="sans-serif-medium"
+    android:text="@string/recents_clear_all"
+    android:textColor="?attr/workspaceTextColor"
+    android:background="?android:attr/selectableItemBackground"
+    android:textSize="14sp"
+/>
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index 89e0571..6102c38 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -14,14 +14,23 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.quickstep.views.LauncherRecentsView
+<com.android.quickstep.views.RecentsViewContainer
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:theme="@style/HomeScreenElementTheme"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:clipChildren="false"
-    android:clipToPadding="false"
     android:visibility="invisible"
-    android:focusableInTouchMode="true" >
+>
+    <include layout="@layout/overview_clear_all_button"/>
 
-</com.android.quickstep.views.LauncherRecentsView>
\ No newline at end of file
+    <com.android.quickstep.views.LauncherRecentsView
+        android:id="@id/overview_panel"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:focusableInTouchMode="true"
+        android:theme="@style/HomeScreenElementTheme"
+    >
+
+    </com.android.quickstep.views.LauncherRecentsView>
+</com.android.quickstep.views.RecentsViewContainer>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index c741913..0199cd9 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -45,4 +45,7 @@
     <!-- Copied from framework resource:
        docked_stack_divider_thickness - 2 * docked_stack_divider_insets -->
     <dimen name="multi_window_task_divider_size">10dp</dimen>
+
+    <!-- Width of the space behind the last task in Overview. In the center of it, there is "Clear all" button. -->
+    <dimen name="clear_all_container_width">168dp</dimen>
 </resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index bafa294..34cc0b7 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -35,4 +35,7 @@
 
     <!-- Content description for the recent apps's accessibility option that closes it. [CHAR LIMIT=NONE] -->
     <string name="accessibility_close_task">Close</string>
+
+    <!-- Recents: Title of a button that clears the task list, i.e. closes all tasks. [CHAR LIMIT=30] -->
+    <string name="recents_clear_all">Clear all</string>
 </resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 124ec20..49d4931 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -20,7 +20,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
 import static com.android.quickstep.views.RecentsView.ADJACENT_SCALE;
-import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
+import static com.android.quickstep.views.RecentsViewContainer.CONTENT_ALPHA;
 
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
@@ -33,21 +33,24 @@
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.quickstep.views.LauncherRecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
 
 @TargetApi(Build.VERSION_CODES.O)
 public class RecentsViewStateController implements StateHandler {
 
     private final Launcher mLauncher;
     private final LauncherRecentsView mRecentsView;
+    private final RecentsViewContainer mRecentsViewContainer;
 
     public RecentsViewStateController(Launcher launcher) {
         mLauncher = launcher;
         mRecentsView = launcher.getOverviewPanel();
+        mRecentsViewContainer = launcher.getOverviewPanelContainer();
     }
 
     @Override
     public void setState(LauncherState state) {
-        mRecentsView.setContentAlpha(state.overviewUi ? 1 : 0);
+        mRecentsViewContainer.setContentAlpha(state.overviewUi ? 1 : 0);
         float[] scaleTranslationYFactor = state.getOverviewScaleAndTranslationYFactor(mLauncher);
         mRecentsView.setAdjacentScale(scaleTranslationYFactor[0]);
         mRecentsView.setTranslationYFactor(scaleTranslationYFactor[1]);
@@ -66,7 +69,7 @@
                 builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
         setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR, scaleTranslationYFactor[1],
                 builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
-        setter.setFloat(mRecentsView, CONTENT_ALPHA, toState.overviewUi ? 1 : 0,
+        setter.setFloat(mRecentsViewContainer, CONTENT_ALPHA, toState.overviewUi ? 1 : 0,
                 AGGRESSIVE_EASE_IN_OUT);
 
         if (!toState.overviewUi) {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 597e333..c602392 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -87,21 +87,10 @@
 public abstract class RecentsView<T extends BaseActivity>
         extends PagedView implements OnSharedPreferenceChangeListener, Insettable {
 
+    public static final boolean DEBUG_SHOW_CLEAR_ALL_BUTTON = false;
+
     private final Rect mTempRect = new Rect();
 
-    public static final FloatProperty<RecentsView> CONTENT_ALPHA =
-            new FloatProperty<RecentsView>("contentAlpha") {
-        @Override
-        public void setValue(RecentsView recentsView, float v) {
-            recentsView.setContentAlpha(v);
-        }
-
-        @Override
-        public Float get(RecentsView recentsView) {
-            return recentsView.mContentAlpha;
-        }
-    };
-
     public static final FloatProperty<RecentsView> ADJACENT_SCALE =
             new FloatProperty<RecentsView>("adjacentScale") {
         @Override
@@ -180,6 +169,8 @@
     // Keeps track of task views whose visual state should not be reset
     private ArraySet<TaskView> mIgnoreResetTaskViews = new ArraySet<>();
 
+    private RecentsViewContainer mContainerView;
+
     // Variables for empty state
     private final Drawable mEmptyIcon;
     private final CharSequence mEmptyMessage;
@@ -320,12 +311,18 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
-        super.onTouchEvent(ev);
+        if (DEBUG_SHOW_CLEAR_ALL_BUTTON && mTouchState == TOUCH_STATE_REST && mScroller.isFinished()
+                && getChildCount() != 0
+                && ev.getX() > getChildAt(getChildCount() - 1).getRight() - getScrollX()) {
+            // If nothing is in motion, allow events to the right of the last task to go to the
+            // Clear All button.
+            return false;
+        }
+
         if (ev.getAction() == MotionEvent.ACTION_UP && mShowEmptyMessage) {
             onAllTasksRemoved();
         }
-        // Do not let touch escape to siblings below this view.
-        return true;
+        return super.onTouchEvent(ev);
     }
 
     private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
@@ -424,6 +421,10 @@
 
     protected abstract void getTaskSize(DeviceProfile dp, Rect outRect);
 
+    public void getTaskSize(Rect outRect) {
+        getTaskSize(mActivity.getDeviceProfile(), outRect);
+    }
+
     @Override
     protected boolean computeScrollHelper() {
         boolean scrolling = super.computeScrollHelper();
@@ -530,6 +531,7 @@
 
         unloadVisibleTaskData();
         setCurrentPage(0);
+        scrollTo(0, 0);
 
         OverviewCallbacks.get(getContext()).onResetOverview();
     }
@@ -844,11 +846,11 @@
         snapToPageRelative(1);
     }
 
-    public void setContentAlpha(float alpha) {
-        if (mContentAlpha == alpha) {
-            return;
-        }
+    public float getContentAlpha() {
+        return mContentAlpha;
+    }
 
+    public void setContentAlpha(float alpha) {
         mContentAlpha = alpha;
         for (int i = getChildCount() - 1; i >= 0; i--) {
             TaskView child = getPageAt(i);
@@ -860,8 +862,6 @@
         int alphaInt = Math.round(alpha * 255);
         mEmptyMessagePaint.setAlpha(alphaInt);
         mEmptyIcon.setAlpha(alphaInt);
-
-        setVisibility(alpha > 0 ? VISIBLE : GONE);
     }
 
     public void setAdjacentScale(float adjacentScale) {
@@ -929,6 +929,9 @@
         mShowEmptyMessage = isEmpty;
         updateEmptyStateUi(hasSizeChanged);
         invalidate();
+        if (mContainerView != null) {
+            mContainerView.onEmptyStateChanged(!DEBUG_SHOW_CLEAR_ALL_BUTTON || mShowEmptyMessage);
+        }
     }
 
     @Override
@@ -1095,4 +1098,30 @@
     protected String getCurrentPageDescription() {
         return "";
     }
+
+    public void dismissAllTasks() {
+        for (int i = 0; i < getChildCount(); ++i) {
+            Task task = getPageAt(i).getTask();
+            if (task != null) {
+                ActivityManagerWrapper.getInstance().removeTask(task.key.id);
+            }
+        }
+        onAllTasksRemoved();
+    }
+
+    @Override
+    protected int computeMaxScrollX() {
+        if (!DEBUG_SHOW_CLEAR_ALL_BUTTON || getChildCount() == 0) {
+            return super.computeMaxScrollX();
+        }
+
+        // Allow a clear_all_container_width-sized gap after the last task.
+        return super.computeMaxScrollX() + (int) getResources().getDimension(
+                R.dimen.clear_all_container_width) - getPaddingEnd();
+    }
+
+    public void setContainerView(RecentsViewContainer containerView) {
+        mContainerView = containerView;
+        mContainerView.onEmptyStateChanged(!DEBUG_SHOW_CLEAR_ALL_BUTTON || mShowEmptyMessage);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
new file mode 100644
index 0000000..cd4a6ff
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
@@ -0,0 +1,82 @@
+package com.android.quickstep.views;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.R;
+
+public class RecentsViewContainer extends InsettableFrameLayout {
+    public static final FloatProperty<RecentsViewContainer> CONTENT_ALPHA =
+            new FloatProperty<RecentsViewContainer>("contentAlpha") {
+                @Override
+                public void setValue(RecentsViewContainer view, float v) {
+                    view.setContentAlpha(v);
+                }
+
+                @Override
+                public Float get(RecentsViewContainer view) {
+                    return view.mRecentsView.getContentAlpha();
+                }
+            };
+
+    private final Rect mTempRect = new Rect();
+
+    private RecentsView mRecentsView;
+    private View mClearAllButton;
+
+    public RecentsViewContainer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mClearAllButton = findViewById(R.id.clear_all_button);
+        mClearAllButton.setOnClickListener((v) -> {
+            mRecentsView.dismissAllTasks();
+        });
+
+        mRecentsView = (RecentsView) findViewById(R.id.overview_panel);
+        mRecentsView.setContainerView(this);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+
+        mRecentsView.getTaskSize(mTempRect);
+
+        mClearAllButton.setTranslationX(
+                (mClearAllButton.getMeasuredWidth() - getResources().getDimension(
+                        R.dimen.clear_all_container_width)) / 2);
+        mClearAllButton.setTranslationY(
+                mTempRect.top + (mTempRect.height() - mClearAllButton.getMeasuredHeight()) / 2
+                        - mClearAllButton.getY());
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        super.onTouchEvent(ev);
+        // Do not let touch escape to siblings below this view. This prevents scrolling of the
+        // workspace while in Recents.
+        return true;
+    }
+
+    public void setContentAlpha(float alpha) {
+        if (alpha == mRecentsView.getContentAlpha()) {
+            return;
+        }
+        mRecentsView.setContentAlpha(alpha);
+        setVisibility(alpha > 0 ? VISIBLE : GONE);
+    }
+
+    public void onEmptyStateChanged(boolean isEmpty) {
+        mClearAllButton.setVisibility(isEmpty ? GONE : VISIBLE);
+    }
+}
\ No newline at end of file
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index a4acf06..6556adf 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -40,7 +40,7 @@
             launcher:pageIndicator="@+id/page_indicator" />
 
         <include
-            android:id="@+id/overview_panel"
+            android:id="@+id/overview_panel_container"
             layout="@layout/overview_panel"
             android:visibility="gone" />
 
diff --git a/res/values/config.xml b/res/values/config.xml
index a40afe1..9d1bb41 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -147,6 +147,7 @@
     <item type="id" name="search_container_all_apps" />
 
 <!-- Recents -->
+    <item type="id" name="overview_panel"/>
     <integer name="config_recentsMaxThumbnailCacheSize">6</integer>
     <integer name="config_recentsMaxIconCacheSize">12</integer>
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 8840860..ec0a8ff 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
@@ -205,6 +206,8 @@
     // UI and state for the overview panel
     private View mOverviewPanel;
 
+    private View mOverviewPanelContainer;
+
     @Thunk boolean mWorkspaceLoading = true;
 
     private OnResumeCallback mOnResumeCallback;
@@ -912,6 +915,7 @@
         mWorkspace = mDragLayer.findViewById(R.id.workspace);
         mWorkspace.initParentViews(mDragLayer);
         mOverviewPanel = findViewById(R.id.overview_panel);
+        mOverviewPanelContainer = findViewById(R.id.overview_panel_container);
         mHotseat = findViewById(R.id.hotseat);
         mDragHandleIndicator = findViewById(R.id.drag_indicator);
         mHotseatSearchBox = findViewById(R.id.search_container_hotseat);
@@ -1192,6 +1196,10 @@
         return (T) mOverviewPanel;
     }
 
+    public <T extends View> T getOverviewPanelContainer() {
+        return (T) mOverviewPanelContainer;
+    }
+
     public DropTargetBar getDropTargetBar() {
         return mDropTargetBar;
     }