/* * Copyright (C) 2019 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 android.content.ComponentName; import android.content.Context; import android.os.Process; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.model.AllAppsList; import com.android.launcher3.model.BaseModelUpdateTask; import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.ResourceBasedOverride; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; /** * Locates provider for the folder name. */ public class FolderNameProvider implements ResourceBasedOverride { private static final String TAG = "FolderNameProvider"; private static final boolean DEBUG = true; /** * 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; protected IntSparseArrayMap mFolderInfos; protected List mAppInfos; /** * Retrieve instance of this object that can be overridden in runtime based on the build * variant of the application. */ public static FolderNameProvider newInstance(Context context) { FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class, context.getApplicationContext(), R.string.folder_name_provider_class); Preconditions.assertWorkerThread(); fnp.load(context); return fnp; } public static FolderNameProvider newInstance(Context context, List appInfos, IntSparseArrayMap folderInfos) { Preconditions.assertWorkerThread(); FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class, context.getApplicationContext(), R.string.folder_name_provider_class); fnp.load(appInfos, folderInfos); return fnp; } private void load(Context context) { LauncherAppState.getInstance(context).getModel().enqueueModelUpdateTask( new FolderNameWorker()); } private void load(List appInfos, IntSparseArrayMap folderInfos) { mAppInfos = appInfos; mFolderInfos = folderInfos; } /** * Generate and rank the suggested Folder names. */ public void getSuggestedFolderName(Context context, ArrayList workspaceItemInfos, FolderNameInfos nameInfos) { Preconditions.assertWorkerThread(); if (DEBUG) { Log.d(TAG, "getSuggestedFolderName:" + nameInfos.toString()); } // If all the icons are from work profile, // Then, suggest "Work" as the folder name Set users = workspaceItemInfos.stream().map(w -> w.user) .collect(Collectors.toSet()); if (users.size() == 1 && !users.contains(Process.myUserHandle())) { setAsLastSuggestion(nameInfos, 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 Set packageNames = workspaceItemInfos.stream() .map(WorkspaceItemInfo::getTargetComponent) .filter(Objects::nonNull) .map(ComponentName::getPackageName) .collect(Collectors.toSet()); if (packageNames.size() == 1) { Optional info = getAppInfoByPackageName(packageNames.iterator().next()); // Place it as first viable suggestion and shift everything else info.ifPresent(i -> setAsFirstSuggestion(nameInfos, i.title.toString())); } if (DEBUG) { Log.d(TAG, "getSuggestedFolderName:" + nameInfos.toString()); } } private Optional getAppInfoByPackageName(String packageName) { if (mAppInfos == null || mAppInfos.isEmpty()) { return Optional.empty(); } return mAppInfos.stream() .filter(info -> info.componentName != null) .filter(info -> info.componentName.getPackageName().equals(packageName)) .findAny(); } private void setAsFirstSuggestion(FolderNameInfos nameInfos, CharSequence label) { if (nameInfos == null || nameInfos.contains(label)) { return; } nameInfos.setStatus(FolderNameInfos.HAS_PRIMARY); nameInfos.setStatus(FolderNameInfos.HAS_SUGGESTIONS); CharSequence[] labels = nameInfos.getLabels(); Float[] scores = nameInfos.getScores(); for (int i = labels.length - 1; i > 0; i--) { if (labels[i - 1] != null && !TextUtils.isEmpty(labels[i - 1])) { nameInfos.setLabel(i, labels[i - 1], scores[i - 1]); } } nameInfos.setLabel(0, label, 1.0f); } private void setAsLastSuggestion(FolderNameInfos nameInfos, CharSequence label) { if (nameInfos == null || nameInfos.contains(label)) { return; } nameInfos.setStatus(FolderNameInfos.HAS_PRIMARY); nameInfos.setStatus(FolderNameInfos.HAS_SUGGESTIONS); CharSequence[] labels = nameInfos.getLabels(); for (int i = 0; i < labels.length; i++) { if (labels[i] == null || TextUtils.isEmpty(labels[i])) { nameInfos.setLabel(i, label, 1.0f); return; } } // Overwrite the last suggestion. nameInfos.setLabel(labels.length - 1, label, 1.0f); } private class FolderNameWorker extends BaseModelUpdateTask { @Override public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { mFolderInfos = dataModel.folders.clone(); mAppInfos = Arrays.asList(apps.copyData()); } } }