Change the load logic of FolderNameProvider
Work profile apps are suggested as "Work" folder name

Bug: 147359653
Bug: 147359733

Change-Id: Idb2438de9c71c85cfeca6a6b0e116174ea2f3b62
diff --git a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
new file mode 100644
index 0000000..f769055
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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 static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.shadows.LShadowUserManager;
+import com.android.launcher3.util.LauncherRoboTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+
+@RunWith(LauncherRoboTestRunner.class)
+public final class FolderNameProviderTest {
+    private Context mContext;
+    private WorkspaceItemInfo mItem1;
+    private WorkspaceItemInfo mItem2;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mItem1 = new WorkspaceItemInfo(new AppInfo(
+                new ComponentName("a.b.c", "a.b.c/a.b.c.d"),
+                "title1",
+                LShadowUserManager.newUserHandle(10),
+                new Intent().setComponent(new ComponentName("a.b.c", "a.b.c/a.b.c.d"))
+        ));
+        mItem2 = new WorkspaceItemInfo(new AppInfo(
+                new ComponentName("a.b.c", "a.b.c/a.b.c.d"),
+                "title2",
+                LShadowUserManager.newUserHandle(10),
+                new Intent().setComponent(new ComponentName("a.b.c", "a.b.c/a.b.c.d"))
+        ));
+    }
+
+    @Test
+    public void getSuggestedFolderName_workAssignedToEnd() {
+        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"));
+
+        suggestedNameOut[0] = "candidate1";
+        suggestedNameOut[1] = "candidate2";
+        suggestedNameOut[2] = "candidate3";
+        new FolderNameProvider().getSuggestedFolderName(mContext, list, suggestedNameOut);
+        assertTrue(suggestedNameOut[3].equals("Work"));
+
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowUserManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowUserManager.java
index edf8edb..576ddbd 100644
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowUserManager.java
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowUserManager.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.shadows;
 
+import android.os.Parcel;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.SparseBooleanArray;
@@ -50,4 +51,12 @@
     public void setUserLocked(UserHandle userHandle, boolean enabled) {
         mLockedUsers.put(userHandle.hashCode(), enabled);
     }
+
+    // Create user handle from parcel since UserHandle.of() was only added in later APIs.
+    public static UserHandle newUserHandle(int uid) {
+        Parcel userParcel = Parcel.obtain();
+        userParcel.writeInt(uid);
+        userParcel.setDataPosition(0);
+        return new UserHandle(userParcel);
+    }
 }
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index af219ba..f76ca50 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -26,6 +26,8 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageManagerHelper;
 
@@ -89,6 +91,15 @@
         runtimeStatusFlags = info.runtimeStatusFlags;
     }
 
+    @VisibleForTesting
+    public AppInfo(ComponentName componentName, CharSequence title,
+            UserHandle user, Intent intent) {
+        this.componentName = componentName;
+        this.title = title;
+        this.user = user;
+        this.intent = intent;
+    }
+
     @Override
     protected String dumpProperties() {
         return super.dumpProperties() + " componentName=" + componentName;
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index e005320..63b0e1e 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -16,12 +16,6 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
-import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
-
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherApps;
@@ -68,10 +62,17 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
+import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
+import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
+
 /**
  * Maintains in-memory state of the Launcher. It is expected that there should be only one
  * LauncherModel object held in a static. Also provide APIs for updating the database state
@@ -127,6 +128,16 @@
     }
 
     /**
+     * Returns AppInfo with corresponding package name.
+     * TODO: move to enqueueModelTask
+     */
+    public Optional<AppInfo> getAppInfoByPackageName(String pkg) {
+        return mBgAllAppsList.data.stream()
+                .filter(info -> info.componentName.getPackageName().equals(pkg))
+                .findAny();
+    }
+
+    /**
      * Adds the provided items to the workspace.
      */
     public void addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList) {
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 37aa815..e58d484 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -15,13 +15,23 @@
  */
 package com.android.launcher3.folder;
 
-import android.content.ComponentName;
 import android.content.Context;
+import android.os.Process;
+import android.text.TextUtils;
 
-import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
 import com.android.launcher3.WorkspaceItemInfo;
 
 import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 /**
  * Locates provider for the folder name.
@@ -29,39 +39,65 @@
 public class FolderNameProvider {
 
     /**
-     * IME usually has up to 3 suggest slots. Adding one as in Launcher, there are folder
-     * name edit box that we can also provide suggestion.
+     * IME usually has up to 3 suggest slots. In total, there are 4 suggest slots as the folder
+     * name edit box can also be used to provide suggestion.
      */
     public static final int SUGGEST_MAX = 4;
 
-    /**
-     * 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();
+            ArrayList<WorkspaceItemInfo> workspaceItemInfos, CharSequence[] candidates) {
 
-            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
+        CharSequence suggest;
+        // 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))
+                .collect(Collectors.toList());
+
+        if (distinctItemInfos.size() == 1
+                && !distinctItemInfos.get(0).user.equals(Process.myUserHandle())) {
+            // Place it as last viable suggestion
+            setAsLastSuggestion(candidates,
+                    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
+                        ? p.getTargetComponent().getPackageName() : ""))
+                .collect(Collectors.toList());
+
+        if (distinctItemInfos.size() == 1) {
+            Optional<AppInfo> info = LauncherAppState.getInstance(context).getModel()
+                    .getAppInfoByPackageName(distinctItemInfos.get(0).getTargetComponent()
+                            .getPackageName());
+            // Place it as first viable suggestion and shift everything else
+            info.ifPresent(i -> setAsFirstSuggestion(candidates, i.title.toString()));
+        }
+        return candidates[0];
+    }
+
+    private void setAsFirstSuggestion(CharSequence[] candidatesOut, CharSequence candidate) {
+        for (int i = candidatesOut.length - 1; i > 0; i--) {
+            if (TextUtils.isEmpty(candidatesOut[i])) {
+                candidatesOut[i - 1] = candidatesOut[i];
+            }
+            candidatesOut[0] = candidate;
+        }
+    }
+
+    private void setAsLastSuggestion(CharSequence[] candidatesOut, CharSequence candidate) {
+        for (int i = 0; i < candidate.length(); i++) {
+            if (TextUtils.isEmpty(candidatesOut[i])) {
+                candidatesOut[i] = candidate;
             }
         }
-        return suggestName[0];
+    }
+
+    // This method can be moved to some Utility class location.
+    private static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
+        Map<Object, Boolean> map = new ConcurrentHashMap<>();
+        return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
     }
 }