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
+55 -5
View File
@@ -1,6 +1,7 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
`maven-publish`
}
android {
@@ -8,7 +9,7 @@ android {
defaultConfig {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
@@ -16,8 +17,8 @@ android {
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
create("nightly") {
@@ -34,9 +35,58 @@ android {
kotlinOptions {
jvmTarget = "1.8"
}
namespace = "de.mm20.launcher2.shared"
namespace = "de.mm20.launcher2.sdk"
publishing {
singleVariant("release") {
withSourcesJar()
}
}
}
dependencies {
implementation(project(":core:shared"))
api(project(":core:shared"))
implementation(libs.bundles.kotlin)
}
publishing {
publications {
register<MavenPublication>("release") {
groupId = "de.mm20.launcher2"
artifactId = "plugin-sdk"
version = "1.0.0-SNAPSHOT"
pom {
name = "Kvaesitso shared library"
description = "Contains shared code between the launcher and its plugin SDK"
licenses {
license {
name = "The Apache License, Version 2.0"
url = "http://www.apache.org/licenses/LICENSE-2.0.txt"
}
}
developers {
developer {
id = "MM2-0"
name = "U.N.Owen"
}
}
}
afterEvaluate {
from(components["release"])
}
}
}
repositories {
mavenLocal()
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/MM2-0/Kvaesitso")
credentials {
username = project.findProperty("gpr.user") as String? ?: System.getenv("USERNAME")
password = project.findProperty("gpr.key") as String? ?: System.getenv("TOKEN")
}
}
}
}
@@ -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,
)
)
}
}