From 8c36c72efc3a279e4c08eee8e3007b72c06aedb1 Mon Sep 17 00:00:00 2001 From: Abhishek Sharma <66074182+abhixv@users.noreply.github.com> Date: Sat, 20 Dec 2025 03:26:30 +0530 Subject: [PATCH 1/4] feat: Add option to clear home screen in settings (#6125) Signed-off-by: abhixv --- lawnchair/res/values/strings.xml | 7 +++++ .../destinations/HomeScreenPreferences.kt | 13 +++++++++ .../android/launcher3/LauncherAppState.java | 14 +++++++++ .../android/launcher3/model/ModelWriter.java | 29 +++++++++++++++++++ 4 files changed, 63 insertions(+) diff --git a/lawnchair/res/values/strings.xml b/lawnchair/res/values/strings.xml index cc7f515900..26cc2511cc 100644 --- a/lawnchair/res/values/strings.xml +++ b/lawnchair/res/values/strings.xml @@ -166,6 +166,8 @@ Home screen Feed, grid, icons + Home screen actions + Dock Search bar, icon count @@ -262,6 +264,9 @@ Reset custom icons All custom icons will be reset. Do you want to continue? + Clear home screen + Home screen will be cleared. Do you want to continue? + Icons Reset to default @@ -892,4 +897,6 @@ Grant permissions Permissions needed Tap to grant permissions + + Home screen cleared diff --git a/lawnchair/src/app/lawnchair/ui/preferences/destinations/HomeScreenPreferences.kt b/lawnchair/src/app/lawnchair/ui/preferences/destinations/HomeScreenPreferences.kt index bc72c6d685..5cbd6e6a20 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/destinations/HomeScreenPreferences.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/destinations/HomeScreenPreferences.kt @@ -45,6 +45,7 @@ import app.lawnchair.ui.preferences.components.layout.PreferenceGroup import app.lawnchair.ui.preferences.components.layout.PreferenceLayout import app.lawnchair.ui.preferences.navigation.HomeScreenGrid import app.lawnchair.util.collectAsStateBlocking +import com.android.launcher3.LauncherAppState import com.android.launcher3.R import com.android.launcher3.Utilities import kotlinx.coroutines.launch @@ -68,6 +69,7 @@ fun HomeScreenPreferences( ) { val lockHomeScreenAdapter = prefs2.lockHomeScreen.getAdapter() val showDeckLayout = prefs2.showDeckLayout.getAdapter().state.value + val context = LocalContext.current if (showDeckLayout) { HomeLayoutSettings() @@ -95,6 +97,17 @@ fun HomeScreenPreferences( description = stringResource(id = R.string.infinite_scrolling_description), ) } + PreferenceGroup(heading = stringResource(id = R.string.home_screen_actions)) { + ClickablePreference( + label = stringResource(id = R.string.remove_all_views_from_home_screen), + confirmationText = stringResource(id = R.string.remove_all_views_from_home_screen_desc), + onClick = { + scope.launch { + LauncherAppState.getInstance(context).clearAllViewsFromHomeScreen() + } + }, + ) + } PreferenceGroup(heading = stringResource(id = R.string.minus_one)) { val feedAvailable = OverlayCallbackImpl.minusOneAvailable(LocalContext.current) val enableFeedAdapter = prefs2.enableFeed.getAdapter() diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index e0a4283456..d8dbd3f1cc 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -39,6 +39,7 @@ import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.ArchiveCompatibilityParams; import android.os.UserHandle; import android.util.Log; +import android.widget.Toast; import androidx.annotation.Nullable; import androidx.core.os.BuildCompat; @@ -220,6 +221,19 @@ public class LauncherAppState implements SafeCloseable { refreshAndReloadLauncher(); } + public void clearAllViewsFromHomeScreen() { + final boolean isViewsRemoved = + mLauncher.getModelWriter().clearAllHomeScreenViewsByType( + LauncherSettings.Favorites.CONTAINER_DESKTOP); + if (isViewsRemoved) { + Toast.makeText( + mLauncher, + R.string.home_screen_all_views_removed_msg, + Toast.LENGTH_SHORT + ).show(); + } + } + private void refreshAndReloadLauncher() { LauncherIcons.clearPool(mContext); mIconCache.updateIconParams( diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java index 4170c2b53a..bbf555443c 100644 --- a/src/com/android/launcher3/model/ModelWriter.java +++ b/src/com/android/launcher3/model/ModelWriter.java @@ -154,6 +154,35 @@ public class ModelWriter { throw e; } } + + /** + * Clears all views from the home screen. + */ + public boolean clearAllHomeScreenViewsByType(int type) { + final ArrayList itemsToRemove = new ArrayList<>(); + + for (ItemInfo item : mBgDataModel.itemsIdMap) { + if (item.container == type) { + itemsToRemove.add(item); + } + } + + if (itemsToRemove.isEmpty()) { + return false; + } + + enqueueDeleteRunnable(newModelTask(() -> { + final ModelDbController db = mModel.getModelDbController(); + + for (ItemInfo item : itemsToRemove) { + db.delete(TABLE_NAME, itemIdMatch(item.id), null); + mBgDataModel.removeItem(mContext, item); + } + })); + + mModel.forceReload(); + return true; + } /** * Move an item in the DB to a new From faa300c3d947a943a79320ccaecaec3cf2ffade8 Mon Sep 17 00:00:00 2001 From: SuperDragonXD <70206496+SuperDragonXD@users.noreply.github.com> Date: Sat, 20 Dec 2025 06:07:28 +0800 Subject: [PATCH 2/4] fix: Shorten device search hint string This should also make the app match Pixel Launcher's UI as well. Fixes #6192 --- lawnchair/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lawnchair/res/values/strings.xml b/lawnchair/res/values/strings.xml index 26cc2511cc..28edcf49b1 100644 --- a/lawnchair/res/values/strings.xml +++ b/lawnchair/res/values/strings.xml @@ -765,7 +765,7 @@ --> - Search apps, web, and more + Search web and more Search apps No apps found matching \"%1$s\" From 079a819e8e1f03f0edc9e7ef4496b2ee24746463 Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Sun, 30 Nov 2025 16:31:04 +0700 Subject: [PATCH 3/4] fix: Search widget can't launch Google due to background restrctions on Android 14 --- lawnchair/src/app/lawnchair/qsb/providers/Google.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lawnchair/src/app/lawnchair/qsb/providers/Google.kt b/lawnchair/src/app/lawnchair/qsb/providers/Google.kt index 89b3ad3bca..c30bfe540f 100644 --- a/lawnchair/src/app/lawnchair/qsb/providers/Google.kt +++ b/lawnchair/src/app/lawnchair/qsb/providers/Google.kt @@ -1,5 +1,6 @@ package app.lawnchair.qsb.providers +import android.app.ActivityOptions import android.app.PendingIntent import android.appwidget.AppWidgetHostView import android.content.Context @@ -11,6 +12,7 @@ import app.lawnchair.qsb.ThemingMethod import app.lawnchair.util.pendingIntent import com.android.launcher3.Launcher import com.android.launcher3.R +import com.android.launcher3.Utilities import com.android.launcher3.qsb.QsbContainerView import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull @@ -31,6 +33,12 @@ data object Google : QsbSearchProvider( if (!forceWebsite) { val subscription = getSearchIntent(launcher) val pendingIntent = subscription.firstOrNull() + val options = ActivityOptions.makeBasic() + if (Utilities.ATLEAST_U) { + options.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED, + ) + } if (pendingIntent != null) { launcher.startIntentSender( pendingIntent.intentSender, @@ -38,6 +46,7 @@ data object Google : QsbSearchProvider( Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK, Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK, 0, + options.toBundle() ) return } From a96751af7ee227f345172c055f2a0b2273492298 Mon Sep 17 00:00:00 2001 From: MrSluffy Date: Sat, 20 Dec 2025 17:07:34 +0800 Subject: [PATCH 4/4] feat(caddy&deck): add sys and g apps folder --- .../allapps/LawnchairAlphabeticalAppsList.kt | 9 ++-- .../src/app/lawnchair/deck/LawndeckManager.kt | 45 ++++++++++------ .../src/app/lawnchair/qsb/providers/Google.kt | 2 +- .../lawnchair/util/AppCategorizationUtils.kt | 54 +++++++++++++++++++ 4 files changed, 88 insertions(+), 22 deletions(-) create mode 100644 lawnchair/src/app/lawnchair/util/AppCategorizationUtils.kt diff --git a/lawnchair/src/app/lawnchair/allapps/LawnchairAlphabeticalAppsList.kt b/lawnchair/src/app/lawnchair/allapps/LawnchairAlphabeticalAppsList.kt index 0955fa0c5c..3e242e5a79 100644 --- a/lawnchair/src/app/lawnchair/allapps/LawnchairAlphabeticalAppsList.kt +++ b/lawnchair/src/app/lawnchair/allapps/LawnchairAlphabeticalAppsList.kt @@ -8,10 +8,10 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import app.lawnchair.data.folder.model.FolderOrderUtils import app.lawnchair.data.folder.model.FolderViewModel -import app.lawnchair.flowerpot.Flowerpot import app.lawnchair.launcher import app.lawnchair.preferences.PreferenceManager import app.lawnchair.preferences2.PreferenceManager2 +import app.lawnchair.util.categorizeAppsWithSystemAndGoogle import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener import com.android.launcher3.allapps.AllAppsStore import com.android.launcher3.allapps.AlphabeticalAppsList @@ -44,7 +44,6 @@ class LawnchairAlphabeticalAppsList( private val filteredList = mutableListOf() private val folderOrder = FolderOrderUtils.stringToIntList(prefs.drawerListOrder.get()) - private val potsManager = Flowerpot.Manager.getInstance(context) init { context.launcher.deviceProfile.inv.addOnChangeListener(this) @@ -87,8 +86,10 @@ class LawnchairAlphabeticalAppsList( if (isWorkOrPrivateSpace(appList)) return super.addAppsWithSections(appList, position) if (!drawerListDefault) { - val categorizedApps = potsManager.categorizeApps(appList) - categorizedApps.forEach { (category, apps) -> + val validApps = appList.mapNotNull { it } + val finalCategorizedApps = categorizeAppsWithSystemAndGoogle(validApps, context) + + finalCategorizedApps.forEach { (category, apps) -> if (apps.size == 1) { mAdapterItems.add(AdapterItem.asApp(apps.first())) } else { diff --git a/lawnchair/src/app/lawnchair/deck/LawndeckManager.kt b/lawnchair/src/app/lawnchair/deck/LawndeckManager.kt index d7a7c74c3f..c0cd9f3cf7 100644 --- a/lawnchair/src/app/lawnchair/deck/LawndeckManager.kt +++ b/lawnchair/src/app/lawnchair/deck/LawndeckManager.kt @@ -6,6 +6,7 @@ import app.lawnchair.LawnchairLauncher import app.lawnchair.flowerpot.Flowerpot import app.lawnchair.launcher import app.lawnchair.launcherNullable +import app.lawnchair.util.categorizeAppsWithSystemAndGoogle import app.lawnchair.util.restartLauncher import com.android.launcher3.InvariantDeviceProfile import com.android.launcher3.LauncherAppState @@ -17,6 +18,7 @@ import com.android.launcher3.model.data.FolderInfo import com.android.launcher3.model.data.WorkspaceItemInfo import com.android.launcher3.provider.RestoreDbTask import com.android.launcher3.util.ComponentKey +import com.android.launcher3.util.PackageManagerHelper import java.io.File import java.util.Locale import kotlinx.coroutines.CompletableDeferred @@ -101,9 +103,8 @@ class LawndeckManager(private val context: Context) { onProgress?.invoke("Categorizing apps...") - // Use flowerpot to categorize apps - val potsManager = Flowerpot.Manager.getInstance(context) - val categorizedApps = potsManager.categorizeApps(apps.map { it as? AppInfo }) + val validApps = apps.mapNotNull { it as? AppInfo } + val finalCategorizedApps = categorizeAppsWithSystemAndGoogle(validApps, context) onProgress?.invoke("Adding apps to workspace...") @@ -115,7 +116,7 @@ class LawndeckManager(private val context: Context) { var singleAppCount = 0 // Process each category - categorizedApps.forEach { (category, categoryApps) -> + finalCategorizedApps.forEach { (category, categoryApps) -> if (categoryApps.isEmpty()) return@forEach if (categoryApps.size == 1) { @@ -188,22 +189,32 @@ class LawndeckManager(private val context: Context) { val activityInfo = activities[0] val appInfo = AppInfo(context, activityInfo, user) - // Use flowerpot to categorize the app - val potsManager = Flowerpot.Manager.getInstance(context) - val categorizedApps = potsManager.categorizeApps(listOf(appInfo)) + val intent = appInfo.intent - if (categorizedApps.isEmpty()) { - // No category found, add directly to workspace - ItemInstallQueue.INSTANCE.get(context).queueItem(packageName, user) - return - } + // Determine category: Google Apps > System Apps > Flowerpot categories + val category = when { + packageName.startsWith("com.google.") -> "Google Apps" - // Get the category for this app (categorizedApps is a Map>) - val categoryEntry = categorizedApps.entries.firstOrNull() ?: run { - ItemInstallQueue.INSTANCE.get(context).queueItem(packageName, user) - return + intent != null && PackageManagerHelper.isSystemApp(context, intent) -> "System Apps" + + else -> { + // Use flowerpot to categorize the app + val potsManager = Flowerpot.Manager.getInstance(context) + val categorizedApps = potsManager.categorizeApps(listOf(appInfo)) + + if (categorizedApps.isEmpty()) { + // No category found, add directly to workspace + ItemInstallQueue.INSTANCE.get(context).queueItem(packageName, user) + return + } + + // Get the category from flowerpot + categorizedApps.entries.firstOrNull()?.key ?: run { + ItemInstallQueue.INSTANCE.get(context).queueItem(packageName, user) + return + } + } } - val category = categoryEntry.key // Check if there's already a folder for this category on workspace val existingFolder = findFolderByCategory(category, dataModel) diff --git a/lawnchair/src/app/lawnchair/qsb/providers/Google.kt b/lawnchair/src/app/lawnchair/qsb/providers/Google.kt index c30bfe540f..6f5654a856 100644 --- a/lawnchair/src/app/lawnchair/qsb/providers/Google.kt +++ b/lawnchair/src/app/lawnchair/qsb/providers/Google.kt @@ -46,7 +46,7 @@ data object Google : QsbSearchProvider( Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK, Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK, 0, - options.toBundle() + options.toBundle(), ) return } diff --git a/lawnchair/src/app/lawnchair/util/AppCategorizationUtils.kt b/lawnchair/src/app/lawnchair/util/AppCategorizationUtils.kt new file mode 100644 index 0000000000..db1c7d0cc3 --- /dev/null +++ b/lawnchair/src/app/lawnchair/util/AppCategorizationUtils.kt @@ -0,0 +1,54 @@ +package app.lawnchair.util + +import android.content.Context +import app.lawnchair.flowerpot.Flowerpot +import com.android.launcher3.model.data.AppInfo +import com.android.launcher3.util.PackageManagerHelper + +/** + * Categorizes apps into System Apps, Google Apps, and Flowerpot categories. + * + * @param apps List of apps to categorize + * @param context Context for checking system apps and accessing Flowerpot + * @return Map of category names to lists of apps in that category + */ +fun categorizeAppsWithSystemAndGoogle( + apps: List, + context: Context, +): Map> { + val systemApps = mutableListOf() + val googleApps = mutableListOf() + val otherApps = mutableListOf() + + apps.forEach { app -> + val packageName = app.targetPackage ?: return@forEach + val intent = app.intent + + // Check if it's a Google app first (Google apps can also be system apps) + when { + packageName.startsWith("com.google.") -> googleApps.add(app) + intent != null && PackageManagerHelper.isSystemApp(context, intent) -> systemApps.add(app) + else -> otherApps.add(app) + } + } + + // Use flowerpot to categorize other apps (non-system, non-Google) + val potsManager = Flowerpot.Manager.getInstance(context) + val categorizedApps = potsManager.categorizeApps(otherApps) + + // Build final categorized apps map + val finalCategorizedApps = mutableMapOf>() + + if (systemApps.isNotEmpty()) { + finalCategorizedApps["System Apps"] = systemApps + } + + if (googleApps.isNotEmpty()) { + finalCategorizedApps["Google Apps"] = googleApps + } + + // Add flowerpot categorized apps + finalCategorizedApps.putAll(categorizedApps) + + return finalCategorizedApps +}