Introduce FolderNameInfo class.

* Introduce FolderNameInfo class for passing down the folder name
suggestions from FolderNameProvider.
* Use FolderNameInfo for storing the serialized suggested names for
Folders. It is parsed and used in FolderEdit.

Bug: 148417030
Bug: 148916551
Bug: 148432151

Change-Id: Idaa81e203cc42889be15d0845230b4508521041c
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/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 024c7dd..0222b57 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/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 8dc7a3a..8717c23 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;
@@ -925,11 +926,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);
                 }
             }
         }