blob: bf5959442dfa30d0e6a91a804214b1207055ebb0 [file] [log] [blame]
/*
* 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.annotation.SuppressLint;
import android.app.admin.DevicePolicyManager;
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 androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BaseModelUpdateTask;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.StringCache;
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 = false;
/**
* 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<FolderInfo> mFolderInfos;
protected List<AppInfo> 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<AppInfo> appInfos,
IntSparseArrayMap<FolderInfo> 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<AppInfo> appInfos, IntSparseArrayMap<FolderInfo> folderInfos) {
mAppInfos = appInfos;
mFolderInfos = folderInfos;
}
/**
* Generate and rank the suggested Folder names.
*/
@WorkerThread
public void getSuggestedFolderName(Context context,
ArrayList<WorkspaceItemInfo> 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<UserHandle> users = workspaceItemInfos.stream().map(w -> w.user)
.collect(Collectors.toSet());
if (users.size() == 1 && !users.contains(Process.myUserHandle())) {
setAsLastSuggestion(nameInfos, getWorkFolderName(context));
}
// 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<String> packageNames = workspaceItemInfos.stream()
.map(WorkspaceItemInfo::getTargetComponent)
.filter(Objects::nonNull)
.map(ComponentName::getPackageName)
.collect(Collectors.toSet());
if (packageNames.size() == 1) {
Optional<AppInfo> info = getAppInfoByPackageName(packageNames.iterator().next());
// Place it as first viable suggestion and shift everything else
info.ifPresent(i -> setAsFirstSuggestion(
nameInfos, i.title == null ? "" : i.title.toString()));
}
if (DEBUG) {
Log.d(TAG, "getSuggestedFolderName:" + nameInfos.toString());
}
}
@WorkerThread
@SuppressLint("NewApi")
private String getWorkFolderName(Context context) {
if (!Utilities.ATLEAST_T) {
return context.getString(R.string.work_folder_name);
}
return context.getSystemService(DevicePolicyManager.class).getResources()
.getString(StringCache.WORK_FOLDER_NAME, () ->
context.getString(R.string.work_folder_name));
}
private Optional<AppInfo> 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(@NonNull final LauncherAppState app,
@NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
mFolderInfos = dataModel.folders.clone();
mAppInfos = Arrays.asList(apps.copyData());
}
}
}