Plugins: bringup
This commit is contained in:
@@ -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,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user