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