diff --git a/lawnchair/src/app/lawnchair/search/engine/provider/AppSearchProvider.kt b/lawnchair/src/app/lawnchair/search/engine/provider/AppSearchProvider.kt new file mode 100644 index 0000000000..cc1c73d205 --- /dev/null +++ b/lawnchair/src/app/lawnchair/search/engine/provider/AppSearchProvider.kt @@ -0,0 +1,81 @@ +package app.lawnchair.search.engine.provider + +import android.content.Context +import android.content.pm.ShortcutInfo +import app.lawnchair.launcher +import app.lawnchair.preferences2.PreferenceManager2 +import app.lawnchair.search.algorithms.filterHiddenApps +import app.lawnchair.search.engine.SearchResult +import com.android.launcher3.model.AllAppsList +import com.android.launcher3.model.data.AppInfo +import com.android.launcher3.popup.PopupPopulator +import com.android.launcher3.search.StringMatcherUtility +import com.android.launcher3.shortcuts.ShortcutRequest +import com.patrykmichalik.opto.core.firstBlocking +import java.util.Locale +import me.xdrop.fuzzywuzzy.FuzzySearch +import me.xdrop.fuzzywuzzy.algorithms.WeightedRatio + +object AppSearchProvider { + + fun search(context: Context, query: String, allApps: AllAppsList): List { + val prefs = PreferenceManager2.getInstance(context) + val hiddenApps = prefs.hiddenApps.firstBlocking() + val hiddenAppsInSearch = prefs.hiddenAppsInSearch.firstBlocking() + val maxAppResults = prefs.maxAppSearchResultCount.firstBlocking() + val enableFuzzySearch = prefs.enableFuzzySearch.firstBlocking() + + val appResults = if (enableFuzzySearch) { + fuzzySearch(allApps.data, query, maxAppResults, hiddenApps, hiddenAppsInSearch) + } else { + normalSearch(allApps.data, query, maxAppResults, hiddenApps, hiddenAppsInSearch) + } + + val finalResults = mutableListOf() + appResults.mapTo(finalResults) { SearchResult.App(data = it) } + + if (appResults.size == 1) { + val singleApp = appResults.first() + val shortcuts = getShortcuts(singleApp, context) + shortcuts.mapTo(finalResults) { SearchResult.Shortcut(data = it) } + } + + return finalResults + } + + private fun normalSearch(apps: List, query: String, maxResultsCount: Int, hiddenApps: Set, hiddenAppsInSearch: String): List { + // Do an intersection of the words in the query and each title, and filter out all the + // apps that don't match all of the words in the query. + val queryTextLower = query.lowercase(Locale.getDefault()) + val matcher = StringMatcherUtility.StringMatcher.getInstance() + return apps.asSequence() + .filter { StringMatcherUtility.matches(queryTextLower, it.title.toString(), matcher) } + .filterHiddenApps(queryTextLower, hiddenApps, hiddenAppsInSearch) + .take(maxResultsCount) + .toList() + } + + private fun fuzzySearch(apps: List, query: String, maxResultsCount: Int, hiddenApps: Set, hiddenAppsInSearch: String): List { + val queryTextLower = query.lowercase(Locale.getDefault()) + val filteredApps = apps.asSequence() + .filterHiddenApps(queryTextLower, hiddenApps, hiddenAppsInSearch) + .toList() + val matches = FuzzySearch.extractSorted( + queryTextLower, + filteredApps, + { it.sectionName + it.title }, + WeightedRatio(), + 65, + ) + + return matches.take(maxResultsCount) + .map { it.referent } + } + + private fun getShortcuts(app: AppInfo, context: Context): List { + val shortcuts = ShortcutRequest(context.launcher, app.user) + .withContainer(app.targetComponent) + .query(ShortcutRequest.PUBLISHED) + return PopupPopulator.sortAndFilterShortcuts(shortcuts) + } +}