diff --git a/lawnchair/src/app/lawnchair/allapps/views/SearchResultIcon.kt b/lawnchair/src/app/lawnchair/allapps/views/SearchResultIcon.kt index 060d80863e..cf35cd9c36 100644 --- a/lawnchair/src/app/lawnchair/allapps/views/SearchResultIcon.kt +++ b/lawnchair/src/app/lawnchair/allapps/views/SearchResultIcon.kt @@ -12,7 +12,7 @@ import androidx.core.view.isInvisible import androidx.core.view.isVisible import app.lawnchair.launcher import app.lawnchair.search.adapter.SHORTCUT -import app.lawnchair.search.adapter.SearchResultActionCallBack +import app.lawnchair.search.model.SearchResultActionCallBack import app.lawnchair.search.adapter.SearchTargetCompat import app.lawnchair.util.runOnMainThread import com.android.launcher3.BubbleTextView diff --git a/lawnchair/src/app/lawnchair/allapps/views/SearchResultIconRow.kt b/lawnchair/src/app/lawnchair/allapps/views/SearchResultIconRow.kt index e1da741053..4c0dcd2645 100644 --- a/lawnchair/src/app/lawnchair/allapps/views/SearchResultIconRow.kt +++ b/lawnchair/src/app/lawnchair/allapps/views/SearchResultIconRow.kt @@ -11,7 +11,7 @@ import app.lawnchair.allapps.views.SearchResultView.Companion.FLAG_HIDE_SUBTITLE import app.lawnchair.font.FontManager import app.lawnchair.search.adapter.HISTORY import app.lawnchair.search.adapter.SETTINGS -import app.lawnchair.search.adapter.SearchResultActionCallBack +import app.lawnchair.search.model.SearchResultActionCallBack import app.lawnchair.search.adapter.SearchTargetCompat import app.lawnchair.search.adapter.WEB_SUGGESTION import com.android.app.search.LayoutType diff --git a/lawnchair/src/app/lawnchair/allapps/views/SearchResultRightLeftIcon.kt b/lawnchair/src/app/lawnchair/allapps/views/SearchResultRightLeftIcon.kt index 0a8425fa4f..25cec7ed50 100644 --- a/lawnchair/src/app/lawnchair/allapps/views/SearchResultRightLeftIcon.kt +++ b/lawnchair/src/app/lawnchair/allapps/views/SearchResultRightLeftIcon.kt @@ -10,7 +10,7 @@ import android.widget.TextView import androidx.core.view.ViewCompat import app.lawnchair.font.FontManager import app.lawnchair.launcher -import app.lawnchair.search.adapter.SearchResultActionCallBack +import app.lawnchair.search.model.SearchResultActionCallBack import app.lawnchair.search.adapter.SearchTargetCompat import app.lawnchair.util.AppInfo import app.lawnchair.util.AppInfoHelper diff --git a/lawnchair/src/app/lawnchair/allapps/views/SearchResultText.kt b/lawnchair/src/app/lawnchair/allapps/views/SearchResultText.kt index d0bf29a886..c631f6ef21 100644 --- a/lawnchair/src/app/lawnchair/allapps/views/SearchResultText.kt +++ b/lawnchair/src/app/lawnchair/allapps/views/SearchResultText.kt @@ -13,7 +13,7 @@ import app.lawnchair.search.LawnchairRecentSuggestionProvider import app.lawnchair.search.adapter.HEADER_JUSTIFY import app.lawnchair.search.adapter.SPACE import app.lawnchair.search.adapter.SPACE_MINI -import app.lawnchair.search.adapter.SearchResultActionCallBack +import app.lawnchair.search.model.SearchResultActionCallBack import app.lawnchair.search.adapter.SearchTargetCompat import app.lawnchair.theme.color.tokens.ColorTokens import com.android.launcher3.R diff --git a/lawnchair/src/app/lawnchair/allapps/views/SearchResultView.kt b/lawnchair/src/app/lawnchair/allapps/views/SearchResultView.kt index 15ff17adeb..c5f6a9bc0f 100644 --- a/lawnchair/src/app/lawnchair/allapps/views/SearchResultView.kt +++ b/lawnchair/src/app/lawnchair/allapps/views/SearchResultView.kt @@ -8,7 +8,7 @@ import app.lawnchair.search.adapter.CONTACT import app.lawnchair.search.adapter.FILES import app.lawnchair.search.adapter.MARKET_STORE import app.lawnchair.search.adapter.START_PAGE -import app.lawnchair.search.adapter.SearchResultActionCallBack +import app.lawnchair.search.model.SearchResultActionCallBack import app.lawnchair.search.adapter.SearchTargetCompat import app.lawnchair.search.adapter.WEB_SUGGESTION import com.android.app.search.LayoutType diff --git a/lawnchair/src/app/lawnchair/search/LawnchairSearchAdapterProvider.kt b/lawnchair/src/app/lawnchair/search/LawnchairSearchAdapterProvider.kt index 59681d7586..1dd74bb585 100644 --- a/lawnchair/src/app/lawnchair/search/LawnchairSearchAdapterProvider.kt +++ b/lawnchair/src/app/lawnchair/search/LawnchairSearchAdapterProvider.kt @@ -8,7 +8,7 @@ import androidx.core.util.contains import app.lawnchair.allapps.views.SearchItemDecorator import app.lawnchair.allapps.views.SearchResultView import app.lawnchair.search.adapter.SearchAdapterItem -import app.lawnchair.search.adapter.SearchResultActionCallBack +import app.lawnchair.search.model.SearchResultActionCallBack import com.android.app.search.LayoutType import com.android.launcher3.DeviceProfile import com.android.launcher3.R diff --git a/lawnchair/src/app/lawnchair/search/adapter/GenerateSearchTarget.kt b/lawnchair/src/app/lawnchair/search/adapter/SearchTargetFactory.kt similarity index 63% rename from lawnchair/src/app/lawnchair/search/adapter/GenerateSearchTarget.kt rename to lawnchair/src/app/lawnchair/search/adapter/SearchTargetFactory.kt index 5ca9ae6bbf..4530bd2a2a 100644 --- a/lawnchair/src/app/lawnchair/search/adapter/GenerateSearchTarget.kt +++ b/lawnchair/src/app/lawnchair/search/adapter/SearchTargetFactory.kt @@ -3,6 +3,7 @@ package app.lawnchair.search.adapter import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.content.pm.ShortcutInfo import android.graphics.BitmapFactory import android.graphics.drawable.Icon import android.net.Uri @@ -28,28 +29,56 @@ import app.lawnchair.util.file2Uri import app.lawnchair.util.mimeCompat import com.android.app.search.LayoutType import com.android.launcher3.R +import com.android.launcher3.model.data.AppInfo import com.android.launcher3.util.ComponentKey import com.android.launcher3.util.PackageManagerHelper import java.io.File import java.io.IOException import java.io.InputStream +import java.security.MessageDigest import java.util.Locale +import okio.ByteString -class GenerateSearchTarget(private val context: Context) { +class SearchTargetFactory( + private val context: Context +) { + fun createAppSearchTarget(appInfo: AppInfo, asRow: Boolean = false): SearchTargetCompat { + val componentName = appInfo.componentName + val user = appInfo.user + return SearchTargetCompat.Builder( + SearchTargetCompat.RESULT_TYPE_APPLICATION, + if (asRow) LayoutType.SMALL_ICON_HORIZONTAL_TEXT else LayoutType.ICON_SINGLE_VERTICAL_TEXT, + generateHashKey(ComponentKey(componentName, user).toString()), + ).apply { + setPackageName(componentName.packageName) + setUserHandle(user) + setExtras(bundleOf("class" to componentName.className)) + }.build() + } - private val marketSearchComponent = resolveMarketSearchActivity() + fun createShortcutTarget(shortcutInfo: ShortcutInfo): SearchTargetCompat { + return SearchTargetCompat.Builder( + SearchTargetCompat.RESULT_TYPE_SHORTCUT, + LayoutType.SMALL_ICON_HORIZONTAL_TEXT, + "shortcut_" + generateHashKey("${shortcutInfo.`package`}|${shortcutInfo.userHandle}|${shortcutInfo.id}"), + ).apply { + setShortcutInfo(shortcutInfo) + setUserHandle(shortcutInfo.userHandle) + setExtras(Bundle()) + }.build() + } - internal fun getSuggestionTarget(suggestion: String, suggestionProvider: String): SearchTargetCompat { + fun createWebSuggestionsTarget(suggestion: String, suggestionProvider: String): SearchTargetCompat { val url = WebSearchProvider.fromString(suggestionProvider).getSearchUrl(suggestion) val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) val id = suggestion + url - val action = SearchActionCompat.Builder(id, suggestion) - .setIcon( + val action = SearchActionCompat.Builder(id, suggestion).apply { + setIcon( Icon.createWithResource(context, R.drawable.ic_allapps_search) .setTint(ColorTokens.TextColorSecondary.resolveColor(context)), ) - .setIntent(browserIntent) - .build() + setIntent(browserIntent) + }.build() return createSearchTarget( id, action, @@ -59,7 +88,7 @@ class GenerateSearchTarget(private val context: Context) { ) } - internal fun getCalculationTarget( + internal fun createCalculatorTarget( calculation: Calculation, ): SearchTargetCompat { val result = calculation.result @@ -76,14 +105,35 @@ class GenerateSearchTarget(private val context: Context) { val extras = bundleOf() - return createCalculatorTarget( + return createSearchTarget( id, action, + LayoutType.CALCULATOR, + SearchTargetCompat.RESULT_TYPE_CALCULATOR, + "", extras, ) } - internal fun getRecentKeywordTarget(recentKeyword: RecentKeyword, suggestionProvider: String): SearchTargetCompat { + fun createHeaderTarget(header: String, pkg: String = HEADER): SearchTargetCompat { + val id = "header_$header" + val action = SearchActionCompat.Builder(id, header) + .setIcon( + Icon.createWithResource(context, R.drawable.ic_allapps_search) + .setTint(ColorTokens.TextColorPrimary.resolveColor(context)), + ) + .setIntent(Intent()) + .build() + return createSearchTarget( + id, + action, + LayoutType.TEXT_HEADER, + SearchTargetCompat.RESULT_TYPE_SECTION_HEADER, + pkg, + ) + } + + fun createSearchHistoryTarget(recentKeyword: RecentKeyword, suggestionProvider: String): SearchTargetCompat { val value = recentKeyword.getValueByKey("display1") ?: "" val url = WebSearchProvider.fromString(suggestionProvider).getSearchUrl(value) val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) @@ -104,25 +154,7 @@ class GenerateSearchTarget(private val context: Context) { ) } - internal fun getHeaderTarget(header: String, pkg: String = HEADER): SearchTargetCompat { - val id = "header_$header" - val action = SearchActionCompat.Builder(id, header) - .setIcon( - Icon.createWithResource(context, R.drawable.ic_allapps_search) - .setTint(ColorTokens.TextColorPrimary.resolveColor(context)), - ) - .setIntent(Intent()) - .build() - return createSearchTarget( - id, - action, - LayoutType.TEXT_HEADER, - SearchTargetCompat.RESULT_TYPE_SECTION_HEADER, - pkg, - ) - } - - internal fun getSettingSearchItem(info: SettingInfo): SearchTargetCompat? { + fun createSettingsTarget(info: SettingInfo): SearchTargetCompat? { val id = "_${info.id}" val intent = Intent(info.action) @@ -135,12 +167,12 @@ class GenerateSearchTarget(private val context: Context) { return null } - if (!hasRequiredPermissions(intent)) { + if (!SettingsTarget.hasRequiredPermissions(context, intent)) { Log.w("SettingSearch", "App does not have required permissions for intent: $intent") return null } - val actionBuilder = SearchActionCompat.Builder(id, formatSettingTitle(info.name)) + val actionBuilder = SearchActionCompat.Builder(id, SettingsTarget.formatSettingTitle(info.name)) .setIcon( Icon.createWithResource(context, R.drawable.ic_setting) .setTint(ColorTokens.Accent1_600.resolveColor(context)), @@ -157,24 +189,26 @@ class GenerateSearchTarget(private val context: Context) { ) } - private fun hasRequiredPermissions(intent: Intent): Boolean { - val packageManager = context.packageManager - val resolveInfo = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) - return resolveInfo?.activityInfo?.exported == true + private fun createSearchLinksTarget( + id: String, + action: SearchActionCompat, + pkg: String, + extras: Bundle = Bundle(), + ): SearchTargetCompat { + return SearchTargetCompat.Builder( + SearchTargetCompat.RESULT_TYPE_REDIRECTION, + LayoutType.ICON_HORIZONTAL_TEXT, + generateHashKey(id), + ) + .setPackageName(pkg) + .setUserHandle(Process.myUserHandle()) + .setSearchAction(action) + .setExtras(extras) + .build() } - private fun formatSettingTitle(rawTitle: String?): String { - return rawTitle?.replace('_', ' ') - ?.replace("ACTION", "") - ?.lowercase() - ?.split(' ') - ?.joinToString(" ") { - it.replaceFirstChar { char -> char.uppercase(Locale.ROOT) } - }.orEmpty() - } - - internal fun getMarketSearchItem(query: String): SearchTargetCompat? { - if (marketSearchComponent == null) return null + internal fun createMarketSearchTarget(query: String): SearchTargetCompat? { + val marketSearchComponent = SearchLinksTarget.resolveMarketSearchActivity(context) ?: return null val id = "marketSearch:$query" val action = SearchActionCompat.Builder( id, @@ -187,10 +221,15 @@ class GenerateSearchTarget(private val context: Context) { SearchResultView.EXTRA_ICON_COMPONENT_KEY to marketSearchComponent.toString(), SearchResultView.EXTRA_HIDE_SUBTITLE to true, ) - return createSearchTarget(id, action, MARKET_STORE, extras) + return createSearchLinksTarget( + id, + action, + MARKET_STORE, + extras + ) } - internal fun getWebSearchItem(query: String, suggestionProvider: String): SearchTargetCompat { + internal fun createWebSearchTarget(query: String, suggestionProvider: String): SearchTargetCompat { val webSearchProvider = WebSearchProvider.fromString(suggestionProvider) val webSearchLabel = context.getString(webSearchProvider.label) val url = webSearchProvider.getSearchUrl(query) @@ -206,10 +245,10 @@ class GenerateSearchTarget(private val context: Context) { val extras = bundleOf( SearchResultView.EXTRA_HIDE_SUBTITLE to true, ) - return createSearchTarget(id, action, START_PAGE, extras) + return createSearchLinksTarget(id, action, START_PAGE, extras) } - internal fun getContactSearchItem(info: ContactInfo): SearchTargetCompat { + fun createContactsTarget(info: ContactInfo): SearchTargetCompat { val id = "contact:${info.contactId}${info.number}" val contactUri = Uri.withAppendedPath( ContactsContract.Contacts.CONTENT_URI, @@ -218,7 +257,7 @@ class GenerateSearchTarget(private val context: Context) { val contactIntent = Intent(Intent.ACTION_VIEW, contactUri) val action = SearchActionCompat.Builder(id, info.name) - .setIcon(displayContactPhoto(context, info.name, Uri.parse(info.uri))) + .setIcon(ContactsTarget.displayContactPhoto(context, info.name, Uri.parse(info.uri))) .setContentDescription(info.contactId) .setSubtitle(info.number) .setIntent(contactIntent) @@ -234,7 +273,7 @@ class GenerateSearchTarget(private val context: Context) { ) } - internal fun getFileInfoSearchItem(info: IFileInfo): SearchTargetCompat { + fun createFilesTarget(info: IFileInfo): SearchTargetCompat { val fileUri = when (info) { is FileInfo -> Uri.withAppendedPath( MediaStore.Files.getContentUri("external"), @@ -254,7 +293,7 @@ class GenerateSearchTarget(private val context: Context) { .setDataAndType(fileUri, mimeType) val action = SearchActionCompat.Builder(info.path, info.name) - .setIcon(getPreviewIcon(info)) + .setIcon(FilesTarget.getPreviewIcon(context, info)) .setIntent(fileIntent) .build() @@ -268,7 +307,52 @@ class GenerateSearchTarget(private val context: Context) { ) } - private fun displayContactPhoto(context: Context, name: String, contactUri: Uri): Icon { + companion object { + private const val HASH_ALGORITHM = "SHA-256" + + // TODO find a way to properly provide tag/provide ids to search target + private val messageDigest by lazy { MessageDigest.getInstance(HASH_ALGORITHM) } + + private fun generateHashKey(input: String): String = + ByteString.of(*messageDigest.digest(input.toByteArray())).hex() + + fun createSearchTarget( + id: String, + action: SearchActionCompat, + layoutType: String, + targetCompat: Int, + pkg: String, + extras: Bundle = Bundle(), + ): SearchTargetCompat { + return SearchTargetCompat.Builder( + targetCompat, + layoutType, + generateHashKey(id), + ).apply { + setPackageName(pkg) + setUserHandle(Process.myUserHandle()) + setSearchAction(action) + setExtras(extras) + }.build() + } + + } +} + +object FilesTarget { + fun getPreviewIcon( + context: Context,info: IFileInfo): Icon { + val fileInfo = info as? FileInfo + return if (fileInfo?.isImageType == true) { + Icon.createWithFilePath(fileInfo.path) + } else { + Icon.createWithResource(context, fileInfo?.iconRes ?: R.drawable.ic_folder) + } + } +} + +object ContactsTarget { + fun displayContactPhoto(context: Context, name: String, contactUri: Uri): Icon { var inputStream: InputStream? = null try { inputStream = context.contentResolver.openInputStream(contactUri) @@ -282,26 +366,54 @@ class GenerateSearchTarget(private val context: Context) { inputStream?.close() } - // If contact photo not available, create an icon with the first letter of the name + // If contact photo is not available, create an icon with the first letter of the contact's name val initial = if (name.isNotEmpty()) name[0].uppercaseChar().toString() else "U" val textBitmap = createTextBitmap(context, initial) return Icon.createWithBitmap(textBitmap) } +} - private fun resolveMarketSearchActivity(): ComponentKey? { +object SearchLinksTarget { + fun resolveMarketSearchActivity(context: Context): ComponentKey? { val intent = PackageManagerHelper.getMarketSearchIntent(context, "") val resolveInfo = context.packageManager.resolveActivity(intent, 0) ?: return null val packageName = resolveInfo.activityInfo.packageName val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName) ?: return null return ComponentKey(launchIntent.component, Process.myUserHandle()) } +} - private fun getPreviewIcon(info: IFileInfo): Icon { - val fileInfo = info as? FileInfo - return if (fileInfo?.isImageType == true) { - Icon.createWithFilePath(fileInfo.path) - } else { - Icon.createWithResource(context, fileInfo?.iconRes ?: R.drawable.ic_folder) - } +object SettingsTarget { + fun hasRequiredPermissions(context: Context, intent: Intent): Boolean { + val packageManager = context.packageManager + val resolveInfo = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) + return resolveInfo?.activityInfo?.exported == true + } + + fun formatSettingTitle(rawTitle: String?): String { + return rawTitle?.replace('_', ' ') + ?.replace("ACTION", "") + ?.lowercase() + ?.split(' ') + ?.joinToString(" ") { + it.replaceFirstChar { char -> char.uppercase(Locale.ROOT) } + }.orEmpty() } } + +// keys used in `pkg` param +const val START_PAGE = "startpage" +const val MARKET_STORE = "marketstore" +const val WEB_SUGGESTION = "suggestion" +const val HEADER = "header" +const val CONTACT = "contact" +const val FILES = "files" +const val SPACE = "space" +const val SPACE_MINI = "space_mini" +const val LOADING = "loading" +const val ERROR = "error" +const val SETTINGS = "setting" +const val SHORTCUT = "shortcut" +const val HISTORY = "recent_keyword" +const val HEADER_JUSTIFY = "header_justify" +const val CALCULATOR = "calculator" diff --git a/lawnchair/src/app/lawnchair/search/adapter/SearchTargetUtils.kt b/lawnchair/src/app/lawnchair/search/adapter/SearchTargetUtils.kt deleted file mode 100644 index 20817f08dd..0000000000 --- a/lawnchair/src/app/lawnchair/search/adapter/SearchTargetUtils.kt +++ /dev/null @@ -1,125 +0,0 @@ -package app.lawnchair.search.adapter - -import android.content.pm.ShortcutInfo -import android.os.Bundle -import android.os.Process -import androidx.core.os.bundleOf -import com.android.app.search.LayoutType -import com.android.launcher3.model.data.AppInfo -import com.android.launcher3.util.ComponentKey -import java.security.MessageDigest -import okio.ByteString - -// We're generate hash key as alt in id, so we can avoid -// the wrong action when click, -// TODO remove when we properly manage creating search target -private fun generateHashKey(input: String): String = - ByteString.of(*MessageDigest.getInstance("SHA-256").digest(input.toByteArray())).hex() - -fun createSearchTarget(appInfo: AppInfo, asRow: Boolean = false): SearchTargetCompat { - val componentName = appInfo.componentName - val user = appInfo.user - return SearchTargetCompat.Builder( - SearchTargetCompat.RESULT_TYPE_APPLICATION, - if (asRow) LayoutType.SMALL_ICON_HORIZONTAL_TEXT else LayoutType.ICON_SINGLE_VERTICAL_TEXT, - generateHashKey(ComponentKey(componentName, user).toString()), - ) - .setPackageName(componentName.packageName) - .setUserHandle(user) - .setExtras(bundleOf("class" to componentName.className)) - .build() -} - -fun createSearchTarget(shortcutInfo: ShortcutInfo): SearchTargetCompat { - return SearchTargetCompat.Builder( - SearchTargetCompat.RESULT_TYPE_SHORTCUT, - LayoutType.SMALL_ICON_HORIZONTAL_TEXT, - "shortcut_" + generateHashKey("${shortcutInfo.`package`}|${shortcutInfo.userHandle}|${shortcutInfo.id}"), - ) - .setShortcutInfo(shortcutInfo) - .setUserHandle(shortcutInfo.userHandle) - .setExtras(Bundle()) - .build() -} - -fun createSearchTarget( - id: String, - action: SearchActionCompat, - pkg: String, - extras: Bundle = Bundle(), -): SearchTargetCompat { - return SearchTargetCompat.Builder( - SearchTargetCompat.RESULT_TYPE_REDIRECTION, - LayoutType.ICON_HORIZONTAL_TEXT, - generateHashKey(id), - ) - .setPackageName(pkg) - .setUserHandle(Process.myUserHandle()) - .setSearchAction(action) - .setExtras(extras) - .build() -} - -fun createCalculatorTarget( - id: String, - action: SearchActionCompat, - extras: Bundle = Bundle(), -): SearchTargetCompat { - return SearchTargetCompat.Builder( - SearchTargetCompat.RESULT_TYPE_CALCULATOR, - LayoutType.CALCULATOR, - generateHashKey(id), - ) - .setPackageName("") - .setUserHandle(Process.myUserHandle()) - .setSearchAction(action) - .setExtras(extras) - .build() -} - -fun createSearchTarget( - id: String, - action: SearchActionCompat, - layoutType: String, - targetCompat: Int, - pkg: String, - extras: Bundle = Bundle(), -): SearchTargetCompat { - return SearchTargetCompat.Builder( - targetCompat, - layoutType, - generateHashKey(id), - ) - .setPackageName(pkg) - .setUserHandle(Process.myUserHandle()) - .setSearchAction(action) - .setExtras(extras) - .build() -} - -fun createDividerTarget(): SearchTargetCompat { - return SearchTargetCompat.Builder( - SearchTargetCompat.RESULT_TYPE_SHORTCUT, - LayoutType.DIVIDER, - "divider", - ) - .setPackageName("") - .setUserHandle(Process.myUserHandle()) - .build() -} - -const val START_PAGE = "startpage" -const val MARKET_STORE = "marketstore" -const val WEB_SUGGESTION = "suggestion" -const val HEADER = "header" -const val CONTACT = "contact" -const val FILES = "files" -const val SPACE = "space" -const val SPACE_MINI = "space_mini" -const val LOADING = "loading" -const val ERROR = "error" -const val SETTINGS = "setting" -const val SHORTCUT = "shortcut" -const val HISTORY = "recent_keyword" -const val HEADER_JUSTIFY = "header_justify" -const val CALCULATOR = "calculator" diff --git a/lawnchair/src/app/lawnchair/search/algorithms/LawnchairAppSearchAlgorithm.kt b/lawnchair/src/app/lawnchair/search/algorithms/LawnchairAppSearchAlgorithm.kt index 4ae3c45a0c..380db854c8 100644 --- a/lawnchair/src/app/lawnchair/search/algorithms/LawnchairAppSearchAlgorithm.kt +++ b/lawnchair/src/app/lawnchair/search/algorithms/LawnchairAppSearchAlgorithm.kt @@ -3,10 +3,9 @@ package app.lawnchair.search.algorithms import android.content.Context import android.os.Handler import app.lawnchair.preferences2.PreferenceManager2 -import app.lawnchair.search.adapter.GenerateSearchTarget +import app.lawnchair.search.adapter.SearchTargetFactory import app.lawnchair.search.adapter.SPACE import app.lawnchair.search.adapter.SearchTargetCompat -import app.lawnchair.search.adapter.createSearchTarget import app.lawnchair.util.isDefaultLauncher import com.android.launcher3.LauncherAppState import com.android.launcher3.allapps.BaseAllAppsAdapter @@ -25,7 +24,8 @@ class LawnchairAppSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm(c private val appState = LauncherAppState.getInstance(context) private val resultHandler = Handler(Executors.MAIN_EXECUTOR.looper) - private val generateSearchTarget = GenerateSearchTarget(context) + // todo maybe use D.I.? + private val searchTargetFactory = SearchTargetFactory(context) private var hiddenApps: Set = setOf() @@ -33,21 +33,21 @@ class LawnchairAppSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm(c private var enableFuzzySearch = false private var maxResultsCount = 5 - private val pref2 = PreferenceManager2.getInstance(context) + private val prefs2 = PreferenceManager2.getInstance(context) val coroutineScope = CoroutineScope(context = Dispatchers.IO) init { - pref2.enableFuzzySearch.onEach(launchIn = coroutineScope) { + prefs2.enableFuzzySearch.onEach(launchIn = coroutineScope) { enableFuzzySearch = it } - pref2.hiddenApps.onEach(launchIn = coroutineScope) { + prefs2.hiddenApps.onEach(launchIn = coroutineScope) { hiddenApps = it } - pref2.hiddenAppsInSearch.onEach(launchIn = coroutineScope) { + prefs2.hiddenAppsInSearch.onEach(launchIn = coroutineScope) { hiddenAppsInSearch = it } - pref2.maxAppSearchResultCount.onEach(launchIn = coroutineScope) { + prefs2.maxAppSearchResultCount.onEach(launchIn = coroutineScope) { maxResultsCount = it } } @@ -86,22 +86,22 @@ class LawnchairAppSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm(c if (appResults.isNotEmpty()) { if (appResults.size == 1) { val singleAppResult = appResults.firstOrNull() - singleAppResult?.let { searchTargets.add(createSearchTarget(it, true)) } + singleAppResult?.let { searchTargets.add(searchTargetFactory.createAppSearchTarget(it, true)) } if (context.isDefaultLauncher()) { val shortcuts = singleAppResult?.let { searchUtils.getShortcuts(it, context) } if (shortcuts != null) { if (shortcuts.isNotEmpty()) { - searchTargets.addAll(shortcuts.map(::createSearchTarget)) + searchTargets.addAll(shortcuts.map(searchTargetFactory::createShortcutTarget)) } } } } else { - appResults.mapTo(searchTargets, ::createSearchTarget) + appResults.mapTo(searchTargets, searchTargetFactory::createAppSearchTarget) } - searchTargets.add(generateSearchTarget.getHeaderTarget(SPACE)) + searchTargets.add(searchTargetFactory.createHeaderTarget(SPACE)) } - generateSearchTarget.getMarketSearchItem(query)?.let { searchTargets.add(it) } + searchTargetFactory.createMarketSearchTarget(query)?.let { searchTargets.add(it) } setFirstItemQuickLaunch(searchTargets) val adapterItems = transformSearchResults(searchTargets) diff --git a/lawnchair/src/app/lawnchair/search/algorithms/LawnchairLocalSearchAlgorithm.kt b/lawnchair/src/app/lawnchair/search/algorithms/LawnchairLocalSearchAlgorithm.kt index 26e3ba41eb..7dfb7708f2 100644 --- a/lawnchair/src/app/lawnchair/search/algorithms/LawnchairLocalSearchAlgorithm.kt +++ b/lawnchair/src/app/lawnchair/search/algorithms/LawnchairLocalSearchAlgorithm.kt @@ -8,16 +8,15 @@ import app.lawnchair.search.adapter.CALCULATOR import app.lawnchair.search.adapter.CONTACT import app.lawnchair.search.adapter.ERROR import app.lawnchair.search.adapter.FILES -import app.lawnchair.search.adapter.GenerateSearchTarget import app.lawnchair.search.adapter.HEADER_JUSTIFY import app.lawnchair.search.adapter.HISTORY import app.lawnchair.search.adapter.LOADING import app.lawnchair.search.adapter.SETTINGS import app.lawnchair.search.adapter.SPACE -import app.lawnchair.search.adapter.SearchResult +import app.lawnchair.search.model.SearchResult import app.lawnchair.search.adapter.SearchTargetCompat +import app.lawnchair.search.adapter.SearchTargetFactory import app.lawnchair.search.adapter.WEB_SUGGESTION -import app.lawnchair.search.adapter.createSearchTarget import app.lawnchair.search.algorithms.data.Calculation import app.lawnchair.search.algorithms.data.ContactInfo import app.lawnchair.search.algorithms.data.IFileInfo @@ -58,7 +57,7 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm private val appState = LauncherAppState.getInstance(context) private val resultHandler = Handler(Executors.MAIN_EXECUTOR.looper) - private val generateSearchTarget = GenerateSearchTarget(context) + private val searchTargetFactory = SearchTargetFactory(context) private var hiddenApps: Set = emptySet() @@ -180,6 +179,8 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm } } + + private fun getAppSearchResults( apps: MutableList, query: String, @@ -208,16 +209,18 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm ): Flow> = flow { val searchTargets = mutableListOf() - searchTargets.add(generateSearchTarget.getHeaderTarget(SPACE)) + searchTargets.add(searchTargetFactory.createHeaderTarget(SPACE)) if (useWebSuggestions) { withContext(Dispatchers.IO) { - searchTargets.add(generateSearchTarget.getWebSearchItem(query, webSuggestionsProvider)) + searchTargets.add(searchTargetFactory.createWebSearchTarget(query, webSuggestionsProvider)) } } - generateSearchTarget.getMarketSearchItem(query)?.let { searchTargets.add(it) } + searchTargetFactory.createMarketSearchTarget(query)?.let { searchTargets.add(it) } emit(transformSearchResults(searchTargets)) } + + private fun parseAppSearchResults( appResults: List, searchTargets: MutableList, @@ -225,19 +228,19 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm if (appResults.isNotEmpty()) { if (appResults.size == 1) { val singleAppResult = appResults.firstOrNull() - singleAppResult?.let { searchTargets.add(createSearchTarget(it, true)) } + singleAppResult?.let { searchTargets.add(searchTargetFactory.createAppSearchTarget(it, true)) } if (context.isDefaultLauncher()) { val shortcuts = singleAppResult?.let { searchUtils.getShortcuts(it, context) } if (shortcuts != null) { if (shortcuts.isNotEmpty()) { - searchTargets.addAll(shortcuts.map(::createSearchTarget)) + searchTargets.addAll(shortcuts.map(searchTargetFactory::createShortcutTarget)) } } } } else { - appResults.mapTo(searchTargets, ::createSearchTarget) + appResults.mapTo(searchTargets, searchTargetFactory::createAppSearchTarget) } - searchTargets.add(generateSearchTarget.getHeaderTarget(SPACE)) + searchTargets.add(searchTargetFactory.createHeaderTarget(SPACE)) } } @@ -249,11 +252,11 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm val suggestions = filterByType(localSearchResults, WEB_SUGGESTION) if (suggestions.isNotEmpty()) { val suggestionsHeader = - generateSearchTarget.getHeaderTarget(context.getString(R.string.all_apps_search_result_suggestions)) + searchTargetFactory.createHeaderTarget(context.getString(R.string.all_apps_search_result_suggestions)) searchTargets.add(suggestionsHeader) searchTargets.addAll( suggestions.map { - generateSearchTarget.getSuggestionTarget(it.resultData as String, suggestionProvider) + searchTargetFactory.createWebSuggestionsTarget(it.resultData as String, suggestionProvider) }, ) } @@ -262,46 +265,46 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm val calcData = calculator?.resultData as? Calculation if (calcData != null && calcData.isValid) { val calculatorHeader = - generateSearchTarget.getHeaderTarget(context.getString(R.string.all_apps_search_result_calculator)) + searchTargetFactory.createHeaderTarget(context.getString(R.string.all_apps_search_result_calculator)) searchTargets.add(calculatorHeader) searchTargets.add( - generateSearchTarget.getCalculationTarget(calcData), + searchTargetFactory.createCalculatorTarget(calcData), ) } val contacts = filterByType(localSearchResults, CONTACT) if (contacts.isNotEmpty()) { val contactsHeader = - generateSearchTarget.getHeaderTarget(context.getString(R.string.all_apps_search_result_contacts_from_device)) + searchTargetFactory.createHeaderTarget(context.getString(R.string.all_apps_search_result_contacts_from_device)) searchTargets.add(contactsHeader) - searchTargets.addAll(contacts.map { generateSearchTarget.getContactSearchItem(it.resultData as ContactInfo) }) + searchTargets.addAll(contacts.map { searchTargetFactory.createContactsTarget(it.resultData as ContactInfo) }) } val settings = filterByType(localSearchResults, SETTINGS) if (settings.isNotEmpty()) { val settingsHeader = - generateSearchTarget.getHeaderTarget(context.getString(R.string.all_apps_search_result_settings_entry_from_device)) + searchTargetFactory.createHeaderTarget(context.getString(R.string.all_apps_search_result_settings_entry_from_device)) searchTargets.add(settingsHeader) - searchTargets.addAll(settings.mapNotNull { generateSearchTarget.getSettingSearchItem(it.resultData as SettingInfo) }) + searchTargets.addAll(settings.mapNotNull { searchTargetFactory.createSettingsTarget(it.resultData as SettingInfo) }) } // todo refactor to only show when search is first clicked val recentKeyword = filterByType(localSearchResults, HISTORY) if (recentKeyword.isNotEmpty()) { - val recentKeywordHeader = generateSearchTarget.getHeaderTarget( + val recentKeywordHeader = searchTargetFactory.createHeaderTarget( context.getString(R.string.search_pref_result_history_title), HEADER_JUSTIFY, ) searchTargets.add(recentKeywordHeader) - searchTargets.addAll(recentKeyword.map { generateSearchTarget.getRecentKeywordTarget(it.resultData as RecentKeyword, suggestionProvider) }) + searchTargets.addAll(recentKeyword.map { searchTargetFactory.createSearchHistoryTarget(it.resultData as RecentKeyword, suggestionProvider) }) } val files = filterByType(localSearchResults, FILES) if (files.isNotEmpty()) { val filesHeader = - generateSearchTarget.getHeaderTarget(context.getString(R.string.all_apps_search_result_files)) + searchTargetFactory.createHeaderTarget(context.getString(R.string.all_apps_search_result_files)) searchTargets.add(filesHeader) - searchTargets.addAll(files.map { generateSearchTarget.getFileInfoSearchItem(it.resultData as IFileInfo) }) + searchTargets.addAll(files.map { searchTargetFactory.createFilesTarget(it.resultData as IFileInfo) }) } } diff --git a/lawnchair/src/app/lawnchair/search/adapter/SearchResult.kt b/lawnchair/src/app/lawnchair/search/model/SearchResult.kt similarity index 68% rename from lawnchair/src/app/lawnchair/search/adapter/SearchResult.kt rename to lawnchair/src/app/lawnchair/search/model/SearchResult.kt index daf90504a3..8ff69b389d 100644 --- a/lawnchair/src/app/lawnchair/search/adapter/SearchResult.kt +++ b/lawnchair/src/app/lawnchair/search/model/SearchResult.kt @@ -1,4 +1,4 @@ -package app.lawnchair.search.adapter +package app.lawnchair.search.model data class SearchResult( val resultType: String, diff --git a/lawnchair/src/app/lawnchair/search/adapter/SearchResultActionCallBack.kt b/lawnchair/src/app/lawnchair/search/model/SearchResultActionCallBack.kt similarity index 61% rename from lawnchair/src/app/lawnchair/search/adapter/SearchResultActionCallBack.kt rename to lawnchair/src/app/lawnchair/search/model/SearchResultActionCallBack.kt index 79f6224ebc..9d6718d2d2 100644 --- a/lawnchair/src/app/lawnchair/search/adapter/SearchResultActionCallBack.kt +++ b/lawnchair/src/app/lawnchair/search/model/SearchResultActionCallBack.kt @@ -1,4 +1,4 @@ -package app.lawnchair.search.adapter +package app.lawnchair.search.model interface SearchResultActionCallBack { fun action()