diff --git a/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt b/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt index bdac05df9e..063dad12e4 100644 --- a/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt +++ b/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt @@ -22,6 +22,8 @@ open class LauncherRestoreEventLogger : ResourceBasedOverride { const val RESTORE_ERROR_SHORTCUT_NOT_FOUND = "shortcut_not_found" const val RESTORE_ERROR_APP_NOT_INSTALLED = "app_not_installed" const val RESTORE_ERROR_WIDGETS_DISABLED = "widgets_disabled" + const val RESTORE_ERROR_PROFILE_NOT_RESTORED = "profile_not_restored" + const val RESTORE_ERROR_WIDGET_REMOVED = "widget_not_found" fun newInstance(context: Context?): LauncherRestoreEventLogger { return ResourceBasedOverride.Overrides.getObject( diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java index 20b2971f27..b4bdf5ff6b 100644 --- a/src/com/android/launcher3/provider/RestoreDbTask.java +++ b/src/com/android/launcher3/provider/RestoreDbTask.java @@ -18,12 +18,18 @@ package com.android.launcher3.provider; import static android.os.Process.myUserHandle; +import static com.android.launcher3.Flags.enableLauncherBrMetrics; import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY; import static com.android.launcher3.LauncherPrefs.APP_WIDGET_IDS; import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE; import static com.android.launcher3.LauncherPrefs.OLD_APP_WIDGET_IDS; import static com.android.launcher3.LauncherPrefs.RESTORE_DEVICE; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; +import static com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RESTORE_ERROR_PROFILE_NOT_RESTORED; +import static com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RESTORE_ERROR_WIDGETS_DISABLED; +import static com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RESTORE_ERROR_WIDGET_REMOVED; import static com.android.launcher3.provider.LauncherDbUtils.dropTable; import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID; @@ -53,6 +59,7 @@ import com.android.launcher3.LauncherPrefs; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.Utilities; +import com.android.launcher3.backuprestore.LauncherRestoreEventLogger; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.DeviceGridState; import com.android.launcher3.model.LoaderTask; @@ -123,8 +130,11 @@ public class RestoreDbTask { FileLog.d(TAG, "performRestore: starting restore from db"); try (SQLiteTransaction t = new SQLiteTransaction(db)) { RestoreDbTask task = new RestoreDbTask(); - task.sanitizeDB(context, controller, db, new BackupManager(context)); - task.restoreAppWidgetIdsIfExists(context, controller); + BackupManager backupManager = new BackupManager(context); + LauncherRestoreEventLogger restoreEventLogger = + LauncherRestoreEventLogger.Companion.newInstance(context); + task.sanitizeDB(context, controller, db, backupManager, restoreEventLogger); + task.restoreAppWidgetIdsIfExists(context, controller, restoreEventLogger); t.commit(); return true; } catch (Exception e) { @@ -147,7 +157,8 @@ public class RestoreDbTask { */ @VisibleForTesting protected int sanitizeDB(Context context, ModelDbController controller, SQLiteDatabase db, - BackupManager backupManager) throws Exception { + BackupManager backupManager, LauncherRestoreEventLogger restoreEventLogger) + throws Exception { logFavoritesTable(db, "Old Launcher Database before sanitizing:", null, null); // Primary user ids long myProfileId = controller.getSerialNumberForUser(myUserHandle()); @@ -186,6 +197,9 @@ public class RestoreDbTask { Arrays.fill(args, "?"); final String where = "profileId NOT IN (" + TextUtils.join(", ", Arrays.asList(args)) + ")"; logFavoritesTable(db, "items to delete from unrestored profiles:", where, profileIds); + if (enableLauncherBrMetrics()) { + reportUnrestoredProfiles(db, where, profileIds, restoreEventLogger); + } int itemsDeletedCount = db.delete(Favorites.TABLE_NAME, where, profileIds); FileLog.d(TAG, itemsDeletedCount + " total items from unrestored user(s) were deleted"); @@ -345,21 +359,24 @@ public class RestoreDbTask { DeviceGridState deviceGridState = new DeviceGridState(context); FileLog.d(TAG, "restore initiated from backup: DeviceGridState=" + deviceGridState); LauncherPrefs.get(context).putSync(RESTORE_DEVICE.to(deviceGridState.getDeviceType())); - LauncherPrefs.get(context).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(true)); + if (enableLauncherBrMetrics()) { + LauncherPrefs.get(context).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(true)); + } } @WorkerThread @VisibleForTesting - void restoreAppWidgetIdsIfExists(Context context, ModelDbController controller) { + void restoreAppWidgetIdsIfExists(Context context, ModelDbController controller, + LauncherRestoreEventLogger restoreEventLogger) { LauncherPrefs lp = LauncherPrefs.get(context); if (lp.has(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS)) { AppWidgetHost host = new AppWidgetHost(context, APPWIDGET_HOST_ID); - restoreAppWidgetIds(context, controller, + restoreAppWidgetIds(context, controller, restoreEventLogger, IntArray.fromConcatString(lp.get(OLD_APP_WIDGET_IDS)).toArray(), IntArray.fromConcatString(lp.get(APP_WIDGET_IDS)).toArray(), host); } else { - FileLog.d(TAG, "No app widget ids were received from backup to restore."); + FileLog.d(TAG, "Did not receive new app widget id map during Launcher restore"); } lp.remove(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS); @@ -370,10 +387,13 @@ public class RestoreDbTask { */ @WorkerThread private void restoreAppWidgetIds(Context context, ModelDbController controller, - int[] oldWidgetIds, int[] newWidgetIds, @NonNull AppWidgetHost host) { + LauncherRestoreEventLogger launcherRestoreEventLogger, int[] oldWidgetIds, + int[] newWidgetIds, @NonNull AppWidgetHost host) { if (WidgetsModel.GO_DISABLE_WIDGETS) { FileLog.e(TAG, "Skipping widget ID remap as widgets not supported"); host.deleteHost(); + launcherRestoreEventLogger.logFavoritesItemsRestoreFailed(Favorites.ITEM_TYPE_APPWIDGET, + oldWidgetIds.length, RESTORE_ERROR_WIDGETS_DISABLED); return; } if (!RestoreDbTask.isPending(context)) { @@ -437,11 +457,16 @@ public class RestoreDbTask { FileLog.d(TAG, "Deleting widgetId: " + newWidgetIds[i] + " with old id: " + oldWidgetId); host.deleteAppWidgetId(newWidgetIds[i]); + launcherRestoreEventLogger.logSingleFavoritesItemRestoreFailed( + ITEM_TYPE_APPWIDGET, + RESTORE_ERROR_WIDGET_REMOVED + ); } } } } + logFavoritesTable(controller.getDb(), "launcher db after remap widget ids", null, null); LauncherAppState app = LauncherAppState.getInstanceNoCreate(); if (app != null) { app.getModel().forceReload(); @@ -476,17 +501,16 @@ public class RestoreDbTask { StringBuilder builder = new StringBuilder(); builder.append("["); for (int i = 0; i < widgetIdList.size(); i++) { - builder.append("[") + builder.append("[appWidgetId=") .append(widgetIdList.get(i)) - .append(", ") + .append(", restoreFlag=") .append(widgetRestoreList.get(i)) - .append(", ") + .append(", profileId=") .append(widgetProfileIdList.get(i)) .append("]"); } builder.append("]"); - Log.d(TAG, "restoreAppWidgetIds: all widget ids in database: " - + builder); + Log.d(TAG, "restoreAppWidgetIds: all widget ids in database: " + builder); } catch (Exception ex) { Log.e(TAG, "Getting widget ids from the database failed", ex); } @@ -545,7 +569,7 @@ public class RestoreDbTask { */ public static void logFavoritesTable(SQLiteDatabase database, @NonNull String logHeader, String where, String[] profileIds) { - try (Cursor itemsToDelete = database.query( + try (Cursor cursor = database.query( /* table */ Favorites.TABLE_NAME, /* columns */ DB_COLUMNS_TO_LOG, /* selection */ where, @@ -554,26 +578,53 @@ public class RestoreDbTask { /* having */ null, /* orderBy */ null )) { - if (itemsToDelete.moveToFirst()) { - String[] columnNames = itemsToDelete.getColumnNames(); + if (cursor.moveToFirst()) { + String[] columnNames = cursor.getColumnNames(); StringBuilder stringBuilder = new StringBuilder(logHeader + "\n"); do { for (String columnName : columnNames) { stringBuilder.append(columnName) .append("=") - .append(itemsToDelete.getString( - itemsToDelete.getColumnIndex(columnName))) + .append(cursor.getString( + cursor.getColumnIndex(columnName))) .append(" "); } stringBuilder.append("\n"); - } while (itemsToDelete.moveToNext()); + } while (cursor.moveToNext()); FileLog.d(TAG, stringBuilder.toString()); } else { - FileLog.d(TAG, "logFavoritesTable: No items found from query for" + FileLog.d(TAG, "logFavoritesTable: No items found from query for " + "\"" + logHeader + "\""); } } catch (Exception e) { FileLog.e(TAG, "logFavoritesTable: Error reading from database", e); } } + + + /** + * Queries and reports the count of each itemType to be removed due to unrestored profiles. + * @param database The Launcher db to query from. + * @param where Query being used for to find unrestored profiles + * @param profileIds profile ids that were not restored + * @param restoreEventLogger Backup/Restore Logger to report metrics + */ + private void reportUnrestoredProfiles(SQLiteDatabase database, String where, + String[] profileIds, LauncherRestoreEventLogger restoreEventLogger) { + final String query = "SELECT itemType, COUNT(*) AS count FROM favorites WHERE " + + where + " GROUP BY itemType"; + try (Cursor cursor = database.rawQuery(query, profileIds)) { + if (cursor.moveToFirst()) { + do { + restoreEventLogger.logFavoritesItemsRestoreFailed( + cursor.getInt(cursor.getColumnIndexOrThrow(ITEM_TYPE)), + cursor.getInt(cursor.getColumnIndexOrThrow("count")), + RESTORE_ERROR_PROFILE_NOT_RESTORED + ); + } while (cursor.moveToNext()); + } + } catch (Exception e) { + FileLog.e(TAG, "reportUnrestoredProfiles: Error reading from database", e); + } + } } diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java index 10d9133902..733f1e9a97 100644 --- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java +++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java @@ -59,6 +59,7 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherPrefs; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.backuprestore.LauncherRestoreEventLogger; import com.android.launcher3.model.ModelDbController; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.LauncherModelHelper; @@ -90,6 +91,7 @@ public class RestoreDbTaskTest { private SQLiteDatabase mMockDb; private Cursor mMockCursor; private LauncherPrefs mPrefs; + private LauncherRestoreEventLogger mMockRestoreEventLogger; @Before public void setup() { @@ -100,6 +102,7 @@ public class RestoreDbTaskTest { mMockDb = mock(SQLiteDatabase.class); mMockCursor = mock(Cursor.class); mPrefs = new LauncherPrefs(mContext); + mMockRestoreEventLogger = mock(LauncherRestoreEventLogger.class); } @After @@ -178,7 +181,7 @@ public class RestoreDbTaskTest { assertEquals(10, getItemCountForProfile(db, myProfileId_old)); assertEquals(6, getItemCountForProfile(db, workProfileId_old)); - mTask.sanitizeDB(mContext, controller, controller.getDb(), bm); + mTask.sanitizeDB(mContext, controller, controller.getDb(), bm, mMockRestoreEventLogger); // All the data has been migrated to the new user ids assertEquals(0, getItemCountForProfile(db, myProfileId_old)); @@ -207,7 +210,7 @@ public class RestoreDbTaskTest { assertEquals(10, getItemCountForProfile(db, myProfileId_old)); assertEquals(6, getItemCountForProfile(db, workProfileId_old)); - mTask.sanitizeDB(mContext, controller, controller.getDb(), bm); + mTask.sanitizeDB(mContext, controller, controller.getDb(), bm, mMockRestoreEventLogger); // All the data has been migrated to the new user ids assertEquals(0, getItemCountForProfile(db, myProfileId_old)); @@ -219,7 +222,7 @@ public class RestoreDbTaskTest { @Test public void givenLauncherPrefsHasNoIds_whenRestoreAppWidgetIdsIfExists_thenIdsAreRemoved() { // When - mTask.restoreAppWidgetIdsIfExists(mContext, mMockController); + mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger); // Then assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse(); } @@ -235,7 +238,7 @@ public class RestoreDbTaskTest { // When setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds); - mTask.restoreAppWidgetIdsIfExists(mContext, mMockController); + mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger); // Then assertThat(expectedHost.getAppWidgetIds()).isEqualTo(expectedOldIds); @@ -257,7 +260,7 @@ public class RestoreDbTaskTest { // When setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds); - mTask.restoreAppWidgetIdsIfExists(mContext, mMockController); + mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger); // Then assertThat(expectedHost.getAppWidgetIds()).isEqualTo(expectedOldIds); @@ -280,12 +283,13 @@ public class RestoreDbTaskTest { when(mMockDb.query(any(), any(), any(), any(), any(), any(), any())) .thenReturn(mMockCursor); when(mMockCursor.moveToFirst()).thenReturn(true); + when(mMockCursor.getColumnNames()).thenReturn(new String[] {}); when(mMockCursor.isAfterLast()).thenReturn(true); RestoreDbTask.setPending(mContext); // When setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds); - mTask.restoreAppWidgetIdsIfExists(mContext, mMockController); + mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger); // Then assertThat(expectedHost.getAppWidgetIds()).isEqualTo(allExpectedIds);