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