From b7452f6bcefe1d6e91457cf1bc0cfe012734441b Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Wed, 8 Nov 2023 19:02:53 +0100 Subject: [PATCH] Plugin stuff, idk, i wrote that shit yesterday --- .../plugin/config/SearchPluginConfig.kt | 38 ++++++++++++ .../plugin/config/StorageStrategy.kt | 11 +++- .../plugin/contracts/FilePluginContract.kt | 2 - .../plugin/contracts/PluginContract.kt | 1 + .../files/providers/PluginFileProvider.kt | 61 ++++++++++++++----- .../launcher2/sdk/base/BasePluginProvider.kt | 7 +++ .../sdk/base/SearchPluginProvider.kt | 12 ++-- .../java/de/mm20/launcher2/sdk/files/File.kt | 5 -- .../mm20/launcher2/sdk/files/FileProvider.kt | 7 ++- 9 files changed, 113 insertions(+), 31 deletions(-) create mode 100644 core/shared/src/main/java/de/mm20/launcher2/plugin/config/SearchPluginConfig.kt diff --git a/core/shared/src/main/java/de/mm20/launcher2/plugin/config/SearchPluginConfig.kt b/core/shared/src/main/java/de/mm20/launcher2/plugin/config/SearchPluginConfig.kt new file mode 100644 index 00000000..4f68e856 --- /dev/null +++ b/core/shared/src/main/java/de/mm20/launcher2/plugin/config/SearchPluginConfig.kt @@ -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, + ) + ) + } + } +} \ No newline at end of file diff --git a/core/shared/src/main/java/de/mm20/launcher2/plugin/config/StorageStrategy.kt b/core/shared/src/main/java/de/mm20/launcher2/plugin/config/StorageStrategy.kt index ea7ddbc1..bfa6a46c 100644 --- a/core/shared/src/main/java/de/mm20/launcher2/plugin/config/StorageStrategy.kt +++ b/core/shared/src/main/java/de/mm20/launcher2/plugin/config/StorageStrategy.kt @@ -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 + } + } + } } \ No newline at end of file diff --git a/core/shared/src/main/java/de/mm20/launcher2/plugin/contracts/FilePluginContract.kt b/core/shared/src/main/java/de/mm20/launcher2/plugin/contracts/FilePluginContract.kt index 415a1bc8..3d08f3f6 100644 --- a/core/shared/src/main/java/de/mm20/launcher2/plugin/contracts/FilePluginContract.kt +++ b/core/shared/src/main/java/de/mm20/launcher2/plugin/contracts/FilePluginContract.kt @@ -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 diff --git a/core/shared/src/main/java/de/mm20/launcher2/plugin/contracts/PluginContract.kt b/core/shared/src/main/java/de/mm20/launcher2/plugin/contracts/PluginContract.kt index 3fcf380a..ccbeb2bf 100644 --- a/core/shared/src/main/java/de/mm20/launcher2/plugin/contracts/PluginContract.kt +++ b/core/shared/src/main/java/de/mm20/launcher2/plugin/contracts/PluginContract.kt @@ -6,5 +6,6 @@ object PluginContract { object Methods { const val GetType = "getType" const val GetState = "getState" + const val GetConfig = "getConfig" } } \ No newline at end of file diff --git a/data/files/src/main/java/de/mm20/launcher2/files/providers/PluginFileProvider.kt b/data/files/src/main/java/de/mm20/launcher2/files/providers/PluginFileProvider.kt index a3384664..c7c36e0e 100644 --- a/data/files/src/main/java/de/mm20/launcher2/files/providers/PluginFileProvider.kt +++ b/data/files/src/main/java/de/mm20/launcher2/files/providers/PluginFileProvider.kt @@ -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? { + 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, ) diff --git a/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/base/BasePluginProvider.kt b/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/base/BasePluginProvider.kt index dfb6375d..abfa7593 100644 --- a/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/base/BasePluginProvider.kt +++ b/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/base/BasePluginProvider.kt @@ -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 } diff --git a/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/base/SearchPluginProvider.kt b/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/base/SearchPluginProvider.kt index b7cb5309..48bdab4c 100644 --- a/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/base/SearchPluginProvider.kt +++ b/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/base/SearchPluginProvider.kt @@ -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 : BasePluginProvider() { +abstract class SearchPluginProvider( + private val config: SearchPluginConfig, +) : BasePluginProvider() { /** * Search for items matching the given query @@ -108,6 +108,10 @@ abstract class SearchPluginProvider : BasePluginProvider() { } } + final override fun getPluginConfig(): Bundle { + return config.toBundle() + } + internal abstract fun createCursor(capacity: Int): MatrixCursor internal abstract fun writeToCursor(cursor: MatrixCursor, item: T) } \ No newline at end of file diff --git a/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/files/File.kt b/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/files/File.kt index 20198f09..73ac8304 100644 --- a/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/files/File.kt +++ b/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/files/File.kt @@ -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, ) \ No newline at end of file diff --git a/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/files/FileProvider.kt b/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/files/FileProvider.kt index 537f1af4..3c0af293 100644 --- a/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/files/FileProvider.kt +++ b/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/files/FileProvider.kt @@ -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() { +abstract class FileProvider( + config: SearchPluginConfig = SearchPluginConfig(), +) : SearchPluginProvider(config) { abstract override suspend fun search(query: String): List final override fun getPluginType(): PluginType { @@ -23,7 +26,6 @@ abstract class FileProvider : SearchPluginProvider() { FilePluginContract.FileColumns.ContentUri, FilePluginContract.FileColumns.ThumbnailUri, FilePluginContract.FileColumns.IsDirectory, - FilePluginContract.FileColumns.StorageStrategy ), capacity, ) @@ -40,7 +42,6 @@ abstract class FileProvider : SearchPluginProvider() { item.uri.toString(), item.thumbnailUri?.toString(), if (item.isDirectory) 1 else 0, - item.storageStrategy.name, ) ) }