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

Commit b988ab77 authored by Schneider Victor-tulias's avatar Schneider Victor-tulias
Browse files

Improve workspace loading times.

Updated loadWorkspace to load all required icons in a series of bulk sql queries. This reduces the cost of SQL lookups (up to two lookups per user, rather than one lookup per icon)

Bug: 195674813
Test: Added all icons to workspace, added duplicate icons, added icons for same component name from different users

Change-Id: I56afaa04e7c7701f0d3c86b31c53f578dfa73fe6
parent 927d6dcc
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -147,6 +147,11 @@ public final class FeatureFlags {
    public static final BooleanFlag ENABLE_THEMED_ICONS = getDebugFlag(
            "ENABLE_THEMED_ICONS", true, "Enable themed icons on workspace");

    public static final BooleanFlag ENABLE_BULK_WORKSPACE_ICON_LOADING = getDebugFlag(
            "ENABLE_BULK_WORKSPACE_ICON_LOADING",
            false,
            "Enable loading workspace icons in bulk.");

    // Keep as DeviceFlag for remote disable in emergency.
    public static final BooleanFlag ENABLE_OVERVIEW_SELECTIONS = new DeviceFlag(
            "ENABLE_OVERVIEW_SELECTIONS", true, "Show Select Mode button in Overview Actions");
+94 −0
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.launcher3.icons;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;

import static java.util.stream.Collectors.groupingBy;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -30,10 +32,15 @@ import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ShortcutInfo;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.graphics.drawable.Drawable;
import android.os.Process;
import android.os.Trace;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;

import androidx.annotation.NonNull;

@@ -47,6 +54,7 @@ import com.android.launcher3.icons.cache.BaseIconCache;
import com.android.launcher3.icons.cache.CachingLogic;
import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.IconRequestInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -56,8 +64,13 @@ import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;

/**
 * Cache of application icons.  Icons can be made from any thread.
@@ -306,6 +319,87 @@ public class IconCache extends BaseIconCache {
        applyCacheEntry(entry, infoInOut);
    }

    /**
     * Creates an sql cursor for a query of a set of ItemInfoWithIcon icons and titles.
     *
     * @param iconRequestInfos List of IconRequestInfos representing titles and icons to query.
     * @param user UserHandle all the given iconRequestInfos share
     * @param useLowResIcons whether we should exclude the icon column from the sql results.
     */
    private <T extends ItemInfoWithIcon> Cursor createBulkQueryCursor(
            List<IconRequestInfo<T>> iconRequestInfos, UserHandle user, boolean useLowResIcons)
            throws SQLiteException {
        String[] queryParams = Stream.concat(
                iconRequestInfos.stream()
                        .map(r -> r.itemInfo.getTargetComponent())
                        .filter(Objects::nonNull)
                        .distinct()
                        .map(ComponentName::flattenToString),
                Stream.of(Long.toString(getSerialNumberForUser(user)))).toArray(String[]::new);
        String componentNameQuery = TextUtils.join(
                ",", Collections.nCopies(queryParams.length - 1, "?"));

        return mIconDb.query(
                useLowResIcons ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES,
                IconDB.COLUMN_COMPONENT
                        + " IN ( " + componentNameQuery + " )"
                        + " AND " + IconDB.COLUMN_USER + " = ?",
                queryParams);
    }

    /**
     * Load and fill icons requested in iconRequestInfos using a single bulk sql query.
     */
    public synchronized <T extends ItemInfoWithIcon> void getTitlesAndIconsInBulk(
            List<IconRequestInfo<T>> iconRequestInfos) {
        Map<Pair<UserHandle, Boolean>, List<IconRequestInfo<T>>> iconLoadSubsectionsMap =
                iconRequestInfos.stream()
                        .collect(groupingBy(iconRequest ->
                                Pair.create(iconRequest.itemInfo.user, iconRequest.useLowResIcon)));

        Trace.beginSection("loadIconsInBulk");
        iconLoadSubsectionsMap.forEach((sectionKey, filteredList) -> {
            Map<ComponentName, List<IconRequestInfo<T>>> duplicateIconRequestsMap =
                    filteredList.stream()
                            .collect(groupingBy(iconRequest ->
                                    iconRequest.itemInfo.getTargetComponent()));

            Trace.beginSection("loadIconSubsectionInBulk");
            try (Cursor c = createBulkQueryCursor(
                    filteredList,
                    /* user = */ sectionKey.first,
                    /* useLowResIcons = */ sectionKey.second)) {
                int componentNameColumnIndex = c.getColumnIndexOrThrow(IconDB.COLUMN_COMPONENT);
                while (c.moveToNext()) {
                    ComponentName cn = ComponentName.unflattenFromString(
                            c.getString(componentNameColumnIndex));
                    List<IconRequestInfo<T>> duplicateIconRequests =
                            duplicateIconRequestsMap.get(cn);

                    if (cn != null) {
                        CacheEntry entry = cacheLocked(
                                cn,
                                /* user = */ sectionKey.first,
                                () -> duplicateIconRequests.get(0).launcherActivityInfo,
                                mLauncherActivityInfoCachingLogic,
                                c,
                                /* usePackageIcon= */ false,
                                /* useLowResIcons = */ sectionKey.second);

                        for (IconRequestInfo<T> iconRequest : duplicateIconRequests) {
                            applyCacheEntry(entry, iconRequest.itemInfo);
                        }
                    }
                }
            } catch (SQLiteException e) {
                Log.d(TAG, "Error reading icon cache", e);
            } finally {
                Trace.endSection();
            }
        });
        Trace.endSection();
    }


    /**
     * Fill in {@param infoInOut} with the corresponding icon and label.
+25 −33
Original line number Diff line number Diff line
@@ -16,13 +16,10 @@

package com.android.launcher3.model;

import static android.graphics.BitmapFactory.decodeByteArray;

import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.Intent.ShortcutIconResource;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
@@ -45,11 +42,10 @@ import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.IconRequestInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
@@ -184,32 +180,21 @@ public class LoaderCursor extends CursorWrapper {
     * Loads the icon from the cursor and updates the {@param info} if the icon is an app resource.
     */
    protected boolean loadIcon(WorkspaceItemInfo info) {
        try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
            if (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
                String packageName = getString(iconPackageIndex);
                String resourceName = getString(iconResourceIndex);
                if (!TextUtils.isEmpty(packageName) || !TextUtils.isEmpty(resourceName)) {
                    info.iconResource = new ShortcutIconResource();
                    info.iconResource.packageName = packageName;
                    info.iconResource.resourceName = resourceName;
                    BitmapInfo iconInfo = li.createIconBitmap(info.iconResource);
                    if (iconInfo != null) {
                        info.bitmap = iconInfo;
                        return true;
                    }
                }
        return createIconRequestInfo(info, false).loadWorkspaceIcon(mContext);
    }

            // Failed to load from resource, try loading from DB.
            byte[] data = getBlob(iconIndex);
            try {
                info.bitmap = li.createIconBitmap(decodeByteArray(data, 0, data.length));
                return true;
            } catch (Exception e) {
                Log.e(TAG, "Failed to decode byte array for info " + info, e);
                return false;
            }
        }
    public IconRequestInfo<WorkspaceItemInfo> createIconRequestInfo(
            WorkspaceItemInfo wai, boolean useLowResIcon) {
        String packageName = itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
                ? getString(iconPackageIndex) : null;
        String resourceName = itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
                ? getString(iconResourceIndex) : null;
        byte[] iconBlob = itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
                || restoreFlag != 0
                ? getBlob(iconIndex) : null;

        return new IconRequestInfo<>(
                wai, mActivityInfo, packageName, resourceName, iconBlob, useLowResIcon);
    }

    /**
@@ -262,6 +247,11 @@ public class LoaderCursor extends CursorWrapper {
     */
    public WorkspaceItemInfo getAppShortcutInfo(
            Intent intent, boolean allowMissingTarget, boolean useLowResIcon) {
        return getAppShortcutInfo(intent, allowMissingTarget, useLowResIcon, true);
    }

    public WorkspaceItemInfo getAppShortcutInfo(
            Intent intent, boolean allowMissingTarget, boolean useLowResIcon, boolean loadIcon) {
        if (user == null) {
            Log.d(TAG, "Null user found in getShortcutInfo");
            return null;
@@ -288,10 +278,12 @@ public class LoaderCursor extends CursorWrapper {
        info.user = user;
        info.intent = newIntent;

        if (loadIcon) {
            mIconCache.getTitleAndIcon(info, mActivityInfo, useLowResIcon);
            if (mIconCache.isDefaultIcon(info.bitmap, user)) {
                loadIcon(info);
            }
        }

        if (mActivityInfo != null) {
            AppInfo.updateRuntimeFlagsForActivityTarget(info, mActivityInfo);
+23 −1
Original line number Diff line number Diff line
@@ -72,6 +72,7 @@ import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.IconRequestInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -420,6 +421,7 @@ public class LoaderTask implements Runnable {
                LauncherAppWidgetProviderInfo widgetProviderInfo;
                Intent intent;
                String targetPkg;
                List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos = new ArrayList<>();

                while (!mStopped && c.moveToNext()) {
                    try {
@@ -542,7 +544,10 @@ public class LoaderTask implements Runnable {
                            } else if (c.itemType ==
                                    LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
                                info = c.getAppShortcutInfo(
                                        intent, allowMissingTarget, useLowResIcon);
                                        intent,
                                        allowMissingTarget,
                                        useLowResIcon,
                                        !FeatureFlags.ENABLE_BULK_WORKSPACE_ICON_LOADING.get());
                            } else if (c.itemType ==
                                    LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {

@@ -594,6 +599,8 @@ public class LoaderTask implements Runnable {
                            }

                            if (info != null) {
                                iconRequestInfos.add(c.createIconRequestInfo(info, useLowResIcon));

                                c.applyCommonProperties(info);

                                info.intent = intent;
@@ -811,6 +818,21 @@ public class LoaderTask implements Runnable {
                        Log.e(TAG, "Desktop items loading interrupted", e);
                    }
                }
                if (FeatureFlags.ENABLE_BULK_WORKSPACE_ICON_LOADING.get()) {
                    Trace.beginSection("LoadWorkspaceIconsInBulk");
                    try {
                        mIconCache.getTitlesAndIconsInBulk(iconRequestInfos);
                        for (IconRequestInfo<WorkspaceItemInfo> iconRequestInfo :
                                iconRequestInfos) {
                            WorkspaceItemInfo wai = iconRequestInfo.itemInfo;
                            if (mIconCache.isDefaultIcon(wai.bitmap, wai.user)) {
                                iconRequestInfo.loadWorkspaceIcon(mApp.getContext());
                            }
                        }
                    } finally {
                        Trace.endSection();
                    }
                }
            } finally {
                IOUtils.closeSilently(c);
            }
+101 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.model.data;

import static android.graphics.BitmapFactory.decodeByteArray;

import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherActivityInfo;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.launcher3.LauncherSettings;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.LauncherIcons;

/**
 * Class representing one request for an icon to be queried in a sql database.
 *
 * @param <T> ItemInfoWithIcon subclass whose title and icon can be loaded and filled by an sql
 *           query.
 */
public class IconRequestInfo<T extends ItemInfoWithIcon> {

    private static final String TAG = "IconRequestInfo";

    @NonNull public final T itemInfo;
    @Nullable public final LauncherActivityInfo launcherActivityInfo;
    @Nullable public final String packageName;
    @Nullable public final String resourceName;
    @Nullable public final byte[] iconBlob;
    public final boolean useLowResIcon;

    public IconRequestInfo(
            @NonNull T itemInfo,
            @Nullable LauncherActivityInfo launcherActivityInfo,
            @Nullable String packageName,
            @Nullable String resourceName,
            @Nullable byte[] iconBlob,
            boolean useLowResIcon) {
        this.itemInfo = itemInfo;
        this.launcherActivityInfo = launcherActivityInfo;
        this.packageName = packageName;
        this.resourceName = resourceName;
        this.iconBlob = iconBlob;
        this.useLowResIcon = useLowResIcon;
    }

    /** Loads  */
    public boolean loadWorkspaceIcon(Context context) {
        if (!(itemInfo instanceof WorkspaceItemInfo)) {
            throw new IllegalStateException(
                    "loadWorkspaceIcon should only be use for a WorkspaceItemInfos: " + itemInfo);
        }

        try (LauncherIcons li = LauncherIcons.obtain(context)) {
            WorkspaceItemInfo info = (WorkspaceItemInfo) itemInfo;
            if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
                if (!TextUtils.isEmpty(packageName) || !TextUtils.isEmpty(resourceName)) {
                    info.iconResource = new Intent.ShortcutIconResource();
                    info.iconResource.packageName = packageName;
                    info.iconResource.resourceName = resourceName;
                    BitmapInfo iconInfo = li.createIconBitmap(info.iconResource);
                    if (iconInfo != null) {
                        info.bitmap = iconInfo;
                        return true;
                    }
                }
            }

            // Failed to load from resource, try loading from DB.
            try {
                if (iconBlob == null) {
                    return false;
                }
                info.bitmap = li.createIconBitmap(decodeByteArray(
                        iconBlob, 0, iconBlob.length));
                return true;
            } catch (Exception e) {
                Log.e(TAG, "Failed to decode byte array for info " + info, e);
                return false;
            }
        }
    }
}