• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
19 
20 import android.annotation.SuppressLint;
21 import android.app.admin.DevicePolicyManager;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.os.Process;
25 import android.os.UserHandle;
26 import android.text.TextUtils;
27 import android.util.Log;
28 
29 import androidx.annotation.NonNull;
30 import androidx.annotation.WorkerThread;
31 
32 import com.android.launcher3.LauncherAppState;
33 import com.android.launcher3.LauncherModel.ModelUpdateTask;
34 import com.android.launcher3.R;
35 import com.android.launcher3.Utilities;
36 import com.android.launcher3.model.AllAppsList;
37 import com.android.launcher3.model.BgDataModel;
38 import com.android.launcher3.model.ModelTaskController;
39 import com.android.launcher3.model.StringCache;
40 import com.android.launcher3.model.data.AppInfo;
41 import com.android.launcher3.model.data.CollectionInfo;
42 import com.android.launcher3.model.data.FolderInfo;
43 import com.android.launcher3.model.data.WorkspaceItemInfo;
44 import com.android.launcher3.util.IntSparseArrayMap;
45 import com.android.launcher3.util.Preconditions;
46 import com.android.launcher3.util.ResourceBasedOverride;
47 
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.List;
51 import java.util.Objects;
52 import java.util.Optional;
53 import java.util.Set;
54 import java.util.stream.Collectors;
55 
56 /**
57  * Locates provider for the folder name.
58  */
59 public class FolderNameProvider implements ResourceBasedOverride {
60 
61     private static final String TAG = "FolderNameProvider";
62     private static final boolean DEBUG = false;
63 
64     /**
65      * IME usually has up to 3 suggest slots. In total, there are 4 suggest slots as the folder
66      * name edit box can also be used to provide suggestion.
67      */
68     public static final int SUGGEST_MAX = 4;
69     protected IntSparseArrayMap<CollectionInfo> mCollectionInfos;
70     protected List<AppInfo> mAppInfos;
71 
72     /**
73      * Retrieve instance of this object that can be overridden in runtime based on the build
74      * variant of the application.
75      */
newInstance(Context context)76     public static FolderNameProvider newInstance(Context context) {
77         FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
78                 context.getApplicationContext(), R.string.folder_name_provider_class);
79         Preconditions.assertWorkerThread();
80         fnp.load(context);
81 
82         return fnp;
83     }
84 
newInstance(Context context, List<AppInfo> appInfos, IntSparseArrayMap<CollectionInfo> folderInfos)85     public static FolderNameProvider newInstance(Context context, List<AppInfo> appInfos,
86             IntSparseArrayMap<CollectionInfo> folderInfos) {
87         Preconditions.assertWorkerThread();
88         FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
89                 context.getApplicationContext(), R.string.folder_name_provider_class);
90         fnp.load(appInfos, folderInfos);
91 
92         return fnp;
93     }
94 
load(Context context)95     private void load(Context context) {
96         LauncherAppState.getInstance(context).getModel().enqueueModelUpdateTask(
97                 new FolderNameWorker());
98     }
99 
load(List<AppInfo> appInfos, IntSparseArrayMap<CollectionInfo> folderInfos)100     private void load(List<AppInfo> appInfos, IntSparseArrayMap<CollectionInfo> folderInfos) {
101         mAppInfos = appInfos;
102         mCollectionInfos = folderInfos;
103     }
104 
105     /**
106      * Generate and rank the suggested Folder names.
107      */
108     @WorkerThread
getSuggestedFolderName(Context context, ArrayList<WorkspaceItemInfo> workspaceItemInfos, FolderNameInfos nameInfos)109     public void getSuggestedFolderName(Context context,
110             ArrayList<WorkspaceItemInfo> workspaceItemInfos,
111             FolderNameInfos nameInfos) {
112         Preconditions.assertWorkerThread();
113         if (DEBUG) {
114             Log.d(TAG, "getSuggestedFolderName:" + nameInfos.toString());
115         }
116 
117         // If all the icons are from work profile,
118         // Then, suggest "Work" as the folder name
119         Set<UserHandle> users = workspaceItemInfos.stream().map(w -> w.user)
120                 .collect(Collectors.toSet());
121         if (users.size() == 1 && !users.contains(Process.myUserHandle())) {
122             setAsLastSuggestion(nameInfos, getWorkFolderName(context));
123         }
124 
125         // If all the icons are from same package (e.g., main icon, shortcut, shortcut)
126         // Then, suggest the package's title as the folder name
127         Set<String> packageNames = workspaceItemInfos.stream()
128                 .map(WorkspaceItemInfo::getTargetComponent)
129                 .filter(Objects::nonNull)
130                 .map(ComponentName::getPackageName)
131                 .collect(Collectors.toSet());
132 
133         if (packageNames.size() == 1) {
134             Optional<AppInfo> info = getAppInfoByPackageName(packageNames.iterator().next());
135             // Place it as first viable suggestion and shift everything else
136             info.ifPresent(i -> setAsFirstSuggestion(
137                     nameInfos, i.title == null ? "" : i.title.toString()));
138         }
139         if (DEBUG) {
140             Log.d(TAG, "getSuggestedFolderName:" + nameInfos.toString());
141         }
142     }
143 
144     @WorkerThread
145     @SuppressLint("NewApi")
getWorkFolderName(Context context)146     private String getWorkFolderName(Context context) {
147         if (!Utilities.ATLEAST_T) {
148             return context.getString(R.string.work_folder_name);
149         }
150         return context.getSystemService(DevicePolicyManager.class).getResources()
151                 .getString(StringCache.WORK_FOLDER_NAME, () ->
152                         context.getString(R.string.work_folder_name));
153     }
154 
getAppInfoByPackageName(String packageName)155     private Optional<AppInfo> getAppInfoByPackageName(String packageName) {
156         if (mAppInfos == null || mAppInfos.isEmpty()) {
157             return Optional.empty();
158         }
159         return mAppInfos.stream()
160                 .filter(info -> info.componentName != null)
161                 .filter(info -> info.componentName.getPackageName().equals(packageName))
162                 .findAny();
163     }
164 
setAsFirstSuggestion(FolderNameInfos nameInfos, CharSequence label)165     private void setAsFirstSuggestion(FolderNameInfos nameInfos, CharSequence label) {
166         if (nameInfos == null || nameInfos.contains(label)) {
167             return;
168         }
169         nameInfos.setStatus(FolderNameInfos.HAS_PRIMARY);
170         nameInfos.setStatus(FolderNameInfos.HAS_SUGGESTIONS);
171         CharSequence[] labels = nameInfos.getLabels();
172         Float[] scores = nameInfos.getScores();
173         for (int i = labels.length - 1; i > 0; i--) {
174             if (labels[i - 1] != null && !TextUtils.isEmpty(labels[i - 1])) {
175                 nameInfos.setLabel(i, labels[i - 1], scores[i - 1]);
176             }
177         }
178         nameInfos.setLabel(0, label, 1.0f);
179     }
180 
setAsLastSuggestion(FolderNameInfos nameInfos, CharSequence label)181     private void setAsLastSuggestion(FolderNameInfos nameInfos, CharSequence label) {
182         if (nameInfos == null || nameInfos.contains(label)) {
183             return;
184         }
185         nameInfos.setStatus(FolderNameInfos.HAS_PRIMARY);
186         nameInfos.setStatus(FolderNameInfos.HAS_SUGGESTIONS);
187         CharSequence[] labels = nameInfos.getLabels();
188         for (int i = 0; i < labels.length; i++) {
189             if (labels[i] == null || TextUtils.isEmpty(labels[i])) {
190                 nameInfos.setLabel(i, label, 1.0f);
191                 return;
192             }
193         }
194         // Overwrite the last suggestion.
195         nameInfos.setLabel(labels.length - 1, label, 1.0f);
196     }
197 
198     private class FolderNameWorker implements ModelUpdateTask {
199 
200         @Override
execute(@onNull ModelTaskController taskController, @NonNull BgDataModel dataModel, @NonNull AllAppsList apps)201         public void execute(@NonNull ModelTaskController taskController,
202                 @NonNull BgDataModel dataModel, @NonNull AllAppsList apps) {
203             mCollectionInfos = getCollectionForSuggestions(dataModel);
204             mAppInfos = Arrays.asList(apps.copyData());
205         }
206     }
207 
getCollectionForSuggestions( BgDataModel dataModel)208     public static IntSparseArrayMap<CollectionInfo> getCollectionForSuggestions(
209             BgDataModel dataModel) {
210         IntSparseArrayMap<CollectionInfo> result = new IntSparseArrayMap<>();
211         dataModel.itemsIdMap.stream()
212                 .filter(item -> item.itemType == ITEM_TYPE_FOLDER)
213                 .forEach(item -> result.put(item.id, (FolderInfo) item));
214         return result;
215     }
216 
217 }
218