Loading bliss/src/foundation/e/bliss/widgets/WidgetContainer.kt +56 −6 Original line number Diff line number Diff line Loading @@ -327,6 +327,17 @@ class WidgetContainer(context: Context, attrs: AttributeSet?) : rebindWidgets() } } override fun onPackagesAvailable( packageNames: Array<String?>?, user: UserHandle?, replacing: Boolean, ) { if (!shouldAttemptWidgetIdRepair(context)) return if (::widgetsDbHelper.isInitialized && ::widgetsAdapter.isInitialized) { rebindWidgets() } } } private var initialWidgetsAdded: Boolean Loading Loading @@ -422,15 +433,21 @@ class WidgetContainer(context: Context, attrs: AttributeSet?) : fun rebindWidgets(backup: Boolean = false) { widgetsAdapter.setWidgets(mutableListOf()) if (!backup) { val dbWidgets = widgetsDbHelper.getWidgets().apply { sortedBy { it.position } forEach { addView(it.widgetId) } val dbWidgets = widgetsDbHelper.getWidgets().sortedBy { it.position } val keepWidgetIds = dbWidgets.mapTo(hashSetOf()) { it.widgetId } if (shouldAttemptWidgetIdRepair(context)) { dbWidgets.forEach { restoreWidgetFromDb(it, keepWidgetIds) } if (keepWidgetIds.all { mWidgetManager.getAppWidgetInfo(it) != null }) { LauncherPrefs.get(context) .put(LauncherPrefs.NEEDS_WIDGET_REBIND_AFTER_RESTORE, false) } } else { dbWidgets.forEach { widgetInfo -> addView(widgetInfo.widgetId) } } // Remove all widgets not present in db mWidgetHost.appWidgetIds .filter { id -> dbWidgets.all { info -> info.widgetId != id } } .filter { id -> !keepWidgetIds.contains(id) } .forEach { mWidgetHost.deleteAppWidgetId(it) } } else { if (mOldWidgets.isNotEmpty()) { Loading Loading @@ -468,8 +485,41 @@ class WidgetContainer(context: Context, attrs: AttributeSet?) : addView(widgetId) } } else { mWidgetHost.deleteAppWidgetId(id) mWidgetHost.deleteAppWidgetId(widgetId) } } private fun restoreWidgetFromDb(widgetInfo: WidgetInfo, keepWidgetIds: MutableSet<Int>) { if (mWidgetManager.getAppWidgetInfo(widgetInfo.widgetId) != null) { addView(widgetInfo.widgetId) return } // Seedvault/app-data restore can bring back our widget database, but appWidgetIds are // allocated and persisted by the system; they often don't survive restores. val newWidgetId = mWidgetHost.allocateAppWidgetId() val isWidgetBound = mWidgetManager.bindAppWidgetIdIfAllowed(newWidgetId, widgetInfo.component) if (!isWidgetBound) { mWidgetHost.deleteAppWidgetId(newWidgetId) Logger.e( TAG, "Could not rebind restored widget ${widgetInfo.component.flattenToString()} (oldId=${widgetInfo.widgetId})", ) return } if (newWidgetId != widgetInfo.widgetId) { widgetsDbHelper.updateWidgetId(widgetInfo.widgetId, newWidgetId) keepWidgetIds.remove(widgetInfo.widgetId) keepWidgetIds.add(newWidgetId) mWidgetHost.deleteAppWidgetId(widgetInfo.widgetId) } addView(newWidgetId) } private fun shouldAttemptWidgetIdRepair(context: Context): Boolean { return LauncherPrefs.get(context).get(LauncherPrefs.NEEDS_WIDGET_REBIND_AFTER_RESTORE) } private fun addView(widgetId: Int, backup: Boolean = false) { Loading bliss/src/foundation/e/bliss/widgets/WidgetsDbHelper.kt +21 −0 Original line number Diff line number Diff line Loading @@ -94,6 +94,27 @@ class WidgetsDbHelper(context: Context) : } } fun updateWidgetId(oldId: Int, newId: Int) { if (oldId == newId) { return } Logger.d(TAG, "Updating widgetId $oldId -> $newId") writableDatabase.use { db -> db.beginTransaction() try { // Avoid primary-key conflicts if the new id already exists for some reason. db.execSQL("DELETE FROM $WIDGETS_TABLE WHERE widgetId = ?", arrayOf(newId)) db.execSQL( "UPDATE $WIDGETS_TABLE SET widgetId = ? WHERE widgetId = ?", arrayOf(newId, oldId), ) db.setTransactionSuccessful() } finally { db.endTransaction() } } } fun updateHeight(id: Int, height: Int) { Logger.d(TAG, "Updating widget $id height to $height") writableDatabase.use { db -> Loading res/xml/backupscheme.xml +7 −5 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <full-backup-content xmlns:android="http://schemas.android.com/apk/res/android"> <full-backup-content> <!-- 1.9.0 Database --> <include domain="database" path="launcher_db" /> <!-- QSB Widgets Database --> <include domain="database" path="qsb_widgets.db" /> <include domain="database" path="qsb_widgets" /> <include domain="database" path="app_icons.db" /> <include domain="database" path="launcher.db" /> <include domain="database" path="launcher_5_by_8.db" /> <include domain="database" path="launcher_5_by_7.db" /> <include domain="database" path="launcher_6_by_6.db" /> <include domain="database" path="launcher_6_by_5.db" /> <include domain="database" path="launcher_5_by_6.db" /> <include domain="database" path="launcher_4_by_6.db" /> <include domain="database" path="launcher_4_by_5.db" /> <include domain="database" path="launcher_4_by_6.db" /> <include domain="database" path="launcher_4_by_4.db" /> <include domain="database" path="launcher_3_by_3.db" /> <include domain="database" path="launcher_2_by_2.db" /> <include domain="database" path="launcher_7_by_3.db" /> <include domain="database" path="launcher_8_by_3.db" /> <include domain="sharedpref" path="com.android.launcher3.device.prefs.xml" /> <include domain="sharedpref" path="com.android.launcher3.prefs.xml" /> <include domain="sharedpref" path="plugin_prefs.xml" /> <include domain="file" path="downgrade_schema.json" /> </full-backup-content> No newline at end of file src/com/android/launcher3/LauncherAppState.java +25 −0 Original line number Diff line number Diff line Loading @@ -40,7 +40,9 @@ import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.ArchiveCompatibilityParams; import android.net.Uri; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; import androidx.annotation.Nullable; Loading @@ -53,6 +55,7 @@ import com.android.launcher3.icons.LauncherIconProvider; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.lineage.trust.HiddenAppsFilter; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.CompactWorkspaceAfterRestoreTask; import com.android.launcher3.model.ModelLauncherCallbacks; import com.android.launcher3.model.WidgetsFilterDataProvider; import com.android.launcher3.notification.NotificationListener; Loading Loading @@ -200,6 +203,7 @@ public class LauncherAppState implements SafeCloseable { mAppMonitor.onAppCreated(mContext); // Register an observer to notify Launcher about Private Space settings toggle. registerPrivateSpaceHideWhenLockListener(settingsCache); registerSetupCompleteListener(settingsCache); } public LauncherAppState(Context context, @Nullable String iconCacheFileName) { Loading @@ -225,6 +229,27 @@ public class LauncherAppState implements SafeCloseable { } } private void registerSetupCompleteListener(SettingsCache settingsCache) { // After restore, compact the workspace only once SUW is complete. Uri setupCompleteUri = Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE); SettingsCache.OnChangeListener setupCompleteListener = isSetupComplete -> { if (!isSetupComplete || !LauncherPrefs.get(mContext).get( LauncherPrefs.NEEDS_WORKSPACE_REORDER_AFTER_RESTORE)) { return; } mModel.enqueueModelUpdateTask(new CompactWorkspaceAfterRestoreTask()); }; settingsCache.register(setupCompleteUri, setupCompleteListener); setupCompleteListener.onSettingsChanged( settingsCache.getValue(setupCompleteUri, 0)); mOnTerminateCallback.add(() -> settingsCache.unregister(setupCompleteUri, setupCompleteListener)); } private void registerPrivateSpaceHideWhenLockListener(SettingsCache settingsCache) { SettingsCache.OnChangeListener psHideWhenLockChangedListener = this::onPrivateSpaceHideWhenLockChanged; Loading src/com/android/launcher3/LauncherBackupAgent.java +4 −0 Original line number Diff line number Diff line package com.android.launcher3; import static com.android.launcher3.LauncherPrefs.NEEDS_WIDGET_REBIND_AFTER_RESTORE; import static com.android.launcher3.LauncherPrefs.NEEDS_WORKSPACE_REORDER_AFTER_RESTORE; import static com.android.launcher3.LauncherPrefs.NO_DB_FILES_RESTORED; import android.app.backup.BackupAgent; Loading Loading @@ -54,6 +56,8 @@ public class LauncherBackupAgent extends BackupAgent { public void onRestoreFinished() { RestoreDbTask.setPending(this); FileLog.d(TAG, "onRestoreFinished: set pending for RestoreDbTask"); LauncherPrefs.get(this).putSync(NEEDS_WIDGET_REBIND_AFTER_RESTORE.to(true)); LauncherPrefs.get(this).putSync(NEEDS_WORKSPACE_REORDER_AFTER_RESTORE.to(true)); markIfFilesWereNotActuallyRestored(); } Loading Loading
bliss/src/foundation/e/bliss/widgets/WidgetContainer.kt +56 −6 Original line number Diff line number Diff line Loading @@ -327,6 +327,17 @@ class WidgetContainer(context: Context, attrs: AttributeSet?) : rebindWidgets() } } override fun onPackagesAvailable( packageNames: Array<String?>?, user: UserHandle?, replacing: Boolean, ) { if (!shouldAttemptWidgetIdRepair(context)) return if (::widgetsDbHelper.isInitialized && ::widgetsAdapter.isInitialized) { rebindWidgets() } } } private var initialWidgetsAdded: Boolean Loading Loading @@ -422,15 +433,21 @@ class WidgetContainer(context: Context, attrs: AttributeSet?) : fun rebindWidgets(backup: Boolean = false) { widgetsAdapter.setWidgets(mutableListOf()) if (!backup) { val dbWidgets = widgetsDbHelper.getWidgets().apply { sortedBy { it.position } forEach { addView(it.widgetId) } val dbWidgets = widgetsDbHelper.getWidgets().sortedBy { it.position } val keepWidgetIds = dbWidgets.mapTo(hashSetOf()) { it.widgetId } if (shouldAttemptWidgetIdRepair(context)) { dbWidgets.forEach { restoreWidgetFromDb(it, keepWidgetIds) } if (keepWidgetIds.all { mWidgetManager.getAppWidgetInfo(it) != null }) { LauncherPrefs.get(context) .put(LauncherPrefs.NEEDS_WIDGET_REBIND_AFTER_RESTORE, false) } } else { dbWidgets.forEach { widgetInfo -> addView(widgetInfo.widgetId) } } // Remove all widgets not present in db mWidgetHost.appWidgetIds .filter { id -> dbWidgets.all { info -> info.widgetId != id } } .filter { id -> !keepWidgetIds.contains(id) } .forEach { mWidgetHost.deleteAppWidgetId(it) } } else { if (mOldWidgets.isNotEmpty()) { Loading Loading @@ -468,8 +485,41 @@ class WidgetContainer(context: Context, attrs: AttributeSet?) : addView(widgetId) } } else { mWidgetHost.deleteAppWidgetId(id) mWidgetHost.deleteAppWidgetId(widgetId) } } private fun restoreWidgetFromDb(widgetInfo: WidgetInfo, keepWidgetIds: MutableSet<Int>) { if (mWidgetManager.getAppWidgetInfo(widgetInfo.widgetId) != null) { addView(widgetInfo.widgetId) return } // Seedvault/app-data restore can bring back our widget database, but appWidgetIds are // allocated and persisted by the system; they often don't survive restores. val newWidgetId = mWidgetHost.allocateAppWidgetId() val isWidgetBound = mWidgetManager.bindAppWidgetIdIfAllowed(newWidgetId, widgetInfo.component) if (!isWidgetBound) { mWidgetHost.deleteAppWidgetId(newWidgetId) Logger.e( TAG, "Could not rebind restored widget ${widgetInfo.component.flattenToString()} (oldId=${widgetInfo.widgetId})", ) return } if (newWidgetId != widgetInfo.widgetId) { widgetsDbHelper.updateWidgetId(widgetInfo.widgetId, newWidgetId) keepWidgetIds.remove(widgetInfo.widgetId) keepWidgetIds.add(newWidgetId) mWidgetHost.deleteAppWidgetId(widgetInfo.widgetId) } addView(newWidgetId) } private fun shouldAttemptWidgetIdRepair(context: Context): Boolean { return LauncherPrefs.get(context).get(LauncherPrefs.NEEDS_WIDGET_REBIND_AFTER_RESTORE) } private fun addView(widgetId: Int, backup: Boolean = false) { Loading
bliss/src/foundation/e/bliss/widgets/WidgetsDbHelper.kt +21 −0 Original line number Diff line number Diff line Loading @@ -94,6 +94,27 @@ class WidgetsDbHelper(context: Context) : } } fun updateWidgetId(oldId: Int, newId: Int) { if (oldId == newId) { return } Logger.d(TAG, "Updating widgetId $oldId -> $newId") writableDatabase.use { db -> db.beginTransaction() try { // Avoid primary-key conflicts if the new id already exists for some reason. db.execSQL("DELETE FROM $WIDGETS_TABLE WHERE widgetId = ?", arrayOf(newId)) db.execSQL( "UPDATE $WIDGETS_TABLE SET widgetId = ? WHERE widgetId = ?", arrayOf(newId, oldId), ) db.setTransactionSuccessful() } finally { db.endTransaction() } } } fun updateHeight(id: Int, height: Int) { Logger.d(TAG, "Updating widget $id height to $height") writableDatabase.use { db -> Loading
res/xml/backupscheme.xml +7 −5 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <full-backup-content xmlns:android="http://schemas.android.com/apk/res/android"> <full-backup-content> <!-- 1.9.0 Database --> <include domain="database" path="launcher_db" /> <!-- QSB Widgets Database --> <include domain="database" path="qsb_widgets.db" /> <include domain="database" path="qsb_widgets" /> <include domain="database" path="app_icons.db" /> <include domain="database" path="launcher.db" /> <include domain="database" path="launcher_5_by_8.db" /> <include domain="database" path="launcher_5_by_7.db" /> <include domain="database" path="launcher_6_by_6.db" /> <include domain="database" path="launcher_6_by_5.db" /> <include domain="database" path="launcher_5_by_6.db" /> <include domain="database" path="launcher_4_by_6.db" /> <include domain="database" path="launcher_4_by_5.db" /> <include domain="database" path="launcher_4_by_6.db" /> <include domain="database" path="launcher_4_by_4.db" /> <include domain="database" path="launcher_3_by_3.db" /> <include domain="database" path="launcher_2_by_2.db" /> <include domain="database" path="launcher_7_by_3.db" /> <include domain="database" path="launcher_8_by_3.db" /> <include domain="sharedpref" path="com.android.launcher3.device.prefs.xml" /> <include domain="sharedpref" path="com.android.launcher3.prefs.xml" /> <include domain="sharedpref" path="plugin_prefs.xml" /> <include domain="file" path="downgrade_schema.json" /> </full-backup-content> No newline at end of file
src/com/android/launcher3/LauncherAppState.java +25 −0 Original line number Diff line number Diff line Loading @@ -40,7 +40,9 @@ import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.ArchiveCompatibilityParams; import android.net.Uri; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; import androidx.annotation.Nullable; Loading @@ -53,6 +55,7 @@ import com.android.launcher3.icons.LauncherIconProvider; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.lineage.trust.HiddenAppsFilter; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.CompactWorkspaceAfterRestoreTask; import com.android.launcher3.model.ModelLauncherCallbacks; import com.android.launcher3.model.WidgetsFilterDataProvider; import com.android.launcher3.notification.NotificationListener; Loading Loading @@ -200,6 +203,7 @@ public class LauncherAppState implements SafeCloseable { mAppMonitor.onAppCreated(mContext); // Register an observer to notify Launcher about Private Space settings toggle. registerPrivateSpaceHideWhenLockListener(settingsCache); registerSetupCompleteListener(settingsCache); } public LauncherAppState(Context context, @Nullable String iconCacheFileName) { Loading @@ -225,6 +229,27 @@ public class LauncherAppState implements SafeCloseable { } } private void registerSetupCompleteListener(SettingsCache settingsCache) { // After restore, compact the workspace only once SUW is complete. Uri setupCompleteUri = Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE); SettingsCache.OnChangeListener setupCompleteListener = isSetupComplete -> { if (!isSetupComplete || !LauncherPrefs.get(mContext).get( LauncherPrefs.NEEDS_WORKSPACE_REORDER_AFTER_RESTORE)) { return; } mModel.enqueueModelUpdateTask(new CompactWorkspaceAfterRestoreTask()); }; settingsCache.register(setupCompleteUri, setupCompleteListener); setupCompleteListener.onSettingsChanged( settingsCache.getValue(setupCompleteUri, 0)); mOnTerminateCallback.add(() -> settingsCache.unregister(setupCompleteUri, setupCompleteListener)); } private void registerPrivateSpaceHideWhenLockListener(SettingsCache settingsCache) { SettingsCache.OnChangeListener psHideWhenLockChangedListener = this::onPrivateSpaceHideWhenLockChanged; Loading
src/com/android/launcher3/LauncherBackupAgent.java +4 −0 Original line number Diff line number Diff line package com.android.launcher3; import static com.android.launcher3.LauncherPrefs.NEEDS_WIDGET_REBIND_AFTER_RESTORE; import static com.android.launcher3.LauncherPrefs.NEEDS_WORKSPACE_REORDER_AFTER_RESTORE; import static com.android.launcher3.LauncherPrefs.NO_DB_FILES_RESTORED; import android.app.backup.BackupAgent; Loading Loading @@ -54,6 +56,8 @@ public class LauncherBackupAgent extends BackupAgent { public void onRestoreFinished() { RestoreDbTask.setPending(this); FileLog.d(TAG, "onRestoreFinished: set pending for RestoreDbTask"); LauncherPrefs.get(this).putSync(NEEDS_WIDGET_REBIND_AFTER_RESTORE.to(true)); LauncherPrefs.get(this).putSync(NEEDS_WORKSPACE_REORDER_AFTER_RESTORE.to(true)); markIfFilesWereNotActuallyRestored(); } Loading