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
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 477964a..371bb80 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -160,7 +160,7 @@
         // 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 135b88d..11bc11f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1956,6 +1956,19 @@
      * @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 @@
                 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 @@
             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 @@
                     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 @@
         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 @@
             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 @@
                 // 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 5c5c101..de0d300 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -477,7 +477,9 @@
                 }
 
                 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 6c091f0..93f3d9f 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -3068,7 +3068,8 @@
             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 @@
      *
      * @param matcher  the matcher generated by the caller.
      */
-    public void persistRemoveItemsByMatcher(Predicate<ItemInfo> matcher) {
-        mLauncher.getModelWriter().deleteItemsFromDatabase(matcher);
+    public void persistRemoveItemsByMatcher(Predicate<ItemInfo> 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 c5b3913..1f0a011 100644
--- a/src/com/android/launcher3/folder/LauncherDelegate.java
+++ b/src/com/android/launcher3/folder/LauncherDelegate.java
@@ -100,7 +100,8 @@
                     }
 
                     // 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 832c1dd..2a6a691 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -17,6 +17,8 @@
 
 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 @@
         scheduleCallbackTask(c -> c.bindAllWidgets(widgets));
     }
 
-    public void deleteAndBindComponentsRemoved(final Predicate<ItemInfo> matcher) {
-        getModelWriter().deleteItemsFromDatabase(matcher);
+    public void deleteAndBindComponentsRemoved(final Predicate<ItemInfo> 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 015abe9..0a68d4a 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -23,8 +23,10 @@
 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 @@
     /**
      * 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<ItemInfo> matcher) {
+    public void deleteItemsFromDatabase(@NonNull final Predicate<ItemInfo> 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<? extends ItemInfo> items) {
+    public void deleteItemsFromDatabase(final Collection<? extends ItemInfo> 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 @@
     /**
      * 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<? extends ItemInfo> items) {
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 239dd45..489bc38 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -311,7 +311,9 @@
 
             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 @@
             Predicate<ItemInfo> 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 4296d32..1026e0b 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -108,7 +108,8 @@
                 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 5048e13..1565b19 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -96,7 +96,9 @@
         }
         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 c4d2bc4..e95a787 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -176,7 +176,8 @@
                 .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 @@
                             // 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 53c772f..0865152 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -457,7 +457,8 @@
         }
         // 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);
     }