1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.folder;
17 
18 import android.content.ComponentName;
19 import android.content.Context;
20 import android.os.Process;
21 import android.os.UserHandle;
22 import android.text.TextUtils;
23 import android.util.Log;
24 
25 import com.android.launcher3.LauncherAppState;
26 import com.android.launcher3.R;
27 import com.android.launcher3.model.AllAppsList;
28 import com.android.launcher3.model.BaseModelUpdateTask;
29 import com.android.launcher3.model.BgDataModel;
30 import com.android.launcher3.model.data.AppInfo;
31 import com.android.launcher3.model.data.FolderInfo;
32 import com.android.launcher3.model.data.WorkspaceItemInfo;
33 import com.android.launcher3.util.IntSparseArrayMap;
34 import com.android.launcher3.util.Preconditions;
35 import com.android.launcher3.util.ResourceBasedOverride;
36 
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.List;
40 import java.util.Objects;
41 import java.util.Optional;
42 import java.util.Set;
43 import java.util.stream.Collectors;
44 
45 /**
46  * Locates provider for the folder name.
47  */
48 public class FolderNameProvider implements ResourceBasedOverride {
49 
50     private static final String TAG = "FolderNameProvider";
51     private static final boolean DEBUG = true;
52 
53     /**
54      * IME usually has up to 3 suggest slots. In total, there are 4 suggest slots as the folder
55      * name edit box can also be used to provide suggestion.
56      */
57     public static final int SUGGEST_MAX = 4;
58     protected IntSparseArrayMap<FolderInfo> mFolderInfos;
59     protected List<AppInfo> mAppInfos;
60 
61     /**
62      * Retrieve instance of this object that can be overridden in runtime based on the build
63      * variant of the application.
64      */
newInstance(Context context)65     public static FolderNameProvider newInstance(Context context) {
66         FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
67                 context.getApplicationContext(), R.string.folder_name_provider_class);
68         Preconditions.assertWorkerThread();
69         fnp.load(context);
70 
71         return fnp;
72     }
73 
newInstance(Context context, List<AppInfo> appInfos, IntSparseArrayMap<FolderInfo> folderInfos)74     public static FolderNameProvider newInstance(Context context, List<AppInfo> appInfos,
75             IntSparseArrayMap<FolderInfo> folderInfos) {
76         Preconditions.assertWorkerThread();
77         FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
78                 context.getApplicationContext(), R.string.folder_name_provider_class);
79         fnp.load(appInfos, folderInfos);
80 
81         return fnp;
82     }
83 
load(Context context)84     private void load(Context context) {
85         LauncherAppState.getInstance(context).getModel().enqueueModelUpdateTask(
86                 new FolderNameWorker());
87     }
88 
load(List<AppInfo> appInfos, IntSparseArrayMap<FolderInfo> folderInfos)89     private void load(List<AppInfo> appInfos, IntSparseArrayMap<FolderInfo> folderInfos) {
90         mAppInfos = appInfos;
91         mFolderInfos = folderInfos;
92     }
93 
94     /**
95      * Generate and rank the suggested Folder names.
96      */
getSuggestedFolderName(Context context, ArrayList<WorkspaceItemInfo> workspaceItemInfos, FolderNameInfos nameInfos)97     public void getSuggestedFolderName(Context context,
98             ArrayList<WorkspaceItemInfo> workspaceItemInfos,
99             FolderNameInfos nameInfos) {
100         Preconditions.assertWorkerThread();
101         if (DEBUG) {
102             Log.d(TAG, "getSuggestedFolderName:" + nameInfos.toString());
103         }
104         // If all the icons are from work profile,
105         // Then, suggest "Work" as the folder name
106         Set<UserHandle> users = workspaceItemInfos.stream().map(w -> w.user)
107                 .collect(Collectors.toSet());
108         if (users.size() == 1 && !users.contains(Process.myUserHandle())) {
109             setAsLastSuggestion(nameInfos,
110                     context.getResources().getString(R.string.work_folder_name));
111         }
112 
113         // If all the icons are from same package (e.g., main icon, shortcut, shortcut)
114         // Then, suggest the package's title as the folder name
115         Set<String> packageNames = workspaceItemInfos.stream()
116                 .map(WorkspaceItemInfo::getTargetComponent)
117                 .filter(Objects::nonNull)
118                 .map(ComponentName::getPackageName)
119                 .collect(Collectors.toSet());
120 
121         if (packageNames.size() == 1) {
122             Optional<AppInfo> info = getAppInfoByPackageName(packageNames.iterator().next());
123             // Place it as first viable suggestion and shift everything else
124             info.ifPresent(i -> setAsFirstSuggestion(nameInfos, i.title.toString()));
125         }
126         if (DEBUG) {
127             Log.d(TAG, "getSuggestedFolderName:" + nameInfos.toString());
128         }
129     }
130 
getAppInfoByPackageName(String packageName)131     private Optional<AppInfo> getAppInfoByPackageName(String packageName) {
132         if (mAppInfos == null || mAppInfos.isEmpty()) {
133             return Optional.empty();
134         }
135         return mAppInfos.stream()
136                 .filter(info -> info.componentName != null)
137                 .filter(info -> info.componentName.getPackageName().equals(packageName))
138                 .findAny();
139     }
140 
setAsFirstSuggestion(FolderNameInfos nameInfos, CharSequence label)141     private void setAsFirstSuggestion(FolderNameInfos nameInfos, CharSequence label) {
142         if (nameInfos == null || nameInfos.contains(label)) {
143             return;
144         }
145         nameInfos.setStatus(FolderNameInfos.HAS_PRIMARY);
146         nameInfos.setStatus(FolderNameInfos.HAS_SUGGESTIONS);
147         CharSequence[] labels = nameInfos.getLabels();
148         Float[] scores = nameInfos.getScores();
149         for (int i = labels.length - 1; i > 0; i--) {
150             if (labels[i - 1] != null && !TextUtils.isEmpty(labels[i - 1])) {
151                 nameInfos.setLabel(i, labels[i - 1], scores[i - 1]);
152             }
153         }
154         nameInfos.setLabel(0, label, 1.0f);
155     }
156 
setAsLastSuggestion(FolderNameInfos nameInfos, CharSequence label)157     private void setAsLastSuggestion(FolderNameInfos nameInfos, CharSequence label) {
158         if (nameInfos == null || nameInfos.contains(label)) {
159             return;
160         }
161         nameInfos.setStatus(FolderNameInfos.HAS_PRIMARY);
162         nameInfos.setStatus(FolderNameInfos.HAS_SUGGESTIONS);
163         CharSequence[] labels = nameInfos.getLabels();
164         for (int i = 0; i < labels.length; i++) {
165             if (labels[i] == null || TextUtils.isEmpty(labels[i])) {
166                 nameInfos.setLabel(i, label, 1.0f);
167                 return;
168             }
169         }
170         // Overwrite the last suggestion.
171         nameInfos.setLabel(labels.length - 1, label, 1.0f);
172     }
173 
174     private class FolderNameWorker extends BaseModelUpdateTask {
175         @Override
execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps)176         public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
177             mFolderInfos = dataModel.folders.clone();
178             mAppInfos = Arrays.asList(apps.copyData());
179         }
180     }
181 
182 }
183