Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bff92aec authored by Mohammed Althaf T's avatar Mohammed Althaf T 😊
Browse files

Reorder apps after setup restore

parent c7218a69
Loading
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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) {
@@ -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;
+2 −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;
@@ -56,6 +57,7 @@ public class LauncherBackupAgent extends BackupAgent {
        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();
    }

+7 −0
Original line number Diff line number Diff line
@@ -137,6 +137,13 @@ abstract class LauncherPrefs : SafeCloseable {
        @JvmField
        val NEEDS_WIDGET_REBIND_AFTER_RESTORE =
            nonRestorableItem("needs_widget_rebind_after_restore", false, EncryptionType.ENCRYPTED)
        @JvmField
        val NEEDS_WORKSPACE_REORDER_AFTER_RESTORE =
            nonRestorableItem(
                "needs_workspace_reorder_after_restore",
                false,
                EncryptionType.ENCRYPTED,
            )
        @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
        @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")

+129 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2026 e Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 *
 */
package com.android.launcher3.model

import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
import com.android.launcher3.ModelUpdateTask
import com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET
import com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID
import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.util.GridOccupancy
import com.android.launcher3.util.IntSet

class CompactWorkspaceAfterRestoreTask : ModelUpdateTask {

    override fun execute(taskController: ModelTaskController, dataModel: BgDataModel, apps: AllAppsList) {
        val context = taskController.app.context
        val model = taskController.app.model
        val prefs = LauncherPrefs.get(context)
        if (!prefs.get(LauncherPrefs.NEEDS_WORKSPACE_REORDER_AFTER_RESTORE)) return

        val idp = InvariantDeviceProfile.INSTANCE.get(context)
        val columns = idp.numColumnsFixed
        val rows = idp.numRowsFixed

        val screenIds = mutableListOf<Int>()
        val fixedItems = ArrayList<ItemInfo>()
        val movableItems = ArrayList<ItemInfo>()

        synchronized(dataModel) {
            val screens = dataModel.collectWorkspaceScreens()
            for (i in 0 until screens.size()) {
                screenIds.add(screens[i])
            }
            screenIds.sort()

            for (item in dataModel.itemsIdMap) {
                if (item.container != CONTAINER_DESKTOP) continue
                val isFixed = item.spanX > 1 || item.spanY > 1
                if (isFixed) {
                    fixedItems.add(item)
                } else if (item.spanX == 1 && item.spanY == 1) {
                    movableItems.add(item)
                }
            }
        }

        movableItems.sortWith(compareBy({ it.screenId }, { it.cellY }, { it.cellX }))

        val screensToExclude = IntSet()
        if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() && !SHOULD_SHOW_FIRST_PAGE_WIDGET) {
            screensToExclude.add(FIRST_SCREEN_ID)
        }

        val occupiedByScreen = HashMap<Int, GridOccupancy>(screenIds.size)
        fun occupancyFor(screenId: Int) =
            occupiedByScreen.getOrPut(screenId) { GridOccupancy(columns, rows) }

        fixedItems.forEach { occupancyFor(it.screenId).markCells(it, true) }

        val updated = ArrayList<ItemInfo>()
        val xy = IntArray(2)
        movableItems.forEach { item ->
            var placed = false
            for (screenId in screenIds) {
                if (screensToExclude.contains(screenId)) continue
                val occupancy = occupancyFor(screenId)
                if (!occupancy.findVacantCell(xy, item.spanX, item.spanY)) continue
                placed = true
                updatedIfChanged(item, screenId, xy[0], xy[1], columns, updated)
                occupancy.markCells(xy[0], xy[1], item.spanX, item.spanY, true)
                break
            }
            if (!placed) {
                val newScreenId = model.modelDbController.getNewScreenId()
                screenIds.add(newScreenId)
                val occupancy = occupancyFor(newScreenId)
                if (occupancy.findVacantCell(xy, item.spanX, item.spanY)) {
                    updatedIfChanged(item, newScreenId, xy[0], xy[1], columns, updated)
                    occupancy.markCells(xy[0], xy[1], item.spanX, item.spanY, true)
                }
            }
        }

        prefs.putSync(LauncherPrefs.NEEDS_WORKSPACE_REORDER_AFTER_RESTORE.to(false))

        if (updated.isEmpty()) {
            return
        }
        val writer = taskController.getModelWriter()
        updated.forEach { writer.updateItemInDatabase(it) }
        model.forceReload()
    }

    private fun updatedIfChanged(
        item: ItemInfo,
        screenId: Int,
        cellX: Int,
        cellY: Int,
        columns: Int,
        updated: MutableList<ItemInfo>,
    ) {
        if (item.screenId == screenId && item.cellX == cellX && item.cellY == cellY) {
            return
        }
        item.screenId = screenId
        item.cellX = cellX
        item.cellY = cellY
        item.rank = cellX + (cellY * columns)
        updated.add(item)
    }
}