From cb7f6b669305533b1c8c2a13b08ffdfa835512ce Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Mon, 18 Dec 2023 16:51:48 +0100 Subject: [PATCH] Implement custom permission system for plugins --- .../settings/plugins/PluginSettingsScreen.kt | 48 +++++++++++--- .../plugins/PluginSettingsScreenVM.kt | 24 ++++--- .../settings/plugins/PluginsSettingsScreen.kt | 38 ----------- .../plugins/PluginsSettingsScreenVM.kt | 7 -- .../de/mm20/launcher2/plugin/PluginState.kt | 2 + .../permissions/PermissionsManager.kt | 19 ------ .../plugin/contracts/PluginContract.kt | 2 - data/weather/build.gradle.kts | 1 + .../weather/settings/WeatherSettings.kt | 1 + .../weather/settings/WeatherSettingsData.kt | 27 ++++++++ plugins/sdk/build.gradle.kts | 7 ++ plugins/sdk/src/main/AndroidManifest.xml | 13 ++++ .../launcher2/sdk/base/BasePluginProvider.kt | 13 +++- .../launcher2/sdk/permissions/DataStore.kt | 25 ++++++++ .../permissions/PluginPermissionManager.kt | 32 ++++++++++ .../permissions/RequestPermissionActivity.kt | 64 +++++++++++++++++++ .../layout/activity_request_permission.xml | 33 ++++++++++ plugins/sdk/src/main/res/values/strings.xml | 6 ++ .../java/de/mm20/launcher2/plugins/Module.kt | 2 +- .../mm20/launcher2/plugins/PluginService.kt | 56 +++++----------- 20 files changed, 294 insertions(+), 126 deletions(-) create mode 100644 data/weather/src/main/java/de/mm20/launcher2/weather/settings/WeatherSettings.kt create mode 100644 data/weather/src/main/java/de/mm20/launcher2/weather/settings/WeatherSettingsData.kt create mode 100644 plugins/sdk/src/main/AndroidManifest.xml create mode 100644 plugins/sdk/src/main/java/de/mm20/launcher2/sdk/permissions/DataStore.kt create mode 100644 plugins/sdk/src/main/java/de/mm20/launcher2/sdk/permissions/PluginPermissionManager.kt create mode 100644 plugins/sdk/src/main/java/de/mm20/launcher2/sdk/permissions/RequestPermissionActivity.kt create mode 100644 plugins/sdk/src/main/res/layout/activity_request_permission.xml create mode 100644 plugins/sdk/src/main/res/values/strings.xml diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/plugins/PluginSettingsScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/plugins/PluginSettingsScreen.kt index 666a13db..8028ba29 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/plugins/PluginSettingsScreen.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/plugins/PluginSettingsScreen.kt @@ -1,6 +1,10 @@ package de.mm20.launcher2.ui.settings.plugins +import android.app.Activity import android.app.PendingIntent +import android.content.Intent +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState @@ -18,13 +22,10 @@ import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.automirrored.rounded.InsertDriveFile import androidx.compose.material.icons.rounded.Delete import androidx.compose.material.icons.rounded.Error -import androidx.compose.material.icons.rounded.ErrorOutline -import androidx.compose.material.icons.rounded.FileCopy import androidx.compose.material.icons.rounded.Info +import androidx.compose.material.icons.rounded.LightMode import androidx.compose.material.icons.rounded.Settings import androidx.compose.material.icons.rounded.Verified -import androidx.compose.material.icons.rounded.Warning -import androidx.compose.material.icons.rounded.WarningAmber import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -66,12 +67,25 @@ fun PluginSettingsScreen(pluginId: String) { val icon by viewModel.icon.collectAsStateWithLifecycle(null) val types by viewModel.types.collectAsStateWithLifecycle(emptyList()) + val hasPermission by viewModel.hasPermission.collectAsStateWithLifecycle( + null, + minActiveState = Lifecycle.State.RESUMED + ) val filePlugins by viewModel.filePlugins.collectAsStateWithLifecycle( emptyList(), minActiveState = Lifecycle.State.RESUMED ) - val enabledFileSearchPlugins by viewModel.enabledFileSearchPlugins.collectAsStateWithLifecycle(null) + val requestPermissionStarter = + rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == Activity.RESULT_OK) { + viewModel.setPluginEnabled(true) + } + } + + val enabledFileSearchPlugins by viewModel.enabledFileSearchPlugins.collectAsStateWithLifecycle( + null + ) Scaffold( topBar = { @@ -217,6 +231,7 @@ fun PluginSettingsScreen(pluginId: String) { Icon( when (type) { PluginType.FileSearch -> Icons.AutoMirrored.Rounded.InsertDriveFile + PluginType.Weather -> Icons.Rounded.LightMode }, null, modifier = Modifier.size(16.dp), @@ -225,6 +240,7 @@ fun PluginSettingsScreen(pluginId: String) { Text( when (type) { PluginType.FileSearch -> "File search" + PluginType.Weather -> "Weather provider" }, modifier = Modifier.padding(horizontal = 4.dp), style = MaterialTheme.typography.labelMedium, @@ -249,16 +265,25 @@ fun PluginSettingsScreen(pluginId: String) { color = surfaceColor, ) { SwitchPreference( - enabled = pluginPackage != null, + enabled = pluginPackage != null && hasPermission != null, iconPadding = false, title = "Enable plugin", - value = pluginPackage?.enabled == true, + value = pluginPackage?.enabled == true && hasPermission == true, onValueChanged = { - viewModel.setPluginEnabled(it) + if (hasPermission == true) { + viewModel.setPluginEnabled(it) + } else { + requestPermissionStarter.launch( + Intent().apply { + `package` = pluginPackage?.packageName + action = "de.mm20.launcher2.plugin.REQUEST_PERMISSION" + } + ) + } } ) } - AnimatedVisibility(pluginPackage?.enabled == true) { + AnimatedVisibility(pluginPackage?.enabled == true && hasPermission == true) { if (filePlugins.isNotEmpty()) { PreferenceCategory( "File search", @@ -299,7 +324,10 @@ fun PluginSettingsScreen(pluginId: String) { ?: plugin.plugin.description, value = enabledFileSearchPlugins?.contains(plugin.plugin.authority) == true && state is PluginState.Ready, onValueChanged = { - viewModel.setFileSearchPluginEnabled(plugin.plugin.authority, it) + viewModel.setFileSearchPluginEnabled( + plugin.plugin.authority, + it + ) }, iconPadding = false, ) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/plugins/PluginSettingsScreenVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/plugins/PluginSettingsScreenVM.kt index 984935c1..036c3b44 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/plugins/PluginSettingsScreenVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/plugins/PluginSettingsScreenVM.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -50,17 +51,24 @@ class PluginSettingsScreenVM : ViewModel(), KoinComponent { it?.plugins?.map { it.type }?.distinct() ?: emptyList() } - val filePlugins = pluginPackage + val states = pluginPackage .map { - it?.plugins?.mapNotNull { - if (it.type == PluginType.FileSearch) { - val state = pluginService.getPluginState(it) - PluginWithState(it, state) - } else { - null - } + it?.plugins?.map { + val state = pluginService.getPluginState(it) + PluginWithState(it, state) } ?: emptyList() } + .shareIn(viewModelScope, SharingStarted.WhileSubscribed()) + + val hasPermission = states + .map { + it.none { it.state is PluginState.NoPermission } + } + + val filePlugins = states + .map { + it.filter { it.plugin.type == PluginType.FileSearch } + } fun init(pluginId: String) { diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/plugins/PluginsSettingsScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/plugins/PluginsSettingsScreen.kt index 609e19dc..32d2b863 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/plugins/PluginsSettingsScreen.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/plugins/PluginsSettingsScreen.kt @@ -36,50 +36,12 @@ import de.mm20.launcher2.ui.locals.LocalNavController @Composable fun PluginsSettingsScreen() { val viewModel: PluginsSettingsScreenVM = viewModel() - val navController = LocalNavController.current - val hostInstalled by viewModel.hostInstalled.collectAsState(null) - val hasPermission by viewModel.hasPermission.collectAsState(null) val context = LocalContext.current val pluginPackages by viewModel.pluginPackages.collectAsState(null) val enabledPackages by viewModel.enabledPluginPackages.collectAsState(emptyList()) val disabledPackages by viewModel.disabledPluginPackages.collectAsState(emptyList()) PreferenceScreen(title = stringResource(R.string.preference_screen_plugins)) { when { - hostInstalled == false -> { - item { - Column( - modifier = Modifier - .fillMaxWidth() - .fillParentMaxHeight() - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - LargeMessage( - icon = Icons.Rounded.ExtensionOff, - text = stringResource(R.string.plugin_host_not_installed), - color = MaterialTheme.colorScheme.secondary - ) - } - } - } - - hasPermission == false -> { - item { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - MissingPermissionBanner( - text = stringResource(R.string.missing_permission_plugins), - onClick = { viewModel.requestPermission(context) } - ) - } - } - } - pluginPackages?.isEmpty() == true -> { item { Column( diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/plugins/PluginsSettingsScreenVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/plugins/PluginsSettingsScreenVM.kt index f184c242..c5cd4a25 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/plugins/PluginsSettingsScreenVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/plugins/PluginsSettingsScreenVM.kt @@ -19,10 +19,7 @@ import org.koin.core.component.inject class PluginsSettingsScreenVM : ViewModel(), KoinComponent { private val pluginService: PluginService by inject() - private val permissionsManager: PermissionsManager by inject() - val hostInstalled = pluginService.isPluginHostInstalled() - val hasPermission = permissionsManager.hasPermission(PermissionGroup.Plugins) val pluginPackages = pluginService .getPluginPackages() .shareIn(viewModelScope, SharingStarted.WhileSubscribed(100), 1) @@ -35,10 +32,6 @@ class PluginsSettingsScreenVM : ViewModel(), KoinComponent { it.filter { !it.enabled }.sortedBy { it.label } } - fun requestPermission(context: Context) { - permissionsManager.requestPermission(context as AppCompatActivity, PermissionGroup.Plugins) - } - fun getIcon(plugin: PluginPackage) = flow { emit(pluginService.getPluginPackageIcon(plugin)) } diff --git a/core/base/src/main/java/de/mm20/launcher2/plugin/PluginState.kt b/core/base/src/main/java/de/mm20/launcher2/plugin/PluginState.kt index a4c19d2e..85554831 100644 --- a/core/base/src/main/java/de/mm20/launcher2/plugin/PluginState.kt +++ b/core/base/src/main/java/de/mm20/launcher2/plugin/PluginState.kt @@ -19,6 +19,8 @@ sealed class PluginState { data object Error: PluginState() + data object NoPermission: PluginState() + companion object { fun fromBundle(bundle: Bundle): PluginState? { val type = bundle.getString("type") ?: return null diff --git a/core/permissions/src/main/java/de/mm20/launcher2/permissions/PermissionsManager.kt b/core/permissions/src/main/java/de/mm20/launcher2/permissions/PermissionsManager.kt index cf34b24c..e554e468 100644 --- a/core/permissions/src/main/java/de/mm20/launcher2/permissions/PermissionsManager.kt +++ b/core/permissions/src/main/java/de/mm20/launcher2/permissions/PermissionsManager.kt @@ -64,7 +64,6 @@ enum class PermissionGroup { Notifications, AppShortcuts, Accessibility, - Plugins, } internal class PermissionsManagerImpl( @@ -85,9 +84,6 @@ internal class PermissionsManagerImpl( private val locationPermissionState = MutableStateFlow( checkPermissionOnce(PermissionGroup.Location) ) - private val pluginsPermissionState = MutableStateFlow( - checkPermissionOnce(PermissionGroup.Plugins) - ) private val notificationsPermissionState = MutableStateFlow(false) private val accessibilityPermissionState = MutableStateFlow(false) private val appShortcutsPermissionState = MutableStateFlow( @@ -158,14 +154,6 @@ internal class PermissionsManagerImpl( CrashReporter.logException(e) } } - - PermissionGroup.Plugins -> { - ActivityCompat.requestPermissions( - context, - pluginPermissions, - permissionGroup.ordinal - ) - } } } @@ -183,10 +171,6 @@ internal class PermissionsManagerImpl( contactPermissions.all { context.checkPermission(it) } } - PermissionGroup.Plugins -> { - pluginPermissions.all { context.checkPermission(it) } - } - PermissionGroup.ExternalStorage -> { if (isAtLeastApiLevel(Build.VERSION_CODES.R)) { Environment.isExternalStorageManager() @@ -218,7 +202,6 @@ internal class PermissionsManagerImpl( PermissionGroup.Notifications -> notificationsPermissionState PermissionGroup.AppShortcuts -> appShortcutsPermissionState PermissionGroup.Accessibility -> accessibilityPermissionState - PermissionGroup.Plugins -> pluginsPermissionState } } @@ -237,7 +220,6 @@ internal class PermissionsManagerImpl( PermissionGroup.Notifications -> notificationsPermissionState.value = granted PermissionGroup.AppShortcuts -> appShortcutsPermissionState.value = granted PermissionGroup.Accessibility -> accessibilityPermissionState.value = granted - PermissionGroup.Plugins -> pluginsPermissionState.value = granted } } @@ -280,6 +262,5 @@ internal class PermissionsManagerImpl( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ) - private val pluginPermissions = arrayOf(PluginContract.Permission) } } 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 ccbeb2bf..6464ee8e 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 @@ -1,8 +1,6 @@ package de.mm20.launcher2.plugin.contracts object PluginContract { - - const val Permission = "de.mm20.launcher2.permission.USE_PLUGINS" object Methods { const val GetType = "getType" const val GetState = "getState" diff --git a/data/weather/build.gradle.kts b/data/weather/build.gradle.kts index 2dc5348c..ea4df376 100644 --- a/data/weather/build.gradle.kts +++ b/data/weather/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.plugin.serialization) } android { diff --git a/data/weather/src/main/java/de/mm20/launcher2/weather/settings/WeatherSettings.kt b/data/weather/src/main/java/de/mm20/launcher2/weather/settings/WeatherSettings.kt new file mode 100644 index 00000000..515ea095 --- /dev/null +++ b/data/weather/src/main/java/de/mm20/launcher2/weather/settings/WeatherSettings.kt @@ -0,0 +1 @@ +package de.mm20.launcher2.weather.settings diff --git a/data/weather/src/main/java/de/mm20/launcher2/weather/settings/WeatherSettingsData.kt b/data/weather/src/main/java/de/mm20/launcher2/weather/settings/WeatherSettingsData.kt new file mode 100644 index 00000000..124272cd --- /dev/null +++ b/data/weather/src/main/java/de/mm20/launcher2/weather/settings/WeatherSettingsData.kt @@ -0,0 +1,27 @@ +package de.mm20.launcher2.weather.settings + +import kotlinx.serialization.Serializable + + +@Serializable +data class LatLon( + val lat: Double, + val lon: Double, +) + +@Serializable +data class ProviderSettings( + val lastUpdate: Long = 0, + val locationId: String? = null, + val locationName: String? = null, +) + +@Serializable +data class WeatherSettingsData( + val provider: String = "metno", + val autoLocation: Boolean = true, + val location: LatLon? = null, + val locationName: String? = null, + val lastLocation: LatLon? = null, + val providerSettings: Map +) \ No newline at end of file diff --git a/plugins/sdk/build.gradle.kts b/plugins/sdk/build.gradle.kts index 03fd16b3..fb7f122d 100644 --- a/plugins/sdk/build.gradle.kts +++ b/plugins/sdk/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.plugin.serialization) `maven-publish` } @@ -14,6 +15,10 @@ android { consumerProguardFiles("consumer-rules.pro") } + buildFeatures { + viewBinding = true + } + buildTypes { release { proguardFiles( @@ -46,6 +51,8 @@ android { dependencies { api(project(":core:shared")) + implementation(libs.androidx.datastore) + implementation(libs.kotlinx.serialization.json) implementation(libs.bundles.kotlin) } diff --git a/plugins/sdk/src/main/AndroidManifest.xml b/plugins/sdk/src/main/AndroidManifest.xml new file mode 100644 index 00000000..957f4a7e --- /dev/null +++ b/plugins/sdk/src/main/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file 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 b8398e9d..a91a0950 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 @@ -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") diff --git a/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/permissions/DataStore.kt b/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/permissions/DataStore.kt new file mode 100644 index 00000000..a6c3533b --- /dev/null +++ b/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/permissions/DataStore.kt @@ -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> { + override val defaultValue: Set + get() = emptySet() + + override suspend fun readFrom(input: InputStream): Set { + return input.bufferedReader().readLines().toSet() + } + + override suspend fun writeTo(t: Set, output: OutputStream) { + output.bufferedWriter().write(t.joinToString("\n")) + } +} \ No newline at end of file diff --git a/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/permissions/PluginPermissionManager.kt b/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/permissions/PluginPermissionManager.kt new file mode 100644 index 00000000..845a0880 --- /dev/null +++ b/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/permissions/PluginPermissionManager.kt @@ -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 { + 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 + } + } + } +} \ No newline at end of file diff --git a/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/permissions/RequestPermissionActivity.kt b/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/permissions/RequestPermissionActivity.kt new file mode 100644 index 00000000..9a1578e5 --- /dev/null +++ b/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/permissions/RequestPermissionActivity.kt @@ -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() + } + } +} \ No newline at end of file diff --git a/plugins/sdk/src/main/res/layout/activity_request_permission.xml b/plugins/sdk/src/main/res/layout/activity_request_permission.xml new file mode 100644 index 00000000..74d7e934 --- /dev/null +++ b/plugins/sdk/src/main/res/layout/activity_request_permission.xml @@ -0,0 +1,33 @@ + + + + + + +