diff --git a/lawnchair/res/values/strings.xml b/lawnchair/res/values/strings.xml index f214852995..99007ab6e5 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 @@ -277,6 +279,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 @@ -777,7 +782,7 @@ --> - Search apps, web, and more + Search web and more Search apps No apps found matching \"%1$s\" @@ -909,4 +914,6 @@ Grant permissions Permissions needed Tap to grant permissions + + Home screen cleared diff --git a/lawnchair/src/app/lawnchair/allapps/LawnchairAlphabeticalAppsList.kt b/lawnchair/src/app/lawnchair/allapps/LawnchairAlphabeticalAppsList.kt index ff0d253e67..d7db44fedb 100644 --- a/lawnchair/src/app/lawnchair/allapps/LawnchairAlphabeticalAppsList.kt +++ b/lawnchair/src/app/lawnchair/allapps/LawnchairAlphabeticalAppsList.kt @@ -7,10 +7,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 @@ -45,7 +45,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) @@ -88,8 +87,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/ui/preferences/destinations/HomeScreenPreferences.kt b/lawnchair/src/app/lawnchair/ui/preferences/destinations/HomeScreenPreferences.kt index caee77a71c..0dbee1ad34 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/destinations/HomeScreenPreferences.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/destinations/HomeScreenPreferences.kt @@ -44,6 +44,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 @@ -67,6 +68,7 @@ fun HomeScreenPreferences( ) { val lockHomeScreenAdapter = prefs2.lockHomeScreen.getAdapter() val showDeckLayout = prefs2.showDeckLayout.getAdapter().state.value + val context = LocalContext.current if (showDeckLayout) { HomeLayoutSettings() @@ -101,6 +103,20 @@ fun HomeScreenPreferences( ) } } + PreferenceGroup(heading = stringResource(id = R.string.home_screen_actions)) { + Item { + 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() PreferenceGroup(heading = stringResource(id = R.string.minus_one)) { 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 +} diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java index f885daedf9..f9d1e2d7fb 100644 --- a/src/com/android/launcher3/model/ModelWriter.java +++ b/src/com/android/launcher3/model/ModelWriter.java @@ -153,6 +153,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