[Predictive Back] Support WidgetsTwoPaneSheet

Fix: 325930715
Test: Manual - attached video to bug
Flag: aconfig com.android.launcher3.enable_predictive_back_gesture TEAMFOOD
Change-Id: I44098de12253f803526160bce6457a940b2153c7
diff --git a/res/values/id.xml b/res/values/id.xml
index 198496f..59813ad 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -40,6 +40,7 @@
     <item type="id" name="cache_entry_tag_id" />
 
     <item type="id" name="saved_clip_children_tag_id" />
+    <item type="id" name="saved_clip_to_padding_tag_id" />
 
     <item type="id" name="saved_floating_widget_foreground" />
     <item type="id" name="saved_floating_widget_background" />
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index d44438f..d3019c5 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -68,6 +68,8 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
 import android.view.animation.Interpolator;
 
 import androidx.annotation.ChecksSdkIntAtLeast;
@@ -773,6 +775,136 @@
     }
 
     /**
+     * Recursively call {@link ViewGroup#setClipChildren(boolean)} from {@link View} to ts parent
+     * (direct or indirect) inclusive. This method will also save the old clipChildren value on each
+     * view with {@link View#setTag(int, Object)}, which can be restored in
+     * {@link #restoreClipChildrenOnViewTree(View, ViewParent)}.
+     *
+     * Note that if parent is null or not a parent of the view, this method will be applied all the
+     * way to root view.
+     *
+     * @param v child view
+     * @param parent direct or indirect parent of child view
+     * @param clipChildren whether we should clip children
+     */
+    public static void setClipChildrenOnViewTree(
+            @Nullable View v,
+            @Nullable ViewParent parent,
+            boolean clipChildren) {
+        if (v == null) {
+            return;
+        }
+
+        if (v instanceof ViewGroup) {
+            ViewGroup viewGroup = (ViewGroup) v;
+            boolean oldClipChildren = viewGroup.getClipChildren();
+            if (oldClipChildren != clipChildren) {
+                v.setTag(R.id.saved_clip_children_tag_id, oldClipChildren);
+                viewGroup.setClipChildren(clipChildren);
+            }
+        }
+
+        if (v == parent) {
+            return;
+        }
+
+        if (v.getParent() instanceof View) {
+            setClipChildrenOnViewTree((View) v.getParent(), parent, clipChildren);
+        }
+    }
+
+    /**
+     * Recursively call {@link ViewGroup#setClipChildren(boolean)} to restore clip children value
+     * set in {@link #setClipChildrenOnViewTree(View, ViewParent, boolean)} on view to its parent
+     * (direct or indirect) inclusive.
+     *
+     * Note that if parent is null or not a parent of the view, this method will be applied all the
+     * way to root view.
+     *
+     * @param v child view
+     * @param parent direct or indirect parent of child view
+     */
+    public static void restoreClipChildrenOnViewTree(
+            @Nullable View v, @Nullable ViewParent parent) {
+        if (v == null) {
+            return;
+        }
+        if (v instanceof ViewGroup) {
+            ViewGroup viewGroup = (ViewGroup) v;
+            Object viewTag = viewGroup.getTag(R.id.saved_clip_children_tag_id);
+            if (viewTag instanceof Boolean) {
+                viewGroup.setClipChildren((boolean) viewTag);
+                viewGroup.setTag(R.id.saved_clip_children_tag_id, null);
+            }
+        }
+
+        if (v == parent) {
+            return;
+        }
+
+        if (v.getParent() instanceof View) {
+            restoreClipChildrenOnViewTree((View) v.getParent(), parent);
+        }
+    }
+
+    /**
+     * Similar to {@link #setClipChildrenOnViewTree(View, ViewParent, boolean)} but is calling
+     * {@link ViewGroup#setClipToPadding}.
+     */
+    public static void setClipToPaddingOnViewTree(
+            @Nullable View v,
+            @Nullable ViewParent parent,
+            boolean clipToPadding) {
+        if (v == null) {
+            return;
+        }
+
+        if (v instanceof ViewGroup) {
+            ViewGroup viewGroup = (ViewGroup) v;
+            boolean oldClipToPadding = viewGroup.getClipToPadding();
+            if (oldClipToPadding != clipToPadding) {
+                v.setTag(R.id.saved_clip_to_padding_tag_id, oldClipToPadding);
+                viewGroup.setClipToPadding(clipToPadding);
+            }
+        }
+
+        if (v == parent) {
+            return;
+        }
+
+        if (v.getParent() instanceof View) {
+            setClipToPaddingOnViewTree((View) v.getParent(), parent, clipToPadding);
+        }
+    }
+
+    /**
+     * Similar to {@link #restoreClipChildrenOnViewTree(View, ViewParent)} but is calling
+     * {@link ViewGroup#setClipToPadding}.
+     */
+    public static void restoreClipToPaddingOnViewTree(
+            @Nullable View v, @Nullable ViewParent parent) {
+        if (v == null) {
+            return;
+        }
+        if (v instanceof ViewGroup) {
+            ViewGroup viewGroup = (ViewGroup) v;
+            Object viewTag = viewGroup.getTag(R.id.saved_clip_to_padding_tag_id);
+            if (viewTag instanceof Boolean) {
+                viewGroup.setClipToPadding((boolean) viewTag);
+                viewGroup.setTag(R.id.saved_clip_to_padding_tag_id, null);
+            }
+        }
+
+        if (v == parent) {
+            return;
+        }
+
+        if (v.getParent() instanceof View) {
+            restoreClipToPaddingOnViewTree((View) v.getParent(), parent);
+        }
+    }
+
+    /**
      * Translates the {@code targetView} so that it overlaps with {@code exclusionBounds} as little
      * as possible, while remaining within {@code inclusionBounds}.
      * <p>
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index e2c5795..aeef5b8 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -24,6 +24,8 @@
 import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.Utilities.restoreClipChildrenOnViewTree;
+import static com.android.launcher3.Utilities.setClipChildrenOnViewTree;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_BOTTOM_SHEET_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
@@ -38,12 +40,9 @@
 import android.util.FloatProperty;
 import android.view.HapticFeedbackConstants;
 import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
 import android.view.animation.Interpolator;
 
 import androidx.annotation.FloatRange;
-import androidx.annotation.Nullable;
 
 import com.android.app.animation.Interpolators;
 import com.android.launcher3.DeviceProfile;
@@ -423,79 +422,6 @@
     }
 
     /**
-     * Recursively call {@link ViewGroup#setClipChildren(boolean)} from {@link View} to ts parent
-     * (direct or indirect) inclusive. This method will also save the old clipChildren value on each
-     * view with {@link View#setTag(int, Object)}, which can be restored in
-     * {@link #restoreClipChildrenOnViewTree(View, ViewParent)}.
-     *
-     * Note that if parent is null or not a parent of the view, this method will be applied all the
-     * way to root view.
-     *
-     * @param v child view
-     * @param parent direct or indirect parent of child view
-     * @param clipChildren whether we should clip children
-     */
-    private static void setClipChildrenOnViewTree(
-            @Nullable View v,
-            @Nullable ViewParent parent,
-            boolean clipChildren) {
-        if (v == null) {
-            return;
-        }
-
-        if (v instanceof ViewGroup) {
-            ViewGroup viewGroup = (ViewGroup) v;
-            boolean oldClipChildren = viewGroup.getClipChildren();
-            if (oldClipChildren != clipChildren) {
-                v.setTag(R.id.saved_clip_children_tag_id, oldClipChildren);
-                viewGroup.setClipChildren(clipChildren);
-            }
-        }
-
-        if (v == parent) {
-            return;
-        }
-
-        if (v.getParent() instanceof View) {
-            setClipChildrenOnViewTree((View) v.getParent(), parent, clipChildren);
-        }
-    }
-
-    /**
-     * Recursively call {@link ViewGroup#setClipChildren(boolean)} to restore clip children value
-     * set in {@link #setClipChildrenOnViewTree(View, ViewParent, boolean)} on view to its parent
-     * (direct or indirect) inclusive.
-     *
-     * Note that if parent is null or not a parent of the view, this method will be applied all the
-     * way to root view.
-     *
-     * @param v child view
-     * @param parent direct or indirect parent of child view
-     */
-    private static void restoreClipChildrenOnViewTree(
-            @Nullable View v, @Nullable ViewParent parent) {
-        if (v == null) {
-            return;
-        }
-        if (v instanceof ViewGroup) {
-            ViewGroup viewGroup = (ViewGroup) v;
-            Object viewTag = viewGroup.getTag(R.id.saved_clip_children_tag_id);
-            if (viewTag instanceof Boolean) {
-                viewGroup.setClipChildren((boolean) viewTag);
-                viewGroup.setTag(R.id.saved_clip_children_tag_id, null);
-            }
-        }
-
-        if (v == parent) {
-            return;
-        }
-
-        if (v.getParent() instanceof View) {
-            restoreClipChildrenOnViewTree((View) v.getParent(), parent);
-        }
-    }
-
-    /**
      * Updates the total scroll range but does not update the UI.
      */
     public void setShiftRange(float shiftRange) {
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index ddc3cbb..4c9371d 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -347,8 +347,7 @@
     /** Return extra space revealed during predictive back animation. */
     @Px
     protected int getBottomOffsetPx() {
-        final int height = getMeasuredHeight();
-        return (int) ((height / PREDICTIVE_BACK_MIN_SCALE - height) / 2);
+        return (int) (getMeasuredHeight() * (1 - PREDICTIVE_BACK_MIN_SCALE));
     }
 
     /**
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index c3bb993..1e17252 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -17,6 +17,10 @@
 
 import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
 import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker;
+import static com.android.launcher3.Utilities.restoreClipChildrenOnViewTree;
+import static com.android.launcher3.Utilities.restoreClipToPaddingOnViewTree;
+import static com.android.launcher3.Utilities.setClipChildrenOnViewTree;
+import static com.android.launcher3.Utilities.setClipToPaddingOnViewTree;
 
 import android.content.Context;
 import android.graphics.Outline;
@@ -27,6 +31,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewOutlineProvider;
+import android.view.ViewParent;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.ScrollView;
@@ -60,10 +65,13 @@
     private FrameLayout mSuggestedWidgetsContainer;
     private WidgetsListHeader mSuggestedWidgetsHeader;
     private PackageUserKey mSuggestedWidgetsPackageUserKey;
+    private View mPrimaryWidgetListView;
     private LinearLayout mRightPane;
 
     private ScrollView mRightPaneScrollView;
     private WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
+
+    private boolean mOldIsBackSwipeProgressing;
     private int mActivePage = -1;
     private PackageUserKey mSelectedHeader;
 
@@ -124,6 +132,12 @@
         mRightPane.setOutlineProvider(mViewOutlineProviderRightPane);
         mRightPaneScrollView = mContent.findViewById(R.id.right_pane_scroll_view);
         mRightPaneScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
+        mRightPaneScrollView.setOutlineProvider(mViewOutlineProvider);
+        mRightPaneScrollView.setClipToOutline(true);
+
+        mPrimaryWidgetListView = findViewById(R.id.primary_widgets_list_view);
+        mPrimaryWidgetListView.setOutlineProvider(mViewOutlineProvider);
+        mPrimaryWidgetListView.setClipToOutline(true);
 
         onRecommendedWidgetsBound();
         onWidgetsBound();
@@ -134,6 +148,25 @@
     }
 
     @Override
+    protected void onScaleProgressChanged() {
+        super.onScaleProgressChanged();
+        boolean isBackSwipeProgressing = mSlideInViewScale.value > 0;
+        if (isBackSwipeProgressing == mOldIsBackSwipeProgressing) {
+            return;
+        }
+        mOldIsBackSwipeProgressing = isBackSwipeProgressing;
+        if (isBackSwipeProgressing) {
+            setClipChildrenOnViewTree(mPrimaryWidgetListView, (ViewParent) mContent, false);
+            setClipChildrenOnViewTree(mRightPaneScrollView, (ViewParent) mContent, false);
+            setClipToPaddingOnViewTree(mRightPaneScrollView, (ViewParent) mContent, false);
+        } else {
+            restoreClipChildrenOnViewTree(mPrimaryWidgetListView, mContent);
+            restoreClipChildrenOnViewTree(mRightPaneScrollView, mContent);
+            restoreClipToPaddingOnViewTree(mRightPaneScrollView, mContent);
+        }
+    }
+
+    @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
         if (changed && mDeviceProfile.isTwoPanels && enableUnfoldedTwoPanePicker()) {