Merge changes from topic "launcher-loading" into sc-v2-dev am: ca1ee7a161

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/15616248

Change-Id: Iba1ebff3227fbfcbc93f86e01eb08bf30de98cc8
This commit is contained in:
TreeHugger Robot
2021-09-16 04:36:02 +00:00
committed by Automerger Merge Worker
6 changed files with 286 additions and 36 deletions
+24
View File
@@ -87,6 +87,7 @@ import android.os.Parcelable;
import android.os.Process;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.Trace;
import android.text.TextUtils;
import android.text.method.TextKeyListener;
import android.util.Log;
@@ -280,6 +281,11 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
private static final int THEME_CROSS_FADE_ANIMATION_DURATION = 375;
private static final String DISPLAY_WORKSPACE_TRACE_METHOD_NAME = "DisplayWorkspaceFirstFrame";
private static final String DISPLAY_ALL_APPS_TRACE_METHOD_NAME = "DisplayAllApps";
public static final int DISPLAY_WORKSPACE_TRACE_COOKIE = 0;
public static final int DISPLAY_ALL_APPS_TRACE_COOKIE = 1;
private Configuration mOldConfig;
@Thunk
@@ -366,7 +372,15 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
private LauncherState mPrevLauncherState;
@Override
@TargetApi(Build.VERSION_CODES.S)
protected void onCreate(Bundle savedInstanceState) {
// Only use a hard-coded cookie since we only want to trace this once.
if (Utilities.ATLEAST_S) {
Trace.beginAsyncSection(
DISPLAY_WORKSPACE_TRACE_METHOD_NAME, DISPLAY_WORKSPACE_TRACE_COOKIE);
Trace.beginAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
DISPLAY_ALL_APPS_TRACE_COOKIE);
}
Object traceToken = TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT,
TraceHelper.FLAG_UI_EVENT);
if (DEBUG_STRICT_MODE) {
@@ -2584,6 +2598,7 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
}
@Override
@TargetApi(Build.VERSION_CODES.S)
public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
mSynchronouslyBoundPages = boundPages;
mPagesToBindSynchronously = new IntSet();
@@ -2606,6 +2621,10 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
executor.onLoadAnimationCompleted();
}
executor.attachTo(this);
if (Utilities.ATLEAST_S) {
Trace.endAsyncSection(DISPLAY_WORKSPACE_TRACE_METHOD_NAME,
DISPLAY_WORKSPACE_TRACE_COOKIE);
}
}
/**
@@ -2669,9 +2688,14 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
* Implementation of the method from LauncherModel.Callbacks.
*/
@Override
@TargetApi(Build.VERSION_CODES.S)
public void bindAllApplications(AppInfo[] apps, int flags) {
mAppsView.getAppsStore().setApps(apps, flags);
PopupContainerWithArrow.dismissInvalidPopup(this);
if (Utilities.ATLEAST_S) {
Trace.endAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
DISPLAY_ALL_APPS_TRACE_COOKIE);
}
}
/**
@@ -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");
@@ -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.
@@ -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,9 +278,11 @@ public class LoaderCursor extends CursorWrapper {
info.user = user;
info.intent = newIntent;
mIconCache.getTitleAndIcon(info, mActivityInfo, useLowResIcon);
if (mIconCache.isDefaultIcon(info.bitmap, user)) {
loadIcon(info);
if (loadIcon) {
mIconCache.getTitleAndIcon(info, mActivityInfo, useLowResIcon);
if (mIconCache.isDefaultIcon(info.bitmap, user)) {
loadIcon(info);
}
}
if (mActivityInfo != null) {
@@ -43,6 +43,7 @@ import android.content.pm.ShortcutInfo;
import android.graphics.Point;
import android.net.Uri;
import android.os.Bundle;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
@@ -71,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;
@@ -197,7 +199,12 @@ public class LoaderTask implements Runnable {
TimingLogger logger = new TimingLogger(TAG, "run");
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
loadWorkspace(allShortcuts);
Trace.beginSection("LoadWorkspace");
try {
loadWorkspace(allShortcuts);
} finally {
Trace.endSection();
}
logASplit(logger, "loadWorkspace");
// Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
@@ -225,7 +232,13 @@ public class LoaderTask implements Runnable {
verifyNotStopped();
// second step
List<LauncherActivityInfo> allActivityList = loadAllApps();
Trace.beginSection("LoadAllApps");
List<LauncherActivityInfo> allActivityList;
try {
allActivityList = loadAllApps();
} finally {
Trace.endSection();
}
logASplit(logger, "loadAllApps");
verifyNotStopped();
@@ -408,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 {
@@ -530,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) {
@@ -582,6 +599,8 @@ public class LoaderTask implements Runnable {
}
if (info != null) {
iconRequestInfos.add(c.createIconRequestInfo(info, useLowResIcon));
c.applyCommonProperties(info);
info.intent = intent;
@@ -799,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);
}
@@ -0,0 +1,101 @@
/*
* 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;
}
}
}
}