Plugin stuff, idk, i wrote that shit yesterday

This commit is contained in:
MM20 2023-11-08 19:02:53 +01:00
parent c5e524c702
commit b7452f6bce
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
9 changed files with 113 additions and 31 deletions

View File

@ -0,0 +1,38 @@
package de.mm20.launcher2.plugin.config
import android.os.Bundle
import de.mm20.launcher2.plugin.config.StorageStrategy
data class SearchPluginConfig(
/**
* Icon resource to indicate that a result is from this provider
*/
val providerIconRes: Int? = null,
/**
* Strategy to store items from this provider in the launcher database
* @see [StorageStrategy]
*/
val storageStrategy: StorageStrategy = StorageStrategy.StoreCopy,
) {
fun toBundle(): Bundle {
return Bundle().apply {
putInt("providerIconRes", providerIconRes ?: 0)
putString("storageStrategy", storageStrategy.name)
}
}
companion object {
operator fun invoke(bundle: Bundle): SearchPluginConfig? {
return SearchPluginConfig(
providerIconRes = bundle.getInt("providerIconRes", 0).takeIf { it != 0 },
storageStrategy = StorageStrategy.valueOfOrElse(
bundle.getString(
"storageStrategy",
StorageStrategy.StoreCopy.name
),
StorageStrategy.StoreCopy,
)
)
}
}
}

View File

@ -21,5 +21,14 @@ enum class StorageStrategy {
* Use this strategy if your plugin needs to perform network requests to retrieve search * Use this strategy if your plugin needs to perform network requests to retrieve search
* results and if you don't want to implement a cache for search results. * results and if you don't want to implement a cache for search results.
*/ */
StoreCopy, StoreCopy;
companion object {
fun valueOfOrElse(value: String, default: StorageStrategy): StorageStrategy {
return try {
valueOf(value)
} catch (e: IllegalArgumentException) {
default
}
}
}
} }

View File

@ -42,8 +42,6 @@ abstract class FilePluginContract {
const val ThumbnailUri = "thumbnail_uri" const val ThumbnailUri = "thumbnail_uri"
const val StorageStrategy = "storage_strategy"
/** /**
* Whether the file is a directory. * Whether the file is a directory.
* Type: Int * Type: Int

View File

@ -6,5 +6,6 @@ object PluginContract {
object Methods { object Methods {
const val GetType = "getType" const val GetType = "getType"
const val GetState = "getState" const val GetState = "getState"
const val GetConfig = "getConfig"
} }
} }

View File

@ -6,9 +6,12 @@ import android.net.Uri
import android.os.CancellationSignal import android.os.CancellationSignal
import android.util.Log import android.util.Log
import androidx.core.database.getStringOrNull import androidx.core.database.getStringOrNull
import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.plugin.Plugin import de.mm20.launcher2.plugin.Plugin
import de.mm20.launcher2.plugin.config.SearchPluginConfig
import de.mm20.launcher2.plugin.config.StorageStrategy import de.mm20.launcher2.plugin.config.StorageStrategy
import de.mm20.launcher2.plugin.contracts.FilePluginContract import de.mm20.launcher2.plugin.contracts.FilePluginContract
import de.mm20.launcher2.plugin.contracts.PluginContract
import de.mm20.launcher2.plugin.contracts.SearchPluginContract import de.mm20.launcher2.plugin.contracts.SearchPluginContract
import de.mm20.launcher2.search.File import de.mm20.launcher2.search.File
import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentMapOf
@ -32,12 +35,19 @@ class PluginFileProvider(
it.invokeOnCancellation { it.invokeOnCancellation {
cancellationSignal.cancel() cancellationSignal.cancel()
} }
val cursor = context.contentResolver.query( val cursor = try {
uri, context.contentResolver.query(
null, uri,
null, null,
cancellationSignal null,
) cancellationSignal
)
} catch (e: Exception) {
Log.e("MM20", "Plugin ${plugin.authority} threw exception")
CrashReporter.logException(e)
it.resume(emptyList())
return@suspendCancellableCoroutine
}
if (cursor == null) { if (cursor == null) {
Log.e("MM20", "Plugin ${plugin.authority} returned null cursor") Log.e("MM20", "Plugin ${plugin.authority} returned null cursor")
@ -50,6 +60,26 @@ class PluginFileProvider(
} }
} }
private fun getPluginConfig(): SearchPluginConfig? {
val configBundle = try {
context.contentResolver.call(
Uri.Builder()
.scheme("content")
.authority(plugin.authority)
.build(),
PluginContract.Methods.GetConfig,
null,
null
) ?: return null
} catch (e: Exception) {
Log.e("MM20", "Plugin ${plugin.authority} threw exception")
CrashReporter.logException(e)
return null
}
return SearchPluginConfig(configBundle)
}
suspend fun getFile(id: String): File? { suspend fun getFile(id: String): File? {
val uri = Uri.Builder() val uri = Uri.Builder()
.scheme("content") .scheme("content")
@ -76,6 +106,14 @@ class PluginFileProvider(
} }
private fun fromCursor(cursor: Cursor): List<File>? { private fun fromCursor(cursor: Cursor): List<File>? {
val config = getPluginConfig()
if (config == null) {
Log.e("MM20", "Plugin ${plugin.authority} returned null config")
cursor.close()
return null
}
val idIndex = cursor val idIndex = cursor
.getColumnIndex(FilePluginContract.FileColumns.Id) .getColumnIndex(FilePluginContract.FileColumns.Id)
.takeIf { it >= 0 } .takeIf { it >= 0 }
@ -95,9 +133,6 @@ class PluginFileProvider(
val thumbnailUriIndex = val thumbnailUriIndex =
cursor.getColumnIndex(FilePluginContract.FileColumns.ThumbnailUri) cursor.getColumnIndex(FilePluginContract.FileColumns.ThumbnailUri)
.takeIf { it >= 0 } .takeIf { it >= 0 }
val storageStrategyIndex =
cursor.getColumnIndex(FilePluginContract.FileColumns.StorageStrategy)
.takeIf { it >= 0 }
val directoryIndex = val directoryIndex =
cursor.getColumnIndex(FilePluginContract.FileColumns.IsDirectory).takeIf { it >= 0 } cursor.getColumnIndex(FilePluginContract.FileColumns.IsDirectory).takeIf { it >= 0 }
@ -116,13 +151,7 @@ class PluginFileProvider(
thumbnailUri = thumbnailUriIndex?.let { thumbnailUri = thumbnailUriIndex?.let {
cursor.getStringOrNull(it) cursor.getStringOrNull(it)
}?.let { Uri.parse(it) }, }?.let { Uri.parse(it) },
storageStrategy = try { storageStrategy = config.storageStrategy,
storageStrategyIndex?.let {
StorageStrategy.valueOf(cursor.getString(it))
}
} catch (e: IllegalArgumentException) {
null
} ?: StorageStrategy.StoreCopy,
isDirectory = directoryIndex?.let { cursor.getInt(it) } == 1, isDirectory = directoryIndex?.let { cursor.getInt(it) } == 1,
authority = plugin.authority, authority = plugin.authority,
) )

View File

@ -34,6 +34,9 @@ abstract class BasePluginProvider : ContentProvider() {
} }
} }
} }
PluginContract.Methods.GetConfig -> {
getPluginConfig()
}
else -> super.call(method, arg, extras) else -> super.call(method, arg, extras)
} }
@ -41,6 +44,10 @@ abstract class BasePluginProvider : ContentProvider() {
internal abstract fun getPluginType(): PluginType internal abstract fun getPluginType(): PluginType
internal open fun getPluginConfig(): Bundle {
return Bundle.EMPTY
}
open suspend fun getPluginState(): PluginState { open suspend fun getPluginState(): PluginState {
return PluginState.Ready return PluginState.Ready
} }

View File

@ -1,19 +1,19 @@
package de.mm20.launcher2.sdk.base package de.mm20.launcher2.sdk.base
import android.content.ContentValues import android.content.ContentValues
import android.content.Context
import android.content.pm.PackageManager
import android.database.Cursor import android.database.Cursor
import android.database.MatrixCursor import android.database.MatrixCursor
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.CancellationSignal import android.os.CancellationSignal
import de.mm20.launcher2.plugin.contracts.PluginContract import de.mm20.launcher2.plugin.config.SearchPluginConfig
import de.mm20.launcher2.plugin.contracts.SearchPluginContract import de.mm20.launcher2.plugin.contracts.SearchPluginContract
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
abstract class SearchPluginProvider<T> : BasePluginProvider() { abstract class SearchPluginProvider<T>(
private val config: SearchPluginConfig,
) : BasePluginProvider() {
/** /**
* Search for items matching the given query * Search for items matching the given query
@ -108,6 +108,10 @@ abstract class SearchPluginProvider<T> : BasePluginProvider() {
} }
} }
final override fun getPluginConfig(): Bundle {
return config.toBundle()
}
internal abstract fun createCursor(capacity: Int): MatrixCursor internal abstract fun createCursor(capacity: Int): MatrixCursor
internal abstract fun writeToCursor(cursor: MatrixCursor, item: T) internal abstract fun writeToCursor(cursor: MatrixCursor, item: T)
} }

View File

@ -50,9 +50,4 @@ data class File(
* If null, a default icon will be shown, depending on the file type. * If null, a default icon will be shown, depending on the file type.
*/ */
val thumbnailUri: Uri? = null, val thumbnailUri: Uri? = null,
/**
* How the launcher should store this file in its database (i.e. when the user adds it to favorites).
*/
val storageStrategy: StorageStrategy = StorageStrategy.StoreCopy,
) )

View File

@ -2,10 +2,13 @@ package de.mm20.launcher2.sdk.files
import android.database.MatrixCursor import android.database.MatrixCursor
import de.mm20.launcher2.plugin.PluginType import de.mm20.launcher2.plugin.PluginType
import de.mm20.launcher2.plugin.config.SearchPluginConfig
import de.mm20.launcher2.plugin.contracts.FilePluginContract import de.mm20.launcher2.plugin.contracts.FilePluginContract
import de.mm20.launcher2.sdk.base.SearchPluginProvider import de.mm20.launcher2.sdk.base.SearchPluginProvider
abstract class FileProvider : SearchPluginProvider<File>() { abstract class FileProvider(
config: SearchPluginConfig = SearchPluginConfig(),
) : SearchPluginProvider<File>(config) {
abstract override suspend fun search(query: String): List<File> abstract override suspend fun search(query: String): List<File>
final override fun getPluginType(): PluginType { final override fun getPluginType(): PluginType {
@ -23,7 +26,6 @@ abstract class FileProvider : SearchPluginProvider<File>() {
FilePluginContract.FileColumns.ContentUri, FilePluginContract.FileColumns.ContentUri,
FilePluginContract.FileColumns.ThumbnailUri, FilePluginContract.FileColumns.ThumbnailUri,
FilePluginContract.FileColumns.IsDirectory, FilePluginContract.FileColumns.IsDirectory,
FilePluginContract.FileColumns.StorageStrategy
), ),
capacity, capacity,
) )
@ -40,7 +42,6 @@ abstract class FileProvider : SearchPluginProvider<File>() {
item.uri.toString(), item.uri.toString(),
item.thumbnailUri?.toString(), item.thumbnailUri?.toString(),
if (item.isDirectory) 1 else 0, if (item.isDirectory) 1 else 0,
item.storageStrategy.name,
) )
) )
} }