Merge "Revert ENABLE_PREDICTION_DISMISS and ENABLE_HYBRID_HOTSEAT flags to use DeviceFlag" into ub-launcher3-master
diff --git a/proguard.flags b/proguard.flags
index 01302cf..e556c94 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -48,3 +48,6 @@
 -dontwarn android.view.**
 -dontwarn android.os.**
 -dontwarn android.graphics.**
+
+# Ignore warnings for hidden utility classes referenced from the shared lib
+-dontwarn com.android.internal.util.**
\ No newline at end of file
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 0560d68..3c7f308 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -120,6 +120,8 @@
   BACK_GESTURE = 19;
   UNDO = 20;
   DISMISS_PREDICTION = 21;
+  HYBRID_HOTSEAT_ACCEPTED = 22;
+  HYBRID_HOTSEAT_CANCELED = 23;
 }
 
 enum TipType {
@@ -129,6 +131,7 @@
   QUICK_SCRUB_TEXT = 3;
   PREDICTION_TEXT = 4;
   DWB_TOAST = 5;
+  HYBRID_HOTSEAT = 6;
 }
 
 // Used to define the action component of the LauncherEvent.
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 4c87945..00e72b1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.hybridhotseat;
 
+import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.HYBRID_HOTSEAT_CANCELED;
+
 import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -32,6 +35,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.uioverrides.PredictedAppIcon;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.views.AbstractSlideInView;
@@ -95,6 +99,7 @@
         handleClose(true);
         mHotseatEduController.migrate();
         mHotseatEduController.finishOnboarding();
+        logUserAction(true);
         Toast.makeText(mLauncher, R.string.hotseat_items_migrated, Toast.LENGTH_LONG).show();
     }
 
@@ -102,6 +107,7 @@
         if (mHotseatEduController == null) return;
         Toast.makeText(getContext(), R.string.hotseat_no_migration, Toast.LENGTH_LONG).show();
         mHotseatEduController.finishOnboarding();
+        logUserAction(false);
         handleClose(true);
     }
 
@@ -133,7 +139,28 @@
                 mLauncher.getDeviceProfile().hotseatBarSizePx + insets.bottom;
     }
 
+    private void logUserAction(boolean migrated) {
+        LauncherLogProto.Action action = new LauncherLogProto.Action();
+        LauncherLogProto.Target target = new LauncherLogProto.Target();
+        action.type = LauncherLogProto.Action.Type.TOUCH;
+        action.touch = LauncherLogProto.Action.Touch.TAP;
+        target.containerType = LauncherLogProto.ContainerType.TIP;
+        target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
+        target.controlType = migrated ? LauncherLogProto.ControlType.HYBRID_HOTSEAT_ACCEPTED
+                : HYBRID_HOTSEAT_CANCELED;
+        LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
+        UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
+    }
 
+    private void logOnBoardingSeen() {
+        LauncherLogProto.Action action = new LauncherLogProto.Action();
+        LauncherLogProto.Target target = new LauncherLogProto.Target();
+        action.type = LauncherLogProto.Action.Type.TIP;
+        target.containerType = LauncherLogProto.ContainerType.TIP;
+        target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
+        LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
+        UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
+    }
     private void animateOpen() {
         if (mIsOpen || mOpenCloseAnimator.isRunning()) {
             return;
@@ -165,6 +192,7 @@
             return;
         }
         mLauncher.getDragLayer().addView(this);
+        logOnBoardingSeen();
         animateOpen();
         for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
             WorkspaceItemInfo info = predictions.get(i);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index b2e1798..4bbb48c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -86,6 +86,7 @@
         mIsDrawingDot = true;
         int count = canvas.save();
         canvas.translate(-getWidth() * RING_EFFECT_RATIO, -getHeight() * RING_EFFECT_RATIO);
+        canvas.scale(1 + 2 * RING_EFFECT_RATIO, 1 + 2 * RING_EFFECT_RATIO);
         super.drawDotIfNecessary(canvas);
         canvas.restoreToCount(count);
         mIsDrawingDot = false;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index 28fc3da..1b6d291 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -234,6 +234,7 @@
             public void adjustActivityControllerInterpolators() {
                 if (mAdjustInterpolatorsRunnable != null) {
                     mAdjustInterpolatorsRunnable.run();
+                    mAdjustInterpolatorsRunnable = null;
                 }
             }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index 49f667e..8ed1392 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -54,6 +54,7 @@
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ConfigurationCompat;
 
 /**
  * A task in the Recents view.
@@ -357,7 +358,8 @@
 
             final float thumbnailScale;
             int thumbnailRotation = mThumbnailData.rotation;
-            int currentRotation = getDisplay() != null ? getDisplay().getRotation() : 0;
+            int currentRotation = ConfigurationCompat.getWindowConfigurationRotation(
+                    getResources().getConfiguration());
             int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
             // Landscape vs portrait change
             boolean windowingModeSupportsRotation = !mActivity.isInMultiWindowMode()
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 5f81e1f..45a62ab 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -67,13 +67,13 @@
     <string  name="back_gesture_tutorial_close_button_content_description" translatable="false">Close</string>
 
     <!-- Hotseat migration notification title -->
-    <string translatable="false" name="hotseat_migrate_prompt_title">Get suggested apps on the home screen</string>
+    <string translatable="false" name="hotseat_migrate_prompt_title">Easily access your most-used apps</string>
     <!-- Hotseat migration notification content -->
-    <string translatable="false" name="hotseat_migrate_prompt_content">Tap to set up</string>
+    <string translatable="false" name="hotseat_migrate_prompt_content">Pixel suggests your favorite apps based on your routines. Tap to learn more.</string>
     <!-- Hotseat migration wizard title -->
     <string translatable="false" name="hotseat_migrate_title">Suggested apps replace the bottom row of apps</string>
     <!-- Hotseat migration wizard message -->
-    <string translatable="false" name="hotseat_migrate_message">To pin a favorite app, drag it over a suggested app. To hide a suggested app, touch &amp; hold it.</string>
+    <string translatable="false" name="hotseat_migrate_message">Your current apps will move to the last screen. To pin or block a suggested app, drag it off the bottom row.</string>
     <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
     <string translatable="false" name="hotseat_items_migrated">Bottom row of apps moved to last screen</string>
     <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 94ff63b..15503b8 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -167,7 +167,7 @@
     @Override
     public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
         if (requestCode != -1) {
-            mPendingActivityRequestCode = -1;
+            mPendingActivityRequestCode = requestCode;
             StartActivityParams params = new StartActivityParams(this, requestCode);
             params.intent = intent;
             params.options = options;
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 46af8bf..00adfbe 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -201,6 +201,7 @@
         if (mInputConsumerController != null) {
             mInputConsumerController.setInputListener(null);
         }
+        mInputProxySupplier = null;
     }
 
     private boolean onInputConsumerEvent(InputEvent ev) {
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index d2f5d8f..3b3e1c7 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -25,7 +25,6 @@
 import static org.junit.Assert.assertTrue;
 
 import android.content.Intent;
-import android.os.RemoteException;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -84,17 +83,6 @@
     }
 
     @Test
-    @PortraitLandscape
-    @Ignore // Enable after b/131115533
-    public void testPressRecentAppsLauncherAndGetOverview() throws RemoteException {
-        mDevice.pressRecentApps();
-        waitForState("Launcher internal state didn't switch to Overview",
-                () -> LauncherState.OVERVIEW);
-
-        assertNotNull("getOverview() returned null", mLauncher.getOverview());
-    }
-
-    @Test
     @NavigationModeSwitch
     @PortraitLandscape
     public void testWorkspaceSwitchToAllApps() {
diff --git a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
index f769055..74ef55e 100644
--- a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.folder;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -61,15 +61,16 @@
         ArrayList<WorkspaceItemInfo> list = new ArrayList<>();
         list.add(mItem1);
         list.add(mItem2);
-        String[] suggestedNameOut = new String[FolderNameProvider.SUGGEST_MAX];
-        new FolderNameProvider().getSuggestedFolderName(mContext, list, suggestedNameOut);
-        assertTrue(suggestedNameOut[0].equals("Work"));
+        FolderNameInfo[] nameInfos =
+                new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+        new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos);
+        assertEquals("Work", nameInfos[0].getLabel());
 
-        suggestedNameOut[0] = "candidate1";
-        suggestedNameOut[1] = "candidate2";
-        suggestedNameOut[2] = "candidate3";
-        new FolderNameProvider().getSuggestedFolderName(mContext, list, suggestedNameOut);
-        assertTrue(suggestedNameOut[3].equals("Work"));
+        nameInfos[0] = new FolderNameInfo("candidate1", 0.9);
+        nameInfos[1] = new FolderNameInfo("candidate2", 0.8);
+        nameInfos[2] = new FolderNameInfo("candidate3", 0.7);
+        new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos);
+        assertEquals("Work", nameInfos[3].getLabel());
 
     }
 }
diff --git a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
index 48b5a45..95a4146 100644
--- a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
+++ b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
@@ -49,8 +49,9 @@
     @After
     public void tearDown() {
         // Clear existing logs
-        new File(mTempDir, "log-0").delete();
-        new File(mTempDir, "log-1").delete();
+        for (int i = 0; i < FileLog.LOG_DAYS; i++) {
+            new File(mTempDir, "log-" + i).delete();
+        }
         mTempDir.delete();
 
         mTestActive = false;
@@ -89,8 +90,9 @@
 
         Calendar threeDaysAgo = Calendar.getInstance();
         threeDaysAgo.add(Calendar.HOUR, -72);
-        new File(mTempDir, "log-0").setLastModified(threeDaysAgo.getTimeInMillis());
-        new File(mTempDir, "log-1").setLastModified(threeDaysAgo.getTimeInMillis());
+        for (int i = 0; i < FileLog.LOG_DAYS; i++) {
+            new File(mTempDir, "log-" + i).setLastModified(threeDaysAgo.getTimeInMillis());
+        }
 
         FileLog.print("Testing", "abracadabra", new Exception("cat! cat!"));
         writer = new StringWriter();
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 787eee1..336e423 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -48,6 +48,8 @@
 
     public static final int FLAG_MANUAL_FOLDER_NAME = 0x00000008;
 
+    public static final String EXTRA_FOLDER_SUGGESTIONS = "suggest";
+
     public int options;
 
     public Intent suggestedFolderNames;
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index c867369..4be6a8f 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -90,7 +90,7 @@
             "FAKE_LANDSCAPE_UI", false, "Rotate launcher UI instead of using transposed layout");
 
     public static final BooleanFlag FOLDER_NAME_SUGGEST = getDebugFlag(
-            "FOLDER_NAME_SUGGEST", false, "Suggests folder names instead of blank text.");
+            "FOLDER_NAME_SUGGEST", true, "Suggests folder names instead of blank text.");
 
     public static final BooleanFlag APP_SEARCH_IMPROVEMENTS = new DeviceFlag(
             "APP_SEARCH_IMPROVEMENTS", false,
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 8e80916..544efd5 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -89,6 +89,8 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  * Represents a set of icons chosen by the user or generated by the system.
@@ -301,12 +303,12 @@
         post(() -> {
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
                 if (TextUtils.isEmpty(mFolderName.getText())) {
-                    String[] suggestedNames =
-                            mInfo.suggestedFolderNames.getStringArrayExtra("suggest");
-                    mFolderName.setText(suggestedNames[0]);
-                    mFolderName.displayCompletions(Arrays.asList(suggestedNames).subList(1,
-                            suggestedNames.length));
-                    mFolderName.setEnteredCompose(false);
+                    FolderNameInfo[] nameInfos =
+                            (FolderNameInfo[]) mInfo.suggestedFolderNames.getParcelableArrayExtra(
+                                    FolderInfo.EXTRA_FOLDER_SUGGESTIONS);
+                    if (nameInfos != null) {
+                        showLabelSuggestion(nameInfos);
+                    }
                 }
             }
             mFolderName.setHint("");
@@ -443,27 +445,53 @@
     }
 
     /**
-     * Show suggested folder title.
+     * Show suggested folder title in FolderEditText, push InputMethodManager suggestions and save
+     * the suggestedFolderNames.
      */
-    public void showSuggestedTitle(String[] suggestName) {
+    public void showSuggestedTitle(FolderNameInfo[] nameInfos) {
         if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-            mInfo.suggestedFolderNames = new Intent().putExtra("suggest", suggestName);
+            mInfo.suggestedFolderNames = new Intent().putExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS,
+                    nameInfos);
             if (TextUtils.isEmpty(mFolderName.getText().toString())
                     && !mInfo.hasOption(FolderInfo.FLAG_MANUAL_FOLDER_NAME)) {
-                if (suggestName.length > 0 && !TextUtils.isEmpty(suggestName[0])) {
-                    mFolderName.setHint("");
-                    mFolderName.setText(suggestName[0]);
-                    mInfo.title = suggestName[0];
-                    animateOpen(mInfo.contents, 0, true);
-                    mFolderName.showKeyboard();
-                    mFolderName.displayCompletions(
-                            Arrays.asList(suggestName).subList(1, suggestName.length));
-                }
+                showLabelSuggestion(nameInfos);
             }
         }
     }
 
     /**
+     * Show suggested folder title in FolderEditText if the first suggestion is non-empty, push
+     * InputMethodManager suggestions.
+     */
+    private void showLabelSuggestion(FolderNameInfo[] nameInfos) {
+        if (nameInfos == null) {
+            return;
+        }
+        // Open the Folder and Keyboard when the first or second suggestion is valid non-empty
+        // string.
+        boolean shouldOpen = nameInfos.length > 0 && nameInfos[0] != null && !TextUtils.isEmpty(
+                nameInfos[0].getLabel())
+                || nameInfos.length > 1 && nameInfos[1] != null && !TextUtils.isEmpty(
+                nameInfos[1].getLabel());
+        CharSequence firstLabel = nameInfos[0].getLabel();
+
+        if (shouldOpen) {
+            if (!TextUtils.isEmpty(firstLabel)) {
+                mFolderName.setHint("");
+                mFolderName.setText(firstLabel);
+                mInfo.title = firstLabel;
+            }
+            animateOpen(mInfo.contents, 0, true);
+            mFolderName.showKeyboard();
+            mFolderName.displayCompletions(
+                    Arrays.asList(nameInfos).subList(1, nameInfos.length).stream()
+                            .filter(Objects::nonNull)
+                            .map(s -> s.getLabel().toString())
+                            .collect(Collectors.toList()));
+        }
+    }
+
+    /**
      * Creates a new UserFolder, inflated from R.layout.user_folder.
      *
      * @param launcher The main activity.
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 6a47b98..ab1ff10 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -393,15 +393,16 @@
             if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
             final int finalIndex = index;
 
-            String[] suggestedNameOut = new String[FolderNameProvider.SUGGEST_MAX];
+            FolderNameInfo[] nameInfos =
+                    new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
                 Executors.UI_HELPER_EXECUTOR.post(() -> {
                     d.folderNameProvider.getSuggestedFolderName(
-                            getContext(), mInfo.contents, suggestedNameOut);
-                    showFinalView(finalIndex, item, suggestedNameOut);
+                            getContext(), mInfo.contents, nameInfos);
+                    showFinalView(finalIndex, item, nameInfos);
                 });
             } else {
-                showFinalView(finalIndex, item, suggestedNameOut);
+                showFinalView(finalIndex, item, nameInfos);
             }
         } else {
             addItem(item);
@@ -409,12 +410,12 @@
     }
 
     private void showFinalView(int finalIndex, final WorkspaceItemInfo item,
-            String[] suggestedNameOut) {
+            FolderNameInfo[] nameInfos) {
         postDelayed(() -> {
             mPreviewItemManager.hidePreviewItem(finalIndex, false);
             mFolder.showItem(item);
             invalidate();
-            mFolder.showSuggestedTitle(suggestedNameOut);
+            mFolder.showSuggestedTitle(nameInfos);
         }, DROP_IN_ANIMATION_DURATION);
     }
 
diff --git a/src/com/android/launcher3/folder/FolderNameInfo.java b/src/com/android/launcher3/folder/FolderNameInfo.java
new file mode 100644
index 0000000..eb9da90
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderNameInfo.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2020 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.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Information about a single label suggestions of the Folder.
+ */
+
+public final class FolderNameInfo implements Parcelable {
+    private final double mScore;
+    private final CharSequence mLabel;
+
+    /**
+     * Create a simple completion with label.
+     *
+     * @param label The text that should be inserted into the editor and pushed to
+     *              InputMethodManager suggestions.
+     * @param score The score for the label between 0.0 and 1.0.
+     */
+    public FolderNameInfo(CharSequence label, double score) {
+        mScore = score;
+        mLabel = label;
+    }
+
+    private FolderNameInfo(Parcel source) {
+        mScore = source.readDouble();
+        mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+    }
+
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest  The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeDouble(mScore);
+        TextUtils.writeToParcel(mLabel, dest, flags);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    @NonNull
+    public static final Parcelable.Creator<FolderNameInfo> CREATOR =
+            new Parcelable.Creator<FolderNameInfo>() {
+                public FolderNameInfo createFromParcel(Parcel source) {
+                    return new FolderNameInfo(source);
+                }
+
+                public FolderNameInfo[] newArray(int size) {
+                    return new FolderNameInfo[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 957e636..d5990fa 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -30,6 +30,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
@@ -60,29 +61,33 @@
         return fnp;
     }
 
-    public CharSequence getSuggestedFolderName(Context context,
-            ArrayList<WorkspaceItemInfo> workspaceItemInfos, CharSequence[] candidates) {
+    /**
+     * Generate and rank the suggested Folder names.
+     */
+    public void getSuggestedFolderName(Context context,
+            ArrayList<WorkspaceItemInfo> workspaceItemInfos,
+            FolderNameInfo[] nameInfos) {
 
         if (DEBUG) {
-            Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(candidates));
+            Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(nameInfos));
         }
         // If all the icons are from work profile,
         // Then, suggest "Work" as the folder name
         List<WorkspaceItemInfo> distinctItemInfos = workspaceItemInfos.stream()
-                .filter(distinctByKey(p-> p.user))
+                .filter(distinctByKey(p -> p.user))
                 .collect(Collectors.toList());
 
         if (distinctItemInfos.size() == 1
                 && !distinctItemInfos.get(0).user.equals(Process.myUserHandle())) {
             // Place it as last viable suggestion
-            setAsLastSuggestion(candidates,
+            setAsLastSuggestion(nameInfos,
                     context.getResources().getString(R.string.work_folder_name));
         }
 
         // If all the icons are from same package (e.g., main icon, shortcut, shortcut)
         // Then, suggest the package's title as the folder name
         distinctItemInfos = workspaceItemInfos.stream()
-                .filter(distinctByKey(p-> p.getTargetComponent() != null
+                .filter(distinctByKey(p -> p.getTargetComponent() != null
                         ? p.getTargetComponent().getPackageName() : ""))
                 .collect(Collectors.toList());
 
@@ -91,44 +96,46 @@
                     .getAppInfoByPackageName(distinctItemInfos.get(0).getTargetComponent()
                             .getPackageName());
             // Place it as first viable suggestion and shift everything else
-            info.ifPresent(i -> setAsFirstSuggestion(candidates, i.title.toString()));
+            info.ifPresent(i -> setAsFirstSuggestion(nameInfos, i.title.toString()));
         }
         if (DEBUG) {
-            Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(candidates));
+            Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(nameInfos));
         }
-        return candidates[0];
     }
 
-    private void setAsFirstSuggestion(CharSequence[] candidatesOut, CharSequence candidate) {
-        if (contains(candidatesOut, candidate)) {
+    private void setAsFirstSuggestion(FolderNameInfo[] nameInfos, CharSequence label) {
+        if (nameInfos.length == 0 || contains(nameInfos, label)) {
             return;
         }
-        for (int i = candidatesOut.length - 1; i > 0; i--) {
-            if (!TextUtils.isEmpty(candidatesOut[i - 1])) {
-                candidatesOut[i] = candidatesOut[i - 1];
+        for (int i = nameInfos.length - 1; i > 0; i--) {
+            if (nameInfos[i - 1] != null && !TextUtils.isEmpty(nameInfos[i - 1].getLabel())) {
+                nameInfos[i] = nameInfos[i - 1];
             }
         }
-        candidatesOut[0] = candidate;
+        nameInfos[0] = new FolderNameInfo(label, 1.0);
     }
 
-    private void setAsLastSuggestion(CharSequence[] candidatesOut, CharSequence candidate) {
-        if (contains(candidatesOut, candidate)) {
+    private void setAsLastSuggestion(FolderNameInfo[] nameInfos, CharSequence label) {
+        if (nameInfos.length == 0 || contains(nameInfos, label)) {
             return;
         }
 
-        for (int i = 0; i < candidate.length(); i++) {
-            if (TextUtils.isEmpty(candidatesOut[i])) {
-                candidatesOut[i] = candidate;
+        for (int i = 0; i < nameInfos.length; i++) {
+            if (nameInfos[i] == null || TextUtils.isEmpty(nameInfos[i].getLabel())) {
+                nameInfos[i] = new FolderNameInfo(label, 1.0);
                 return;
             }
         }
-        candidatesOut[candidate.length() - 1] = candidate;
+        // Overwrite the last suggestion.
+        int lastIndex = nameInfos.length - 1;
+        nameInfos[lastIndex] = new FolderNameInfo(label, 1.0);
     }
 
-    private boolean contains(CharSequence[] list, CharSequence key) {
-        return Arrays.asList(list).stream()
-                .filter(s -> s != null)
-                .anyMatch(s -> s.toString().equalsIgnoreCase(key.toString()));
+    private boolean contains(FolderNameInfo[] nameInfos, CharSequence label) {
+        return Arrays.stream(nameInfos)
+                .filter(Objects::nonNull)
+                .anyMatch(nameInfo -> nameInfo.getLabel().toString().equalsIgnoreCase(
+                        label.toString()));
     }
 
     // This method can be moved to some Utility class location.
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
index 2c972a0..67f07b1 100644
--- a/src/com/android/launcher3/logging/FileLog.java
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -42,7 +42,7 @@
     private static Handler sHandler = null;
     private static File sLogsDirectory = null;
 
-    private static final int LOG_DAYS = 2;
+    public static final int LOG_DAYS = 4;
 
     public static void setDir(File logsDir) {
         if (ENABLED) {
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 4f61a2a..af802ef 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -59,6 +59,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderGridOrganizer;
+import com.android.launcher3.folder.FolderNameInfo;
 import com.android.launcher3.folder.FolderNameProvider;
 import com.android.launcher3.icons.ComponentWithLabelAndIcon;
 import com.android.launcher3.icons.ComponentWithLabelAndIcon.ComponentWithIconCachingLogic;
@@ -904,11 +905,13 @@
 
         synchronized (mBgDataModel) {
             for (int i = 0; i < mBgDataModel.folders.size(); i++) {
-                String[] suggestedOut = new String[FolderNameProvider.SUGGEST_MAX];
+                FolderNameInfo[] suggestionInfos =
+                        new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
                 FolderInfo info = mBgDataModel.folders.valueAt(i);
                 if (info.suggestedFolderNames == null) {
-                    provider.getSuggestedFolderName(mApp.getContext(), info.contents, suggestedOut);
-                    info.suggestedFolderNames = new Intent().putExtra("suggest", suggestedOut);
+                    provider.getSuggestedFolderName(mApp.getContext(), info.contents,
+                            suggestionInfos);
+                    info.suggestedFolderNames = new Intent().putExtra("suggest", suggestionInfos);
                 }
             }
         }
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index 02d07bb..4b867a7 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -76,7 +76,7 @@
                 }
 
                 try {
-                    base.evaluate();
+                    FailureWatcher.super.apply(base, description).evaluate();
                 } catch (Throwable e) {
                     final String stackTrace = Log.getStackTraceString(e);
                     if (!stackTrace.contains(