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..bf4fb2697e 100644
--- a/lawnchair/src/app/lawnchair/ui/preferences/destinations/HomeScreenPreferences.kt
+++ b/lawnchair/src/app/lawnchair/ui/preferences/destinations/HomeScreenPreferences.kt
@@ -16,6 +16,8 @@
package app.lawnchair.ui.preferences.destinations
+import android.content.Context
+import android.widget.Toast
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
@@ -44,6 +46,9 @@ 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.Launcher
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings
import com.android.launcher3.R
import com.android.launcher3.Utilities
import kotlinx.coroutines.launch
@@ -60,6 +65,7 @@ fun HomeScreenPreferences(
val prefs = preferenceManager()
val prefs2 = preferenceManager2()
val scope = rememberCoroutineScope()
+ val context = LocalContext.current
PreferenceLayout(
label = stringResource(id = R.string.home_screen_label),
backArrowVisible = !LocalIsExpandedScreen.current,
@@ -101,6 +107,19 @@ 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 {
+ clearAllViewsFromHomeScreen(context)
+ }
+ },
+ )
+ }
+ }
val feedAvailable = OverlayCallbackImpl.minusOneAvailable(LocalContext.current)
val enableFeedAdapter = prefs2.enableFeed.getAdapter()
PreferenceGroup(heading = stringResource(id = R.string.minus_one)) {
@@ -275,6 +294,20 @@ fun HomeScreenPreferences(
}
}
+private fun clearAllViewsFromHomeScreen(context: Context) {
+ val model = Launcher.getLauncher(context).modelWriter
+ val isViewsRemoved = model.clearAllHomeScreenViewsByType(
+ LauncherSettings.Favorites.CONTAINER_DESKTOP,
+ )
+ if (isViewsRemoved) {
+ Toast.makeText(
+ context,
+ R.string.home_screen_all_views_removed_msg,
+ Toast.LENGTH_SHORT,
+ ).show()
+ }
+}
+
@Composable
fun HomeScreenTextColorPreference(
modifier: Modifier = Modifier,
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index d8dbd3f1cc..e69de29bb2 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -1,318 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * Modifications copyright 2021, Lawnchair
- */
-
-package com.android.launcher3;
-
-import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
-import static android.content.Context.RECEIVER_EXPORTED;
-
-import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
-import static com.android.launcher3.LauncherPrefs.ICON_STATE;
-import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
-import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
-import static com.android.launcher3.util.SettingsCache.PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-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;
-
-import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.IconProvider;
-import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.model.ModelLauncherCallbacks;
-import com.android.launcher3.notification.NotificationListener;
-import com.android.launcher3.pm.InstallSessionHelper;
-import com.android.launcher3.pm.InstallSessionTracker;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.LockedUserState;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.RunnableList;
-import com.android.launcher3.util.SafeCloseable;
-import com.android.launcher3.util.SettingsCache;
-import com.android.launcher3.util.SimpleBroadcastReceiver;
-import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.widget.custom.CustomWidgetManager;
-
-import app.lawnchair.LawnchairAppKt;
-import app.lawnchair.icons.LawnchairIconProvider;
-
-public class LauncherAppState implements SafeCloseable {
-
- public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher";
-
- // We do not need any synchronization for this variable as its only written on
- // UI thread.
- public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject(
- LauncherAppState::new) {
- @Override
- protected void onPostInit(Context context) {
- super.onPostInit(context);
- LawnchairAppKt.getLawnchairApp(context).onLauncherAppStateCreated();
- }
- };
-
- private Launcher mLauncher;
- private final Context mContext;
- private final LauncherModel mModel;
- private final IconProvider mIconProvider;
- private final IconCache mIconCache;
- private final InvariantDeviceProfile mInvariantDeviceProfile;
- private boolean mIsSafeModeEnabled;
-
- private final RunnableList mOnTerminateCallback = new RunnableList();
-
- public static LauncherAppState getInstance(Context context) {
- return INSTANCE.get(context);
- }
-
- public static LauncherAppState getInstanceNoCreate() {
- return INSTANCE.getNoCreate();
- }
-
- public Context getContext() {
- return mContext;
- }
-
- @SuppressWarnings("NewApi")
- public LauncherAppState(Context context) {
- this(context, LauncherFiles.APP_ICONS_DB);
- Log.v(Launcher.TAG, "LauncherAppState initiated");
- Preconditions.assertUIThread();
-
- mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
- () -> context.getPackageManager().isSafeMode());
- mInvariantDeviceProfile.addOnChangeListener(modelPropertiesChanged -> {
- if (modelPropertiesChanged) {
- refreshAndReloadLauncher();
- }
- });
-
- ModelLauncherCallbacks callbacks = mModel.newModelCallbacks();
- LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
- launcherApps.registerCallback(callbacks);
- mOnTerminateCallback.add(() -> mContext.getSystemService(LauncherApps.class).unregisterCallback(callbacks));
-
- if (BuildCompat.isAtLeastV() && Flags.enableSupportForArchiving()) {
- ArchiveCompatibilityParams params = new ArchiveCompatibilityParams();
- params.setEnableUnarchivalConfirmation(false);
- launcherApps.setArchiveCompatibility(params);
- }
-
- SimpleBroadcastReceiver modelChangeReceiver = new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
- modelChangeReceiver.register(mContext, Intent.ACTION_LOCALE_CHANGED,
- ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
-
- mOnTerminateCallback.add(() -> mContext.unregisterReceiver(modelChangeReceiver));
-
- SafeCloseable userChangeListener = UserCache.INSTANCE.get(mContext)
- .addUserEventListener(mModel::onUserEvent);
- mOnTerminateCallback.add(userChangeListener::close);
-
- if (enableSmartspaceRemovalToggle()) {
- OnSharedPreferenceChangeListener firstPagePinnedItemListener = new OnSharedPreferenceChangeListener() {
- @Override
- public void onSharedPreferenceChanged(
- SharedPreferences sharedPreferences, String key) {
- if (SMARTSPACE_ON_HOME_SCREEN.equals(key)) {
- mModel.forceReload();
- }
- }
- };
- LauncherPrefs.getPrefs(mContext).registerOnSharedPreferenceChangeListener(
- firstPagePinnedItemListener);
- mOnTerminateCallback.add(() -> LauncherPrefs.getPrefs(mContext)
- .unregisterOnSharedPreferenceChangeListener(firstPagePinnedItemListener));
- }
-
- LockedUserState.get(context).runOnUserUnlocked(() -> {
- CustomWidgetManager cwm = CustomWidgetManager.INSTANCE.get(mContext);
- cwm.setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
- mOnTerminateCallback.add(() -> cwm.setWidgetRefreshCallback(null));
-
- IconObserver observer = new IconObserver();
- SafeCloseable iconChangeTracker = mIconProvider.registerIconChangeListener(
- observer, MODEL_EXECUTOR.getHandler());
- mOnTerminateCallback.add(iconChangeTracker::close);
- MODEL_EXECUTOR.execute(observer::verifyIconChanged);
- LauncherPrefs.get(context).addListener(observer, THEMED_ICONS);
- mOnTerminateCallback.add(
- () -> LauncherPrefs.get(mContext).removeListener(observer, THEMED_ICONS));
-
- InstallSessionTracker installSessionTracker = InstallSessionHelper.INSTANCE.get(context)
- .registerInstallTracker(mModel);
- mOnTerminateCallback.add(installSessionTracker::unregister);
- });
-
- // Register an observer to rebind the notification listener when dots are
- // re-enabled.
- SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
- SettingsCache.OnChangeListener notificationLister = this::onNotificationSettingsChanged;
- settingsCache.register(NOTIFICATION_BADGING_URI, notificationLister);
- onNotificationSettingsChanged(settingsCache.getValue(NOTIFICATION_BADGING_URI));
- mOnTerminateCallback.add(() -> settingsCache.unregister(NOTIFICATION_BADGING_URI, notificationLister));
- // Register an observer to notify Launcher about Private Space settings toggle.
- registerPrivateSpaceHideWhenLockListener(settingsCache);
- }
-
- public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
- mContext = context;
-
- mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
- mIconProvider = new LawnchairIconProvider(context, Themes.isThemedIconEnabled(context));
- mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
- iconCacheFileName, mIconProvider);
- mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext),
- PackageManagerHelper.INSTANCE.get(context), iconCacheFileName != null);
- mOnTerminateCallback.add(mIconCache::close);
- mOnTerminateCallback.add(mModel::destroy);
- }
-
- private void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
- if (areNotificationDotsEnabled) {
- NotificationListener.requestRebind(new ComponentName(
- mContext, NotificationListener.class));
- }
- }
-
- private void registerPrivateSpaceHideWhenLockListener(SettingsCache settingsCache) {
- SettingsCache.OnChangeListener psHideWhenLockChangedListener = this::onPrivateSpaceHideWhenLockChanged;
- settingsCache.register(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, psHideWhenLockChangedListener);
- mOnTerminateCallback.add(() -> settingsCache.unregister(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI,
- psHideWhenLockChangedListener));
- }
-
- private void onPrivateSpaceHideWhenLockChanged(boolean isPrivateSpaceHideOnLockEnabled) {
- mModel.forceReload();
- }
-
- public void reloadIcons() {
- 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(
- mInvariantDeviceProfile.fillResIconDpi, mInvariantDeviceProfile.iconBitmapSize);
- mModel.forceReload();
- }
-
- /**
- * Call from Application.onTerminate(), which is not guaranteed to ever be
- * called.
- */
- @Override
- public void close() {
- mOnTerminateCallback.executeAllAndDestroy();
- }
-
- public IconProvider getIconProvider() {
- return mIconProvider;
- }
-
- public void setLauncher(Launcher launcher) {
- mLauncher = launcher;
- }
-
- public Launcher getLauncher() {
- return mLauncher;
- }
-
- public IconCache getIconCache() {
- return mIconCache;
- }
-
- public LauncherModel getModel() {
- return mModel;
- }
-
- public InvariantDeviceProfile getInvariantDeviceProfile() {
- return mInvariantDeviceProfile;
- }
-
- public boolean isSafeModeEnabled() {
- return mIsSafeModeEnabled;
- }
-
- /**
- * Shorthand for {@link #getInvariantDeviceProfile()}
- */
- public static InvariantDeviceProfile getIDP(Context context) {
- return InvariantDeviceProfile.INSTANCE.get(context);
- }
-
- private class IconObserver
- implements IconProvider.IconChangeListener, OnSharedPreferenceChangeListener {
-
- @Override
- public void onAppIconChanged(String packageName, UserHandle user) {
- mModel.onAppIconChanged(packageName, user);
- }
-
- @Override
- public void onSystemIconStateChanged(String iconState) {
- IconShape.INSTANCE.get(mContext).pickBestShape(mContext);
- refreshAndReloadLauncher();
- LauncherPrefs.get(mContext).put(ICON_STATE, iconState);
- }
-
- void verifyIconChanged() {
- String iconState = mIconProvider.getSystemIconState();
- if (!iconState.equals(LauncherPrefs.get(mContext).get(ICON_STATE))) {
- onSystemIconStateChanged(iconState);
- }
- }
-
- @Override
- public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
- if (Themes.KEY_THEMED_ICONS.equals(key)) {
- mIconProvider.setIconThemeSupported(Themes.isThemedIconEnabled(mContext));
- verifyIconChanged();
- }
- }
- }
-}
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index f885daedf9..1867119b94 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -16,6 +16,7 @@
package com.android.launcher3.model;
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -153,6 +154,27 @@ public class ModelWriter {
throw e;
}
}
+
+ /**
+ * Clears all views from the home screen.
+ */
+ public boolean clearAllHomeScreenViewsByType(int type) {
+ final ArrayList itemsToRemove = new ArrayList<>();
+ synchronized (mBgDataModel) {
+ for (ItemInfo item : mBgDataModel.itemsIdMap) {
+ if (item.container == type) {
+ itemsToRemove.add(item);
+ }
+ }
+ }
+
+ if (itemsToRemove.isEmpty()) {
+ return false;
+ }
+
+ deleteItemsFromDatabase(itemsToRemove, "clearAllHomeScreenViewsByType");
+ return true;
+ }
/**
* Move an item in the DB to a new