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
* 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 StorageStrategy = "storage_strategy"
/**
* Whether the file is a directory.
* Type: Int

View File

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

View File

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

View File

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

View File

@ -1,19 +1,19 @@
package de.mm20.launcher2.sdk.base
import android.content.ContentValues
import android.content.Context
import android.content.pm.PackageManager
import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
import android.os.Bundle
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 kotlinx.coroutines.async
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
@ -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 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.
*/
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 de.mm20.launcher2.plugin.PluginType
import de.mm20.launcher2.plugin.config.SearchPluginConfig
import de.mm20.launcher2.plugin.contracts.FilePluginContract
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>
final override fun getPluginType(): PluginType {
@ -23,7 +26,6 @@ abstract class FileProvider : SearchPluginProvider<File>() {
FilePluginContract.FileColumns.ContentUri,
FilePluginContract.FileColumns.ThumbnailUri,
FilePluginContract.FileColumns.IsDirectory,
FilePluginContract.FileColumns.StorageStrategy
),
capacity,
)
@ -40,7 +42,6 @@ abstract class FileProvider : SearchPluginProvider<File>() {
item.uri.toString(),
item.thumbnailUri?.toString(),
if (item.isDirectory) 1 else 0,
item.storageStrategy.name,
)
)
}