Refactor search result generation
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+176
-64
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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<String> = 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)
|
||||
|
||||
@@ -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<String> = emptySet()
|
||||
|
||||
@@ -180,6 +179,8 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun getAppSearchResults(
|
||||
apps: MutableList<AppInfo>,
|
||||
query: String,
|
||||
@@ -208,16 +209,18 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm
|
||||
): Flow<List<BaseAllAppsAdapter.AdapterItem>> = flow {
|
||||
val searchTargets = mutableListOf<SearchTargetCompat>()
|
||||
|
||||
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<AppInfo>,
|
||||
searchTargets: MutableList<SearchTargetCompat>,
|
||||
@@ -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) })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package app.lawnchair.search.adapter
|
||||
package app.lawnchair.search.model
|
||||
|
||||
data class SearchResult(
|
||||
val resultType: String,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package app.lawnchair.search.adapter
|
||||
package app.lawnchair.search.model
|
||||
|
||||
interface SearchResultActionCallBack {
|
||||
fun action()
|
||||
Reference in New Issue
Block a user