Implement custom permission system for plugins
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application>
|
||||
<activity
|
||||
android:name=".permissions.RequestPermissionActivity"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.DeviceDefault.Dialog.NoActionBar.MinWidth">
|
||||
<intent-filter>
|
||||
<action android:name="de.mm20.launcher2.plugin.REQUEST_PERMISSION" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -8,17 +8,22 @@ import android.os.Bundle
|
||||
import de.mm20.launcher2.plugin.PluginType
|
||||
import de.mm20.launcher2.plugin.contracts.PluginContract
|
||||
import de.mm20.launcher2.sdk.PluginState
|
||||
import de.mm20.launcher2.sdk.permissions.PluginPermissionManager
|
||||
import de.mm20.launcher2.sdk.permissions.permissionsDataStore
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
abstract class BasePluginProvider : ContentProvider() {
|
||||
|
||||
override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
|
||||
val context = context ?: return null
|
||||
return when (method) {
|
||||
PluginContract.Methods.GetType -> Bundle().apply {
|
||||
putString("type", getPluginType().name)
|
||||
}
|
||||
|
||||
PluginContract.Methods.GetState -> {
|
||||
checkPermissionOrThrow(context)
|
||||
val state = runBlocking {
|
||||
getPluginState()
|
||||
}
|
||||
@@ -27,6 +32,7 @@ abstract class BasePluginProvider : ContentProvider() {
|
||||
}
|
||||
|
||||
PluginContract.Methods.GetConfig -> {
|
||||
checkPermissionOrThrow(context)
|
||||
getPluginConfig()
|
||||
}
|
||||
|
||||
@@ -45,7 +51,12 @@ abstract class BasePluginProvider : ContentProvider() {
|
||||
}
|
||||
|
||||
internal fun checkPermissionOrThrow(context: Context) {
|
||||
if (context.checkCallingPermission(PluginContract.Permission) == PackageManager.PERMISSION_GRANTED) {
|
||||
val callingPackage = callingPackage ?: throw IllegalArgumentException("No calling package")
|
||||
val permissionManager = PluginPermissionManager(context)
|
||||
val hasPermission = runBlocking {
|
||||
permissionManager.hasPermission(callingPackage).first()
|
||||
}
|
||||
if (hasPermission) {
|
||||
return
|
||||
}
|
||||
throw SecurityException("Caller does not have permission to use plugins")
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package de.mm20.launcher2.sdk.permissions
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.Serializer
|
||||
import androidx.datastore.dataStore
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
internal val Context.permissionsDataStore by dataStore(
|
||||
fileName = "plugin_permissions",
|
||||
serializer = PermissionsSerializer,
|
||||
)
|
||||
|
||||
internal object PermissionsSerializer : Serializer<Set<String>> {
|
||||
override val defaultValue: Set<String>
|
||||
get() = emptySet()
|
||||
|
||||
override suspend fun readFrom(input: InputStream): Set<String> {
|
||||
return input.bufferedReader().readLines().toSet()
|
||||
}
|
||||
|
||||
override suspend fun writeTo(t: Set<String>, output: OutputStream) {
|
||||
output.bufferedWriter().write(t.joinToString("\n"))
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package de.mm20.launcher2.sdk.permissions
|
||||
|
||||
import android.content.Context
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class PluginPermissionManager(
|
||||
context: Context,
|
||||
) {
|
||||
private val dataStore = context.applicationContext.permissionsDataStore
|
||||
|
||||
fun hasPermission(pluginPackage: String): Flow<Boolean> {
|
||||
return dataStore.data.map { it.contains(pluginPackage) }
|
||||
}
|
||||
|
||||
fun grantPermission(pluginPackage: String) {
|
||||
runBlocking {
|
||||
dataStore.updateData {
|
||||
it + pluginPackage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun revokePermission(pluginPackage: String) {
|
||||
runBlocking {
|
||||
dataStore.updateData {
|
||||
it - pluginPackage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package de.mm20.launcher2.sdk.permissions
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.view.LayoutInflater
|
||||
import de.mm20.launcher2.sdk.R
|
||||
import de.mm20.launcher2.sdk.databinding.ActivityRequestPermissionBinding
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class RequestPermissionActivity: Activity() {
|
||||
|
||||
private lateinit var binding: ActivityRequestPermissionBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val callingPackage = callingPackage ?: throw IllegalArgumentException("No calling package")
|
||||
|
||||
val callingPackageInfo = try {
|
||||
packageManager.getApplicationInfo(callingPackage, 0)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
throw IllegalArgumentException("Invalid calling package")
|
||||
}
|
||||
|
||||
val myPackageInfo = try {
|
||||
packageManager.getApplicationInfo(packageName, 0)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
throw IllegalStateException("Invalid package")
|
||||
}
|
||||
|
||||
|
||||
val permissionManager = PluginPermissionManager(this)
|
||||
|
||||
val hasPermission = runBlocking {
|
||||
permissionManager.hasPermission(callingPackage).first()
|
||||
}
|
||||
|
||||
if (hasPermission) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
binding = ActivityRequestPermissionBinding.inflate(LayoutInflater.from(this))
|
||||
val text = getString(
|
||||
R.string.request_permission_message,
|
||||
callingPackageInfo.loadLabel(packageManager),
|
||||
myPackageInfo.loadLabel(packageManager)
|
||||
)
|
||||
binding.textView.text = Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY)
|
||||
setContentView(binding.root)
|
||||
binding.grantButton.setOnClickListener {
|
||||
permissionManager.grantPermission(callingPackage)
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
binding.denyButton.setOnClickListener {
|
||||
permissionManager.revokePermission(callingPackage)
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@android:style/TextAppearance.DeviceDefault"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:gravity="end">
|
||||
|
||||
<Button
|
||||
android:id="@+id/deny_button"
|
||||
style="@android:style/Widget.DeviceDefault.Button.Borderless"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/request_permission_deny_button" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/grant_button"
|
||||
style="@android:style/Widget.DeviceDefault.Button.Borderless"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/request_permission_allow_button" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="request_permission_message"><![CDATA[<b>%1$s</b> wants to access data from <b>%2$s</b>.]]></string>
|
||||
<string name="request_permission_deny_button">Deny</string>
|
||||
<string name="request_permission_allow_button">Allow</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user