From 6a41d56b50d7600f2e09c38b4924f5218962d656 Mon Sep 17 00:00:00 2001 From: Pinyao Ting Date: Tue, 10 May 2022 23:47:36 +0000 Subject: [PATCH] Log the reason why a WorkspaceItemInfo was removed In the past we've seen a WorkspaceItem disappeared from the workspace but wasn't able to determine why it was removed. This CL includes the reason why it was removed in the error log, which hopefully would help us debugging similar issues in the future. Bug: 231239260 Test: make Change-Id: Iba3d57568c9b3e011a6b65b26f0d4170d42fe1a5 --- .../android/launcher3/DeleteDropTarget.java | 2 +- src/com/android/launcher3/Launcher.java | 29 ++++++++++++++----- src/com/android/launcher3/LauncherModel.java | 4 ++- src/com/android/launcher3/Workspace.java | 8 +++-- .../launcher3/folder/LauncherDelegate.java | 3 +- .../launcher3/model/BaseModelUpdateTask.java | 7 +++-- .../android/launcher3/model/ModelWriter.java | 22 +++++++++----- .../launcher3/model/PackageUpdatedTask.java | 7 +++-- .../launcher3/model/ShortcutsChangedTask.java | 3 +- .../model/UserLockStateChangedTask.java | 4 ++- .../launcher3/touch/ItemClickHandler.java | 6 ++-- .../widget/LauncherAppWidgetHostView.java | 3 +- 12 files changed, 67 insertions(+), 31 deletions(-) diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java index 477964a6f4..371bb80484 100644 --- a/src/com/android/launcher3/DeleteDropTarget.java +++ b/src/com/android/launcher3/DeleteDropTarget.java @@ -160,7 +160,7 @@ public class DeleteDropTarget extends ButtonDropTarget { // Remove the item from launcher and the db, we can ignore the containerInfo in this call // because we already remove the drag view from the folder (if the drag originated from // a folder) in Folder.beginDrag() - mLauncher.removeItem(view, item, true /* deleteFromDb */); + mLauncher.removeItem(view, item, true /* deleteFromDb */, "removed by accessibility drop"); mLauncher.getWorkspace().stripEmptyScreens(); mLauncher.getDragLayer() .announceForAccessibility(getContext().getString(R.string.item_removed)); diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 135b88d5b7..11bc11f778 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1956,6 +1956,19 @@ public class Launcher extends StatefulActivity * @param deleteFromDb whether or not to delete this item from the db. */ public boolean removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb) { + return removeItem(v, itemInfo, deleteFromDb, null); + } + + /** + * Unbinds the view for the specified item, and removes the item and all its children. + * + * @param v the view being removed. + * @param itemInfo the {@link ItemInfo} for this view. + * @param deleteFromDb whether or not to delete this item from the db. + * @param reason the resaon for removal. + */ + public boolean removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb, + @Nullable final String reason) { if (itemInfo instanceof WorkspaceItemInfo) { // Remove the shortcut from the folder before removing it from launcher View folderIcon = mWorkspace.getHomescreenIconByItemId(itemInfo.container); @@ -1965,7 +1978,7 @@ public class Launcher extends StatefulActivity mWorkspace.removeWorkspaceItem(v); } if (deleteFromDb) { - getModelWriter().deleteItemFromDatabase(itemInfo); + getModelWriter().deleteItemFromDatabase(itemInfo, reason); } } else if (itemInfo instanceof FolderInfo) { final FolderInfo folderInfo = (FolderInfo) itemInfo; @@ -1980,7 +1993,7 @@ public class Launcher extends StatefulActivity final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo; mWorkspace.removeWorkspaceItem(v); if (deleteFromDb) { - getModelWriter().deleteWidgetInfo(widgetInfo, getAppWidgetHost()); + getModelWriter().deleteWidgetInfo(widgetInfo, getAppWidgetHost(), reason); } } else { return false; @@ -2399,8 +2412,7 @@ public class Launcher extends StatefulActivity if (FeatureFlags.IS_STUDIO_BUILD) { throw (new RuntimeException(desc)); } else { - Log.d(TAG, desc); - getModelWriter().deleteItemFromDatabase(item); + getModelWriter().deleteItemFromDatabase(item, desc); if (TestProtocol.sDebugTracing) { Log.d(TestProtocol.MISSING_PROMISE_ICON, TAG + "bindItems failed for item=" + item); @@ -2480,7 +2492,8 @@ public class Launcher extends StatefulActivity if (item.hasOptionFlag(LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET)) { item.providerName = QsbContainerView.getSearchComponentName(this); if (item.providerName == null) { - getModelWriter().deleteItemFromDatabase(item); + getModelWriter().deleteItemFromDatabase(item, + "search widget removed because search component cannot be found"); return null; } } @@ -2531,10 +2544,10 @@ public class Launcher extends StatefulActivity if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) && (item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED)) { if (appWidgetInfo == null) { - FileLog.d(TAG, "Removing restored widget: id=" + item.appWidgetId + getModelWriter().deleteItemFromDatabase(item, + "Removing restored widget: id=" + item.appWidgetId + " belongs to component " + item.providerName + " user " + item.user + ", as the provider is null and " + removalReason); - getModelWriter().deleteItemFromDatabase(item); return null; } @@ -2604,7 +2617,7 @@ public class Launcher extends StatefulActivity // Verify that we own the widget if (appWidgetInfo == null) { FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId); - getModelWriter().deleteWidgetInfo(item, getAppWidgetHost()); + getModelWriter().deleteWidgetInfo(item, getAppWidgetHost(), removalReason); return null; } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 5c5c101355..de0d3002e7 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -477,7 +477,9 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi } if (!removedIds.isEmpty()) { - deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedIds)); + deleteAndBindComponentsRemoved( + ItemInfoMatcher.ofItemIds(removedIds), + "removed because install session failed"); } } }); diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 6c091f08c1..93f3d9f061 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -3068,7 +3068,8 @@ public class Workspace extends PagedView if (info instanceof LauncherAppWidgetInfo) { LauncherAppWidgetInfo appWidgetInfo = (LauncherAppWidgetInfo) info; if (appWidgetInfo.appWidgetId == appWidgetId) { - mLauncher.removeItem(view, appWidgetInfo, true); + mLauncher.removeItem(view, appWidgetInfo, true, + "widget is removed in response to widget remove broadcast"); return true; } } @@ -3318,8 +3319,9 @@ public class Workspace extends PagedView * * @param matcher the matcher generated by the caller. */ - public void persistRemoveItemsByMatcher(Predicate matcher) { - mLauncher.getModelWriter().deleteItemsFromDatabase(matcher); + public void persistRemoveItemsByMatcher(Predicate matcher, + @Nullable final String reason) { + mLauncher.getModelWriter().deleteItemsFromDatabase(matcher, reason); removeItemsByMatcher(matcher); } diff --git a/src/com/android/launcher3/folder/LauncherDelegate.java b/src/com/android/launcher3/folder/LauncherDelegate.java index c5b3913a2b..1f0a011b39 100644 --- a/src/com/android/launcher3/folder/LauncherDelegate.java +++ b/src/com/android/launcher3/folder/LauncherDelegate.java @@ -100,7 +100,8 @@ public class LauncherDelegate { } // Remove the folder - mLauncher.removeItem(folder.mFolderIcon, info, true /* deleteFromDb */); + mLauncher.removeItem(folder.mFolderIcon, info, true /* deleteFromDb */, + "folder removed because there's only 1 item in it"); if (folder.mFolderIcon instanceof DropTarget) { folder.mDragController.removeDropTarget((DropTarget) folder.mFolderIcon); } diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java index 832c1dd166..2a6a6919e0 100644 --- a/src/com/android/launcher3/model/BaseModelUpdateTask.java +++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java @@ -17,6 +17,8 @@ package com.android.launcher3.model; import android.util.Log; +import androidx.annotation.Nullable; + import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherModel.CallbackTask; @@ -128,8 +130,9 @@ public abstract class BaseModelUpdateTask implements ModelUpdateTask { scheduleCallbackTask(c -> c.bindAllWidgets(widgets)); } - public void deleteAndBindComponentsRemoved(final Predicate matcher) { - getModelWriter().deleteItemsFromDatabase(matcher); + public void deleteAndBindComponentsRemoved(final Predicate matcher, + @Nullable final String reason) { + getModelWriter().deleteItemsFromDatabase(matcher, reason); // Call the components-removed callback scheduleCallbackTask(c -> c.bindWorkspaceComponentsRemoved(matcher)); diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java index 015abe94bc..0a68d4aa34 100644 --- a/src/com/android/launcher3/model/ModelWriter.java +++ b/src/com/android/launcher3/model/ModelWriter.java @@ -23,8 +23,10 @@ import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.net.Uri; +import android.text.TextUtils; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.LauncherAppState; @@ -272,27 +274,30 @@ public class ModelWriter { /** * Removes the specified item from the database */ - public void deleteItemFromDatabase(ItemInfo item) { - deleteItemsFromDatabase(Arrays.asList(item)); + public void deleteItemFromDatabase(ItemInfo item, @Nullable final String reason) { + deleteItemsFromDatabase(Arrays.asList(item), reason); } /** * Removes all the items from the database matching {@param matcher}. */ - public void deleteItemsFromDatabase(Predicate matcher) { + public void deleteItemsFromDatabase(@NonNull final Predicate matcher, + @Nullable final String reason) { deleteItemsFromDatabase(StreamSupport.stream(mBgDataModel.itemsIdMap.spliterator(), false) - .filter(matcher).collect(Collectors.toList())); + .filter(matcher).collect(Collectors.toList()), reason); } /** * Removes the specified items from the database */ - public void deleteItemsFromDatabase(final Collection items) { + public void deleteItemsFromDatabase(final Collection items, + @Nullable final String reason) { ModelVerifier verifier = new ModelVerifier(); FileLog.d(TAG, "removing items from db " + items.stream().map( (item) -> item.getTargetComponent() == null ? "" : item.getTargetComponent().getPackageName()).collect( - Collectors.joining(","))); + Collectors.joining(",")) + + ". Reason: [" + (TextUtils.isEmpty(reason) ? "unknown" : reason) + "]"); notifyDelete(items); enqueueDeleteRunnable(() -> { for (ItemInfo item : items) { @@ -328,14 +333,15 @@ public class ModelWriter { /** * Deletes the widget info and the widget id. */ - public void deleteWidgetInfo(final LauncherAppWidgetInfo info, LauncherAppWidgetHost host) { + public void deleteWidgetInfo(final LauncherAppWidgetInfo info, LauncherAppWidgetHost host, + @Nullable final String reason) { notifyDelete(Collections.singleton(info)); if (host != null && !info.isCustomWidget() && info.isWidgetIdAllocated()) { // Deleting an app widget ID is a void call but writes to disk before returning // to the caller... enqueueDeleteRunnable(() -> host.deleteAppWidgetId(info.appWidgetId)); } - deleteItemFromDatabase(info); + deleteItemFromDatabase(info, reason); } private void notifyDelete(Collection items) { diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index 239dd4565a..489bc38376 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -311,7 +311,9 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { bindUpdatedWorkspaceItems(updatedWorkspaceItems); if (!removedShortcuts.isEmpty()) { - deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedShortcuts)); + deleteAndBindComponentsRemoved( + ItemInfoMatcher.ofItemIds(removedShortcuts), + "removed because the target component is invalid"); } if (!widgets.isEmpty()) { @@ -340,7 +342,8 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { Predicate removeMatch = ItemInfoMatcher.ofPackages(removedPackages, mUser) .or(ItemInfoMatcher.ofComponents(removedComponents, mUser)) .and(ItemInfoMatcher.ofItemIds(forceKeepShortcuts).negate()); - deleteAndBindComponentsRemoved(removeMatch); + deleteAndBindComponentsRemoved(removeMatch, + "removed because the corresponding package or component is removed"); // Remove any queued items from the install queue ItemInstallQueue.INSTANCE.get(context) diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java index 4296d32f24..1026e0bd5b 100644 --- a/src/com/android/launcher3/model/ShortcutsChangedTask.java +++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java @@ -108,7 +108,8 @@ public class ShortcutsChangedTask extends BaseModelUpdateTask { deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys( nonPinnedIds.stream() .map(id -> new ShortcutKey(mPackageName, mUser, id)) - .collect(Collectors.toSet()))); + .collect(Collectors.toSet())), + "removed because the shortcut is no longer available in shortcut service"); } } diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java index 5048e13e3e..1565b19f4c 100644 --- a/src/com/android/launcher3/model/UserLockStateChangedTask.java +++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java @@ -96,7 +96,9 @@ public class UserLockStateChangedTask extends BaseModelUpdateTask { } bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos); if (!removedKeys.isEmpty()) { - deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(removedKeys)); + deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(removedKeys), + "removed during unlock because it's no longer available" + + " (possibly due to clear data)"); } // Remove shortcut id map for that user diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java index c4d2bc44c8..e95a787cfd 100644 --- a/src/com/android/launcher3/touch/ItemClickHandler.java +++ b/src/com/android/launcher3/touch/ItemClickHandler.java @@ -176,7 +176,8 @@ public class ItemClickHandler { .setNeutralButton(R.string.abandoned_clean_this, (d, i) -> launcher.getWorkspace() .persistRemoveItemsByMatcher(ItemInfoMatcher.ofPackages( - Collections.singleton(packageName), user))) + Collections.singleton(packageName), user), + "user explicitly removes the promise app icon")) .create().show(); } @@ -259,7 +260,8 @@ public class ItemClickHandler { // Remove the icon if launcher is successfully initialized launcher.getWorkspace().persistRemoveItemsByMatcher(ItemInfoMatcher .ofShortcutKeys(Collections.singleton(ShortcutKey - .fromItemInfo(shortcut)))); + .fromItemInfo(shortcut))), + "user explicitly removes disabled shortcut"); }) .create() .show(); diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java index 53c772fec2..08651523a6 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java @@ -457,7 +457,8 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView } // Remove and rebind the current widget (which was inflated in the wrong // orientation), but don't delete it from the database - mLauncher.removeItem(this, info, false /* deleteFromDb */); + mLauncher.removeItem(this, info, false /* deleteFromDb */, + "widget removed because of configuration change"); mLauncher.bindAppWidget(info); }