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