Merge "Address SessionCommitReceiver vulnerability by validating intent." into ub-launcher3-qt-future-dev
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 7115943..cdff33b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -17,7 +17,10 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
@@ -27,9 +30,15 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.view.View;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
+import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.SpringAnimationBuilder;
@@ -38,9 +47,6 @@
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
 /**
  * A {@link QuickstepAppTransitionManagerImpl} that also implements recents transitions from
  * {@link RecentsView}.
@@ -144,8 +150,37 @@
     @Override
     public Animator createStateElementAnimation(int index, float... values) {
         switch (index) {
-            case INDEX_SHELF_ANIM:
-                return mLauncher.getAllAppsController().createSpringAnimation(values);
+            case INDEX_SHELF_ANIM: {
+                AllAppsTransitionController aatc = mLauncher.getAllAppsController();
+                Animator springAnim = aatc.createSpringAnimation(values);
+
+                if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+                    // Translate hotseat with the shelf until reaching overview.
+                    float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
+                    ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mLauncher);
+                    float shiftRange = aatc.getShiftRange();
+                    if (values.length == 1) {
+                        values = new float[] {aatc.getProgress(), values[0]};
+                    }
+                    ValueAnimator hotseatAnim = ValueAnimator.ofFloat(values);
+                    hotseatAnim.addUpdateListener(anim -> {
+                        float progress = (Float) anim.getAnimatedValue();
+                        if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
+                            float hotseatShift = (progress - overviewProgress) * shiftRange;
+                            mLauncher.getHotseat().setTranslationY(hotseatShift + sat.translationY);
+                        }
+                    });
+                    hotseatAnim.setInterpolator(LINEAR);
+                    hotseatAnim.setDuration(springAnim.getDuration());
+
+                    AnimatorSet anim = new AnimatorSet();
+                    anim.play(hotseatAnim);
+                    anim.play(springAnim);
+                    return anim;
+                }
+
+                return springAnim;
+            }
             case INDEX_RECENTS_FADE_ANIM:
                 return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
                         RecentsView.CONTENT_ALPHA, values);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 468b8af..63ac528 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -98,7 +98,7 @@
         if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
             // Translate hotseat offscreen if we show it in overview.
             ScaleAndTranslation scaleAndTranslation = super.getHotseatScaleAndTranslation(launcher);
-            scaleAndTranslation.translationY = LayoutUtils.getShelfTrackingDistance(launcher,
+            scaleAndTranslation.translationY += LayoutUtils.getShelfTrackingDistance(launcher,
                     launcher.getDeviceProfile());
             return scaleAndTranslation;
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 93d4de1..25eaab1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -32,7 +32,6 @@
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
 
-import android.content.Context;
 import android.graphics.Rect;
 import android.view.View;
 
@@ -47,6 +46,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 
@@ -91,8 +91,19 @@
     @Override
     public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
         if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
-            // If the hotseat icons are visible in overview, keep them in their normal position.
-            return super.getWorkspaceScaleAndTranslation(launcher);
+            DeviceProfile dp = launcher.getDeviceProfile();
+            if (dp.allAppsIconSizePx >= dp.iconSizePx) {
+                return new ScaleAndTranslation(1, 0, 0);
+            } else {
+                float scale = ((float) dp.allAppsIconSizePx) / dp.iconSizePx;
+                // Distance between the screen center (which is the pivotY for hotseat) and the
+                // bottom of the hotseat (which we want to preserve)
+                float distanceFromBottom = dp.heightPx / 2 - dp.hotseatBarBottomPaddingPx;
+                // On scaling, the bottom edge is moved closer to the pivotY. We move the
+                // hotseat back down so that the bottom edge's position is preserved.
+                float translationY = distanceFromBottom * (1 - scale);
+                return new ScaleAndTranslation(scale, 0, translationY);
+            }
         }
         return getWorkspaceScaleAndTranslation(launcher);
     }
@@ -160,15 +171,7 @@
     }
 
     public static float getDefaultSwipeHeight(Launcher launcher) {
-        return getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
-    }
-
-    public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) {
-        float swipeHeight = dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
-        if (SysUINavigationMode.getMode(context) == SysUINavigationMode.Mode.NO_BUTTON) {
-            swipeHeight -= dp.getInsets().bottom;
-        }
-        return swipeHeight;
+        return LayoutUtils.getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 2161591..3c78dd8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -7,7 +7,6 @@
 
 import com.android.launcher3.testing.TestInformationHandler;
 import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
@@ -25,7 +24,7 @@
         switch (method) {
             case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
                 final float swipeHeight =
-                        OverviewState.getDefaultSwipeHeight(mContext, mDeviceProfile);
+                        LayoutUtils.getDefaultSwipeHeight(mContext, mDeviceProfile);
                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
                 return response;
             }
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index 050bdff..2e118b4 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -26,7 +26,7 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.quickstep.SysUINavigationMode;
 
 import java.lang.annotation.Retention;
 
@@ -39,12 +39,27 @@
     @IntDef({MULTI_WINDOW_STRATEGY_HALF_SCREEN, MULTI_WINDOW_STRATEGY_DEVICE_PROFILE})
     private @interface MultiWindowStrategy {}
 
+    /**
+     * The height for the swipe up motion
+     */
+    public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) {
+        float swipeHeight = dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
+        if (SysUINavigationMode.getMode(context) == SysUINavigationMode.Mode.NO_BUTTON) {
+            swipeHeight -= dp.getInsets().bottom;
+        }
+        return swipeHeight;
+    }
+
     public static void calculateLauncherTaskSize(Context context, DeviceProfile dp, Rect outRect) {
         float extraSpace;
         if (dp.isVerticalBarLayout()) {
             extraSpace = 0;
         } else {
-            extraSpace = dp.hotseatBarSizePx + dp.verticalDragHandleSizePx;
+            Resources res = context.getResources();
+
+            extraSpace = getDefaultSwipeHeight(context, dp) + dp.verticalDragHandleSizePx
+                    + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
+                    + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
         }
         calculateTaskSize(context, dp, extraSpace, MULTI_WINDOW_STRATEGY_HALF_SCREEN, outRect);
     }
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index 26e9eaf..0e591ca 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -38,12 +38,12 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ScrimView;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.quickstep.util.LayoutUtils;
 
 /**
  * Scrim used for all-apps and shelf in Overview
@@ -163,7 +163,7 @@
                 int hotseatSize = dp.hotseatBarSizePx + dp.getInsets().bottom
                         + hotseatPadding.bottom + hotseatPadding.top;
                 float dragHandleTop =
-                        Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(context, dp));
+                        Math.min(hotseatSize, LayoutUtils.getDefaultSwipeHeight(context, dp));
                 mDragHandleProgress =  1 - (dragHandleTop / mShiftRange);
             }
             mTopOffset = dp.getInsets().top - mShelfOffset;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 7bb618d..491e5de 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -97,6 +97,7 @@
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.folder.FolderGridOrganizer;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.folder.FolderNameProvider;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.keyboard.CustomActionsPopup;
@@ -553,6 +554,10 @@
         return mStateManager;
     }
 
+    public FolderNameProvider getFolderNameProvider() {
+        return new FolderNameProvider();
+    }
+
     @Override
     public <T extends View> T findViewById(int id) {
         return mLauncherView.findViewById(id);
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index c5f26aa..5d0effa 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -584,7 +584,7 @@
             LauncherIcons li = LauncherIcons.obtain(appState.getContext());
             Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).iconBitmap;
             li.recycle();
-            float badgeSize = iconSize * LauncherIcons.getBadgeSizeForIconSize(iconSize);
+            float badgeSize = LauncherIcons.getBadgeSizeForIconSize(iconSize);
             float insetFraction = (iconSize - badgeSize) / iconSize;
             return new InsetDrawable(new FastBitmapDrawable(badge),
                     insetFraction, insetFraction, 0, 0);
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 40c6b5f..7a7e1fe 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -38,7 +38,6 @@
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
 
 /**
@@ -96,14 +95,13 @@
             propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
 
             if (!hotseat.getRotationMode().isTransposed) {
-                // Set the hotseat's pivot point to match the workspace's, so that it scales together.
-                DragLayer dragLayer = mLauncher.getDragLayer();
-                float[] workspacePivot =
-                        new float[]{ mWorkspace.getPivotX(), mWorkspace.getPivotY() };
-                dragLayer.getDescendantCoordRelativeToSelf(mWorkspace, workspacePivot);
-                dragLayer.mapCoordInSelfToDescendant(hotseat, workspacePivot);
-                hotseat.setPivotX(workspacePivot[0]);
-                hotseat.setPivotY(workspacePivot[1]);
+                // Set the hotseat's pivot point to match the workspace's, so that it scales
+                // together. Since both hotseat and workspace can move, transform the point
+                // manually instead of using dragLayer.getDescendantCoordRelativeToSelf and
+                // related methods.
+                hotseat.setPivotY(mWorkspace.getPivotY() + mWorkspace.getTop() - hotseat.getTop());
+                hotseat.setPivotX(mWorkspace.getPivotX()
+                        + mWorkspace.getLeft() - hotseat.getLeft());
             }
             float hotseatScale = hotseatScaleAndTranslation.scale;
             Interpolator hotseatScaleInterpolator = builder.getInterpolator(ANIM_HOTSEAT_SCALE,
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 3836c9f..08ce9c2 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -2,8 +2,6 @@
 
 import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
@@ -134,15 +132,6 @@
         } else {
             mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0);
         }
-
-        if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
-            // Translate hotseat with the shelf until reaching overview.
-            float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
-            if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
-                float hotseatShift = (progress - overviewProgress) * mShiftRange;
-                mLauncher.getHotseat().setTranslationY(hotseatShift);
-            }
-        }
     }
 
     public float getProgress() {
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 99a3604..64d236f 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -108,6 +108,10 @@
             "FAKE_LANDSCAPE_UI", false,
             "Rotate launcher UI instead of using transposed layout");
 
+    public static final TogglableFlag FOLDER_NAME_SUGGEST = new TogglableFlag(
+            "FOLDER_NAME_SUGGEST", true,
+            "Suggests folder names instead of blank text.");
+
     public static final TogglableFlag APP_SEARCH_IMPROVEMENTS = new TogglableFlag(
             "APP_SEARCH_IMPROVEMENTS", false,
             "Adds localized title and keyword search and ranking");
@@ -119,7 +123,6 @@
             "ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
             "Allow Launcher to handle nav bar gestures while Assistant is running over it");
 
-
     public static void initialize(Context context) {
         // Avoid the disk read for user builds
         if (Utilities.IS_DEBUG_DEVICE) {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 65d593c..22dda41 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -401,7 +401,6 @@
             mFolderName.setText("");
             mFolderName.setHint(R.string.folder_hint_text);
         }
-
         // In case any children didn't come across during loading, clean up the folder accordingly
         mFolderIcon.post(() -> {
             if (getItemCount() <= 1) {
@@ -410,6 +409,22 @@
         });
     }
 
+
+    /**
+     * Show suggested folder title.
+     */
+    public void showSuggestedTitle(CharSequence suggestName) {
+        if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && mInfo.contents.size() == 2) {
+            if (!TextUtils.isEmpty(suggestName)) {
+                mFolderName.setHint(suggestName);
+                mFolderName.setText(suggestName);
+                mFolderName.showKeyboard();
+                mInfo.title = suggestName;
+            }
+            animateOpen();
+        }
+    }
+
     /**
      * Creates a new UserFolder, inflated from R.layout.user_folder.
      *
@@ -532,8 +547,6 @@
         // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice.
         mDeleteFolderOnDropCompleted = false;
 
-        centerAboutIcon();
-
         AnimatorSet anim = new FolderAnimationManager(this, true /* isOpening */).getAnimator();
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -592,7 +605,6 @@
         if (mDragController.isDragging()) {
             mDragController.forceTouchMove();
         }
-
         mContent.verifyVisibleHighResIcons(mContent.getNextPage());
     }
 
@@ -877,7 +889,6 @@
         // Reordering may have occured, and we need to save the new item locations. We do this once
         // at the end to prevent unnecessary database operations.
         updateItemLocationsInDatabaseBatch();
-
         // Use the item count to check for multi-page as the folder UI may not have
         // been refreshed yet.
         if (getItemCount() <= mContent.itemsPerPage()) {
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 3840639..fd6d1e3 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -58,12 +58,14 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dot.FolderDotInfo;
 import com.android.launcher3.dragndrop.BaseItemDragListener;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.touch.ItemClickHandler;
+import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.IconLabelDotView;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -368,12 +370,17 @@
 
             if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
             final int finalIndex = index;
-            postDelayed(new Runnable() {
-                public void run() {
-                    mPreviewItemManager.hidePreviewItem(finalIndex, false);
-                    mFolder.showItem(item);
-                    invalidate();
-                }
+
+            String[] suggestedNameOut = new String[1];
+            if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
+                Executors.UI_HELPER_EXECUTOR.post(() -> mLauncher.getFolderNameProvider()
+                        .getSuggestedFolderName(getContext(), mInfo.contents, suggestedNameOut));
+            }
+            postDelayed(() -> {
+                mPreviewItemManager.hidePreviewItem(finalIndex, false);
+                mFolder.showItem(item);
+                invalidate();
+                mFolder.showSuggestedTitle(suggestedNameOut[0]);
             }, DROP_IN_ANIMATION_DURATION);
         } else {
             addItem(item);
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
new file mode 100644
index 0000000..0a1221e
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 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.folder;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.WorkspaceItemInfo;
+
+import java.util.ArrayList;
+
+/**
+ * Locates provider for the folder name.
+ */
+public class FolderNameProvider {
+
+    /**
+     * Returns suggested folder name.
+     */
+    public CharSequence getSuggestedFolderName(Context context,
+            ArrayList<WorkspaceItemInfo> workspaceItemInfos, CharSequence[] suggestName) {
+        // Currently only run the algorithm on initial folder creation.
+        // For more than 2 items in the folder, the ranking algorithm for finding
+        // candidate folder name should be rewritten.
+        if (workspaceItemInfos.size() == 2) {
+            ComponentName cmp1 = workspaceItemInfos.get(0).getTargetComponent();
+            ComponentName cmp2 = workspaceItemInfos.get(1).getTargetComponent();
+
+            String pkgName0 = cmp1 == null ? "" : cmp1.getPackageName();
+            String pkgName1 = cmp2 == null ? "" : cmp2.getPackageName();
+            // If the two icons are from the same package,
+            // then assign the main icon's name
+            if (pkgName0.equals(pkgName1)) {
+                WorkspaceItemInfo wInfo0 = workspaceItemInfos.get(0);
+                WorkspaceItemInfo wInfo1 = workspaceItemInfos.get(1);
+                if (workspaceItemInfos.get(0).itemType == Favorites.ITEM_TYPE_APPLICATION) {
+                    suggestName[0] = wInfo0.title;
+                } else if (wInfo1.itemType == Favorites.ITEM_TYPE_APPLICATION) {
+                    suggestName[0] = wInfo1.title;
+                }
+                return suggestName[0];
+                // two icons are all shortcuts. Don't assign title
+            }
+        }
+        return suggestName[0];
+    }
+}
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 54b363e..3b5fd59 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -277,6 +277,7 @@
             page.removeAllViews();
             pages.add(page);
         }
+        mOrganizer.setFolderInfo(mFolder.getInfo());
         setupContentDimensions(itemCount);
 
         Iterator<CellLayout> pageItr = pages.iterator();
@@ -285,7 +286,6 @@
         int position = 0;
         int rank = 0;
 
-        mOrganizer.setFolderInfo(mFolder.getInfo());
         for (int i = 0; i < itemCount; i++) {
             View v = list.size() > i ? list.get(i) : null;
             if (currentPage == null || position >= mOrganizer.getMaxItemsPerPage()) {
diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java
index 08d73d0..12ca5ee 100644
--- a/src/com/android/launcher3/touch/BaseSwipeDetector.java
+++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java
@@ -33,6 +33,7 @@
  * swipe action happens.
  *
  * @see SingleAxisSwipeDetector
+ * @see BothAxesSwipeDetector
  */
 public abstract class BaseSwipeDetector {
 
diff --git a/src/com/android/launcher3/touch/BothAxesSwipeDetector.java b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java
new file mode 100644
index 0000000..944391e
--- /dev/null
+++ b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2019 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.touch;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * Two dimensional scroll/drag/swipe gesture detector that reports x and y displacement/velocity.
+ */
+public class BothAxesSwipeDetector extends BaseSwipeDetector {
+
+    public static final int DIRECTION_UP = 1 << 0;
+    // Note that this will track left instead of right in RTL.
+    public static final int DIRECTION_RIGHT = 1 << 1;
+    public static final int DIRECTION_DOWN = 1 << 2;
+    // Note that this will track right instead of left in RTL.
+    public static final int DIRECTION_LEFT = 1 << 3;
+
+    /* Client of this gesture detector can register a callback. */
+    private final Listener mListener;
+
+    private int mScrollDirections;
+
+    public BothAxesSwipeDetector(@NonNull Context context, @NonNull Listener l) {
+        this(ViewConfiguration.get(context), l, Utilities.isRtl(context.getResources()));
+    }
+
+    @VisibleForTesting
+    protected BothAxesSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
+            boolean isRtl) {
+        super(config, isRtl);
+        mListener = l;
+    }
+
+    public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
+        mScrollDirections = scrollDirectionFlags;
+        mIgnoreSlopWhenSettling = ignoreSlop;
+    }
+
+    @Override
+    protected boolean shouldScrollStart(PointF displacement) {
+        // Check if the client is interested in scroll in current direction.
+        boolean canScrollUp = (mScrollDirections & DIRECTION_UP) > 0
+                && displacement.y <= -mTouchSlop;
+        boolean canScrollRight = (mScrollDirections & DIRECTION_RIGHT) > 0
+                && displacement.x >= mTouchSlop;
+        boolean canScrollDown = (mScrollDirections & DIRECTION_DOWN) > 0
+                && displacement.y >= mTouchSlop;
+        boolean canScrollLeft = (mScrollDirections & DIRECTION_LEFT) > 0
+                && displacement.x <= -mTouchSlop;
+        return canScrollUp || canScrollRight || canScrollDown || canScrollLeft;
+    }
+
+    @Override
+    protected void reportDragStartInternal(boolean recatch) {
+        mListener.onDragStart(!recatch);
+    }
+
+    @Override
+    protected void reportDraggingInternal(PointF displacement, MotionEvent event) {
+        mListener.onDrag(displacement, event);
+    }
+
+    @Override
+    protected void reportDragEndInternal(PointF velocity) {
+        mListener.onDragEnd(velocity);
+    }
+
+    /** Listener to receive updates on the swipe. */
+    public interface Listener {
+        /** @param start whether this was the original drag start, as opposed to a recatch. */
+        void onDragStart(boolean start);
+
+        boolean onDrag(PointF displacement, MotionEvent motionEvent);
+
+        void onDragEnd(PointF velocity);
+    }
+}
diff --git a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
index 0bf2ff6..f2ebc45 100644
--- a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
+++ b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
@@ -161,6 +161,7 @@
 
     /** Listener to receive updates on the swipe. */
     public interface Listener {
+        /** @param start whether this was the original drag start, as opposed to a recatch. */
         void onDragStart(boolean start);
 
         // TODO remove