Change the load logic of FolderNameProvider
Work profile apps are suggested as "Work" folder name Bug: 147359653 Bug: 147359733 Change-Id: Idb2438de9c71c85cfeca6a6b0e116174ea2f3b62
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.android.launcher3.AppInfo;
|
||||
import com.android.launcher3.WorkspaceItemInfo;
|
||||
import com.android.launcher3.shadows.LShadowUserManager;
|
||||
import com.android.launcher3.util.LauncherRoboTestRunner;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@RunWith(LauncherRoboTestRunner.class)
|
||||
public final class FolderNameProviderTest {
|
||||
private Context mContext;
|
||||
private WorkspaceItemInfo mItem1;
|
||||
private WorkspaceItemInfo mItem2;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = RuntimeEnvironment.application;
|
||||
mItem1 = new WorkspaceItemInfo(new AppInfo(
|
||||
new ComponentName("a.b.c", "a.b.c/a.b.c.d"),
|
||||
"title1",
|
||||
LShadowUserManager.newUserHandle(10),
|
||||
new Intent().setComponent(new ComponentName("a.b.c", "a.b.c/a.b.c.d"))
|
||||
));
|
||||
mItem2 = new WorkspaceItemInfo(new AppInfo(
|
||||
new ComponentName("a.b.c", "a.b.c/a.b.c.d"),
|
||||
"title2",
|
||||
LShadowUserManager.newUserHandle(10),
|
||||
new Intent().setComponent(new ComponentName("a.b.c", "a.b.c/a.b.c.d"))
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSuggestedFolderName_workAssignedToEnd() {
|
||||
ArrayList<WorkspaceItemInfo> list = new ArrayList<>();
|
||||
list.add(mItem1);
|
||||
list.add(mItem2);
|
||||
String[] suggestedNameOut = new String[FolderNameProvider.SUGGEST_MAX];
|
||||
new FolderNameProvider().getSuggestedFolderName(mContext, list, suggestedNameOut);
|
||||
assertTrue(suggestedNameOut[0].equals("Work"));
|
||||
|
||||
suggestedNameOut[0] = "candidate1";
|
||||
suggestedNameOut[1] = "candidate2";
|
||||
suggestedNameOut[2] = "candidate3";
|
||||
new FolderNameProvider().getSuggestedFolderName(mContext, list, suggestedNameOut);
|
||||
assertTrue(suggestedNameOut[3].equals("Work"));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.launcher3.shadows;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.util.SparseBooleanArray;
|
||||
@@ -50,4 +51,12 @@ public class LShadowUserManager extends ShadowUserManager {
|
||||
public void setUserLocked(UserHandle userHandle, boolean enabled) {
|
||||
mLockedUsers.put(userHandle.hashCode(), enabled);
|
||||
}
|
||||
|
||||
// Create user handle from parcel since UserHandle.of() was only added in later APIs.
|
||||
public static UserHandle newUserHandle(int uid) {
|
||||
Parcel userParcel = Parcel.obtain();
|
||||
userParcel.writeInt(uid);
|
||||
userParcel.setDataPosition(0);
|
||||
return new UserHandle(userParcel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.PackageManagerHelper;
|
||||
|
||||
@@ -89,6 +91,15 @@ public class AppInfo extends ItemInfoWithIcon {
|
||||
runtimeStatusFlags = info.runtimeStatusFlags;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public AppInfo(ComponentName componentName, CharSequence title,
|
||||
UserHandle user, Intent intent) {
|
||||
this.componentName = componentName;
|
||||
this.title = title;
|
||||
this.user = user;
|
||||
this.intent = intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String dumpProperties() {
|
||||
return super.dumpProperties() + " componentName=" + componentName;
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
|
||||
package com.android.launcher3;
|
||||
|
||||
import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
|
||||
import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.LauncherApps;
|
||||
@@ -68,10 +62,17 @@ import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
|
||||
import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
|
||||
|
||||
/**
|
||||
* Maintains in-memory state of the Launcher. It is expected that there should be only one
|
||||
* LauncherModel object held in a static. Also provide APIs for updating the database state
|
||||
@@ -126,6 +127,16 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
|
||||
mBgAllAppsList = new AllAppsList(iconCache, appFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns AppInfo with corresponding package name.
|
||||
* TODO: move to enqueueModelTask
|
||||
*/
|
||||
public Optional<AppInfo> getAppInfoByPackageName(String pkg) {
|
||||
return mBgAllAppsList.data.stream()
|
||||
.filter(info -> info.componentName.getPackageName().equals(pkg))
|
||||
.findAny();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided items to the workspace.
|
||||
*/
|
||||
|
||||
@@ -15,13 +15,23 @@
|
||||
*/
|
||||
package com.android.launcher3.folder;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.os.Process;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.AppInfo;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.WorkspaceItemInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Locates provider for the folder name.
|
||||
@@ -29,39 +39,65 @@ import java.util.ArrayList;
|
||||
public class FolderNameProvider {
|
||||
|
||||
/**
|
||||
* IME usually has up to 3 suggest slots. Adding one as in Launcher, there are folder
|
||||
* name edit box that we can also provide suggestion.
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Returns suggested folder name.
|
||||
*/
|
||||
public CharSequence getSuggestedFolderName(Context context,
|
||||
ArrayList<WorkspaceItemInfo> workspaceItemInfos, CharSequence[] suggestName) {
|
||||
// Currently only run the algorithm on initial folder creation.
|
||||
// For more than 2 items in the folder, the ranking algorithm for finding
|
||||
// candidate folder name should be rewritten.
|
||||
if (workspaceItemInfos.size() == 2) {
|
||||
ComponentName cmp1 = workspaceItemInfos.get(0).getTargetComponent();
|
||||
ComponentName cmp2 = workspaceItemInfos.get(1).getTargetComponent();
|
||||
ArrayList<WorkspaceItemInfo> workspaceItemInfos, CharSequence[] candidates) {
|
||||
|
||||
String pkgName0 = cmp1 == null ? "" : cmp1.getPackageName();
|
||||
String pkgName1 = cmp2 == null ? "" : cmp2.getPackageName();
|
||||
// If the two icons are from the same package,
|
||||
// then assign the main icon's name
|
||||
if (pkgName0.equals(pkgName1)) {
|
||||
WorkspaceItemInfo wInfo0 = workspaceItemInfos.get(0);
|
||||
WorkspaceItemInfo wInfo1 = workspaceItemInfos.get(1);
|
||||
if (workspaceItemInfos.get(0).itemType == Favorites.ITEM_TYPE_APPLICATION) {
|
||||
suggestName[0] = wInfo0.title;
|
||||
} else if (wInfo1.itemType == Favorites.ITEM_TYPE_APPLICATION) {
|
||||
suggestName[0] = wInfo1.title;
|
||||
}
|
||||
return suggestName[0];
|
||||
// two icons are all shortcuts. Don't assign title
|
||||
CharSequence suggest;
|
||||
// If all the icons are from work profile,
|
||||
// Then, suggest "Work" as the folder name
|
||||
List<WorkspaceItemInfo> distinctItemInfos = workspaceItemInfos.stream()
|
||||
.filter(distinctByKey(p-> p.user))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (distinctItemInfos.size() == 1
|
||||
&& !distinctItemInfos.get(0).user.equals(Process.myUserHandle())) {
|
||||
// Place it as last viable suggestion
|
||||
setAsLastSuggestion(candidates,
|
||||
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
|
||||
distinctItemInfos = workspaceItemInfos.stream()
|
||||
.filter(distinctByKey(p-> p.getTargetComponent() != null
|
||||
? p.getTargetComponent().getPackageName() : ""))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (distinctItemInfos.size() == 1) {
|
||||
Optional<AppInfo> info = LauncherAppState.getInstance(context).getModel()
|
||||
.getAppInfoByPackageName(distinctItemInfos.get(0).getTargetComponent()
|
||||
.getPackageName());
|
||||
// Place it as first viable suggestion and shift everything else
|
||||
info.ifPresent(i -> setAsFirstSuggestion(candidates, i.title.toString()));
|
||||
}
|
||||
return candidates[0];
|
||||
}
|
||||
|
||||
private void setAsFirstSuggestion(CharSequence[] candidatesOut, CharSequence candidate) {
|
||||
for (int i = candidatesOut.length - 1; i > 0; i--) {
|
||||
if (TextUtils.isEmpty(candidatesOut[i])) {
|
||||
candidatesOut[i - 1] = candidatesOut[i];
|
||||
}
|
||||
candidatesOut[0] = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
private void setAsLastSuggestion(CharSequence[] candidatesOut, CharSequence candidate) {
|
||||
for (int i = 0; i < candidate.length(); i++) {
|
||||
if (TextUtils.isEmpty(candidatesOut[i])) {
|
||||
candidatesOut[i] = candidate;
|
||||
}
|
||||
}
|
||||
return suggestName[0];
|
||||
}
|
||||
|
||||
// This method can be moved to some Utility class location.
|
||||
private static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
|
||||
Map<Object, Boolean> map = new ConcurrentHashMap<>();
|
||||
return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user