Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 97253ec3 authored by Hyunyoung Song's avatar Hyunyoung Song
Browse files

Change the load logic of FolderNameProvider

Work profile apps are suggested as "Work" folder name

Bug: 147359653
Bug: 147359733

Change-Id: Idb2438de9c71c85cfeca6a6b0e116174ea2f3b62
parent 2f3d0566
Loading
Loading
Loading
Loading
+75 −0
Original line number Diff line number Diff line
/*
 * 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"));

    }
}
+9 −0
Original line number Diff line number Diff line
@@ -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);
    }
}
+11 −0
Original line number Diff line number Diff line
@@ -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;
+17 −6
Original line number Diff line number Diff line
@@ -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.
     */
+65 −29
Original line number Diff line number Diff line
@@ -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) {

        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];
    }

            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;
    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];
            }
                return suggestName[0];
                // two icons are all shortcuts. Don't assign title
            candidatesOut[0] = candidate;
        }
    }
        return suggestName[0];

    private void setAsLastSuggestion(CharSequence[] candidatesOut, CharSequence candidate) {
        for (int i = 0; i < candidate.length(); i++) {
            if (TextUtils.isEmpty(candidatesOut[i])) {
                candidatesOut[i] = candidate;
            }
        }
    }

    // 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;
    }
}