Plugins: bringup

This commit is contained in:
MM20
2023-11-05 19:02:59 +01:00
parent 7da84a747f
commit 801caf9dd6
54 changed files with 1335 additions and 88 deletions
@@ -0,0 +1,46 @@
package de.mm20.launcher2.sdk.base
import android.content.ContentProvider
import android.os.Bundle
import de.mm20.launcher2.plugin.PluginState
import de.mm20.launcher2.plugin.PluginType
import de.mm20.launcher2.plugin.contracts.PluginContract
import kotlinx.coroutines.runBlocking
abstract class BasePluginProvider : ContentProvider() {
override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
return when (method) {
PluginContract.Methods.GetType -> Bundle().apply {
putString("type", getPluginType().name)
}
PluginContract.Methods.GetState -> Bundle().apply {
val state = runBlocking {
getPluginState()
}
when (state) {
is PluginState.SetupRequired -> {
putString("type", "SetupRequired")
putString("setupActivity", state.setupActivity)
putString("message", state.message)
}
is PluginState.Ready -> {
putString("type", "Ready")
}
}
}
else -> super.call(method, arg, extras)
}
}
internal abstract fun getPluginType(): PluginType
open suspend fun getPluginState(): PluginState {
return PluginState.Ready
}
}
@@ -0,0 +1,121 @@
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.contracts.SearchPluginContract
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
abstract class SearchPluginProvider<T> : BasePluginProvider() {
/**
* Search for items matching the given query
* @param query The query to search for
*/
abstract suspend fun search(query: String): List<T>
abstract suspend fun get(id: String): T?
override fun onCreate(): Boolean {
return true
}
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? {
return query(uri, projection, null, null)
}
override fun query(
uri: Uri,
projection: Array<out String>?,
queryArgs: Bundle?,
cancellationSignal: CancellationSignal?
): Cursor? {
val context = context ?: return null
checkPermissionOrThrow(context)
when {
uri.path == SearchPluginContract.Paths.Search -> {
val query =
uri.getQueryParameter(SearchPluginContract.Paths.QueryParam) ?: return null
val results = search(query, cancellationSignal)
val cursor = createCursor(results.size)
for (result in results) {
writeToCursor(cursor, result)
}
return null
}
uri.pathSegments.size == 2 && uri.pathSegments.first() == SearchPluginContract.Paths.Root -> {
val id = uri.pathSegments[1]
val result = runBlocking {
get(id)
}
return if (result != null) {
val cursor = createCursor(1)
writeToCursor(cursor, result)
cursor
} else {
createCursor(0)
}
}
}
return null
}
override fun getType(uri: Uri): String? {
throw UnsupportedOperationException("This operation is not supported")
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
throw UnsupportedOperationException("This operation is not supported")
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
throw UnsupportedOperationException("This operation is not supported")
}
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
): Int {
throw UnsupportedOperationException("This operation is not supported")
}
private fun search(
query: String,
cancellationSignal: CancellationSignal?
): List<T> {
return runBlocking {
val deferred = async {
search(query)
}
cancellationSignal?.setOnCancelListener {
deferred.cancel()
}
deferred.await()
}
}
internal abstract fun createCursor(capacity: Int): MatrixCursor
internal abstract fun writeToCursor(cursor: MatrixCursor, item: T)
private fun checkPermissionOrThrow(context: Context) {
if (context.checkCallingPermission(PluginContract.Permission) == PackageManager.PERMISSION_GRANTED) {
return
}
throw SecurityException("Caller does not have permission to use plugins")
}
}
@@ -0,0 +1,58 @@
package de.mm20.launcher2.sdk.files
import android.net.Uri
import de.mm20.launcher2.plugin.config.StorageStrategy
data class File(
/**
* A unique and stable identifier for this file.
*/
val id: String,
/**
* The URI to this file. To open this file, an intent with this URI as data and ACTION_VIEW as action
* is used.
*/
val uri: Uri,
/**
* The display name of this file.
*/
val displayName: String,
/**
* The MIME type that is shown to the user and that is used to determine the icon.
*/
val mimeType: String,
/**
* The size of this file in bytes.
*/
val size: Long,
/**
* A path to this file. This is shown to the user purely for informational purposes.
* It is not used to open the file.
*/
val path: String,
/**
* Whether this file is a directory. If set, a folder icon will be shown instead of a file icon.
*/
val isDirectory: Boolean,
/**
* An URI to a thumbnail of this file. This is used to show a preview of the file.
* Supported schemes:
* - content
* - file
* - android.resource
* - http
* - https
*
* 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,
)
@@ -1,17 +1,48 @@
package de.mm20.launcher2.sdk.files
import android.content.ContentProvider
import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
import de.mm20.launcher2.plugin.contracts.FilePluginContract
import de.mm20.launcher2.sdk.base.SearchPluginProvider
abstract class FileProvider: ContentProvider() {
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? {
return null
abstract class FileProvider : SearchPluginProvider<File>() {
abstract override suspend fun search(query: String): List<File>
final override fun getPluginType(): de.mm20.launcher2.plugin.PluginType {
return de.mm20.launcher2.plugin.PluginType.FileSearch
}
override fun createCursor(capacity: Int): MatrixCursor {
return MatrixCursor(
arrayOf(
FilePluginContract.FileColumns.Id,
FilePluginContract.FileColumns.DisplayName,
FilePluginContract.FileColumns.MimeType,
FilePluginContract.FileColumns.Size,
FilePluginContract.FileColumns.Path,
FilePluginContract.FileColumns.ContentUri,
FilePluginContract.FileColumns.ThumbnailUri,
FilePluginContract.FileColumns.IsDirectory,
FilePluginContract.FileColumns.StorageStrategy
),
capacity,
)
}
override fun writeToCursor(cursor: MatrixCursor, item: File) {
cursor.addRow(
arrayOf(
item.id,
item.displayName,
item.mimeType,
item.size,
item.path,
item.uri.toString(),
item.thumbnailUri?.toString(),
if (item.isDirectory) 1 else 0,
item.storageStrategy.name,
)
)
}
}