Refactor permissions manager

This commit is contained in:
MM20 2022-01-03 21:41:44 +01:00
parent 5c0d142fd9
commit b5005ee4ea
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
15 changed files with 183 additions and 84 deletions

View File

@ -21,6 +21,7 @@ import de.mm20.launcher2.websites.websitesModule
import de.mm20.launcher2.widgets.widgetsModule
import de.mm20.launcher2.wikipedia.wikipediaModule
import de.mm20.launcher2.database.databaseModule
import de.mm20.launcher2.permissions.permissionsModule
import de.mm20.launcher2.preferences.preferencesModule
import de.mm20.launcher2.weather.weatherModule
import kotlinx.coroutines.*
@ -67,6 +68,7 @@ class LauncherApplication : Application(), CoroutineScope {
hiddenItemsModule,
iconsModule,
musicModule,
permissionsModule,
preferencesModule,
searchModule,
unitConverterModule,
@ -86,10 +88,4 @@ class LauncherApplication : Application(), CoroutineScope {
}
}
}
object PermissionRequests {
const val CALENDAR = 309
const val LOCATION = 410
const val ALL = 666
}
}

View File

@ -20,8 +20,11 @@ import de.mm20.launcher2.graphics.TextDrawable
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.checkPermission
import de.mm20.launcher2.ktx.dp
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherPreferences
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import java.text.SimpleDateFormat
import java.util.*
@ -76,7 +79,7 @@ class CalendarEvent(
return null
}
companion object {
companion object: KoinComponent {
fun search(
context: Context,
query: String,
@ -87,10 +90,11 @@ class CalendarEvent(
unselectedCalendars: List<Long> = emptyList(),
hiddenEvents: List<Long> = emptyList()
): List<CalendarEvent> {
val permissionsManager: PermissionsManager = get()
val results = mutableListOf<CalendarEvent>()
if (!query.isEmpty() && query.length < 3) return results
if (!LauncherPreferences.instance.searchCalendars) return listOf()
if (!PermissionsManager.checkPermission(context, PermissionsManager.CALENDAR)) {
if (!permissionsManager.checkPermission(PermissionGroup.Calendar)) {
return emptyList()
}
val builder = CalendarContract.Instances.CONTENT_URI.buildUpon()

View File

@ -14,10 +14,13 @@ import de.mm20.launcher2.graphics.TextDrawable
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.asBitmap
import de.mm20.launcher2.ktx.sp
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherPreferences
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
class Contact(
val id: Long,
@ -71,13 +74,14 @@ class Contact(
return null
}
companion object {
companion object: KoinComponent {
fun search(context: Context, query: String): List<Contact> {
if (query.length < 3) return mutableListOf()
if (!LauncherPreferences.instance.searchContacts) {
return mutableListOf()
}
if (!PermissionsManager.checkPermission(context, PermissionsManager.CONTACTS)) {
val permissionsManager: PermissionsManager = get()
if (!permissionsManager.checkPermission(PermissionGroup.Contacts)) {
return mutableListOf()
}
val proj = arrayOf(

View File

@ -4,11 +4,14 @@ import android.content.Context
import android.provider.MediaStore
import androidx.core.database.getStringOrNull
import de.mm20.launcher2.ktx.jsonObjectOf
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.search.SearchableDeserializer
import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.data.*
import org.json.JSONObject
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
class LocalFileSerializer : SearchableSerializer {
override fun serialize(searchable: Searchable): String {
@ -24,11 +27,11 @@ class LocalFileSerializer : SearchableSerializer {
class LocalFileDeserializer(
val context: Context
) : SearchableDeserializer {
) : SearchableDeserializer, KoinComponent {
override fun deserialize(serialized: String): Searchable? {
if (!PermissionsManager.checkPermission(
context,
PermissionsManager.EXTERNAL_STORAGE
val permissionsManager: PermissionsManager = get()
if (!permissionsManager.checkPermission(
PermissionGroup.ExternalStorage
)
) return null
val json = JSONObject(serialized)

View File

@ -21,10 +21,13 @@ import de.mm20.launcher2.files.R
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.formatToString
import de.mm20.launcher2.media.ThumbnailUtilsCompat
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherPreferences
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import java.io.IOException
import java.util.*
import java.io.File as JavaIOFile
@ -161,14 +164,14 @@ open class LocalFile(
companion object {
companion object: KoinComponent {
fun search(context: Context, query: String): List<LocalFile> {
val results = mutableListOf<LocalFile>()
if (!LauncherPreferences.instance.searchFiles) return results
if (query.isBlank()) return results
if (!PermissionsManager.checkPermission(
context,
PermissionsManager.EXTERNAL_STORAGE
val permissionsManager: PermissionsManager = get()
if (!permissionsManager.checkPermission(
PermissionGroup.ExternalStorage
)
) return results
val uri = MediaStore.Files.getContentUri("external").buildUpon()

View File

@ -39,6 +39,8 @@ dependencies {
implementation(libs.androidx.core)
implementation(libs.androidx.appcompat)
implementation(libs.koin.android)
implementation(project(":ktx"))
implementation(project(":base"))
implementation(project(":icons"))

View File

@ -4,11 +4,12 @@ import android.content.Context
import android.graphics.drawable.ColorDrawable
import androidx.core.content.ContextCompat
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.R
class MissingPermission(override val label: String, val permissionGroup: Int): Searchable() {
class MissingPermission(override val label: String, val permissionGroup: PermissionGroup): Searchable() {
override val key: String
get() = "permission://${permissionGroup}"
get() = "permission://${permissionGroup.ordinal}"
override fun getPlaceholderIcon(context: Context): LauncherIcon {
return LauncherIcon(

View File

@ -0,0 +1,8 @@
package de.mm20.launcher2.permissions
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
val permissionsModule = module {
single<PermissionsManager> { PermissionsManagerImpl(androidContext()) }
}

View File

@ -1,55 +1,75 @@
package de.mm20.launcher2.permissions
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.Settings
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import de.mm20.launcher2.ktx.checkPermission
import de.mm20.launcher2.ktx.isAtLeastApiLevel
object PermissionsManager {
interface PermissionsManager {
fun requestPermission(context: AppCompatActivity, permissionGroup: PermissionGroup)
fun requestPermission(context: Activity, permissionGroup: Int) {
fun checkPermission(permissionGroup: PermissionGroup): Boolean
}
enum class PermissionGroup {
Calendar,
Location,
Contacts,
ExternalStorage
}
class PermissionsManagerImpl(
private val context: Context
): PermissionsManager {
override fun requestPermission(activity: AppCompatActivity, permissionGroup: PermissionGroup) {
when (permissionGroup) {
CALENDAR -> {
ActivityCompat.requestPermissions(context, calendarPermissions, permissionGroup)
PermissionGroup.Calendar -> {
ActivityCompat.requestPermissions(activity, calendarPermissions, permissionGroup.ordinal)
}
LOCATION -> {
ActivityCompat.requestPermissions(context, locationPermissions, permissionGroup)
PermissionGroup.Location -> {
ActivityCompat.requestPermissions(activity, locationPermissions, permissionGroup.ordinal)
}
CONTACTS -> {
ActivityCompat.requestPermissions(context, contactPermissions, permissionGroup)
PermissionGroup.Contacts -> {
ActivityCompat.requestPermissions(activity, contactPermissions, permissionGroup.ordinal)
}
EXTERNAL_STORAGE -> {
PermissionGroup.ExternalStorage -> {
if (isAtLeastApiLevel(Build.VERSION_CODES.R)) {
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).also {
it.data = Uri.parse("package:${context.packageName}")
}
context.startActivity(intent)
val intent =
Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).also {
it.data = Uri.parse("package:${activity.packageName}")
}
activity.startActivity(intent)
} else {
ActivityCompat.requestPermissions(context, externalStoragePermissions, permissionGroup)
ActivityCompat.requestPermissions(
activity,
externalStoragePermissions,
permissionGroup.ordinal
)
}
}
}
}
fun checkPermission(context: Context, permissionGroup: Int): Boolean {
override fun checkPermission(permissionGroup: PermissionGroup): Boolean {
return when (permissionGroup) {
CALENDAR -> {
PermissionGroup.Calendar -> {
calendarPermissions.all { context.checkPermission(it) }
}
LOCATION -> {
PermissionGroup.Location -> {
locationPermissions.all { context.checkPermission(it) }
}
CONTACTS -> {
PermissionGroup.Contacts -> {
contactPermissions.all { context.checkPermission(it) }
}
EXTERNAL_STORAGE -> {
PermissionGroup.ExternalStorage -> {
if (isAtLeastApiLevel(Build.VERSION_CODES.R)) {
Environment.isExternalStorageManager()
} else {
@ -60,15 +80,14 @@ object PermissionsManager {
}
}
const val CALENDAR = 0x1
private val calendarPermissions = arrayOf(Manifest.permission.READ_CALENDAR)
const val LOCATION = 0x2
private val locationPermissions = arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION)
const val CONTACTS = 0x4
private val locationPermissions = arrayOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
)
private val contactPermissions = arrayOf(Manifest.permission.READ_CONTACTS)
const val EXTERNAL_STORAGE = 0x8
private val externalStoragePermissions = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)
const val NOTIFICATIONS = 0x10
const val ALL = 0x12
private val externalStoragePermissions = arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
}

View File

@ -0,0 +1,63 @@
package de.mm20.launcher2.ui.component
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Lock
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun MissingPermissionBanner(
modifier: Modifier = Modifier,
text: String,
onClick: () -> Unit,
secondaryAction: @Composable () -> Unit = {}
) {
Surface(
modifier = modifier,
color = MaterialTheme.colorScheme.primaryContainer,
shape = RoundedCornerShape(8.dp)
) {
Column {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
modifier = Modifier.padding(16.dp),
imageVector = Icons.Rounded.Lock,
contentDescription = null
)
Text(
text = text,
modifier = Modifier
.weight(1f)
.padding(vertical = 16.dp)
.padding(end = 16.dp),
style = MaterialTheme.typography.bodyMedium
)
}
Row(
Modifier
.align(Alignment.End)
.padding(8.dp)
) {
secondaryAction()
TextButton(
modifier = Modifier.padding(start = 8.dp),
onClick = onClick) {
Text("Grant", style = MaterialTheme.typography.labelLarge)
}
}
}
}
}

View File

@ -213,14 +213,6 @@ class LauncherActivity : AppCompatActivity() {
ThemeHelper.applyTheme(theme)
if (LauncherPreferences.instance.firstRunVersion < 1) {
ActivityCompat.requestPermissions(
this, arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
), PermissionsManager.ALL
)
LauncherPreferences.instance.firstRunVersion = 1
}
@ -646,15 +638,6 @@ class LauncherActivity : AppCompatActivity() {
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
search(binding.searchBar.getSearchQuery())
when (requestCode) {
PermissionsManager.LOCATION -> {
ViewModelProvider(this).get(WeatherViewModel::class.java).requestUpdate(this)
}
PermissionsManager.ALL -> {
ViewModelProvider(this).get(WeatherViewModel::class.java).requestUpdate(this)
search(binding.searchBar.getSearchQuery())
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {

View File

@ -9,6 +9,7 @@ import android.widget.FrameLayout
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.LiveData
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherPreferences
import de.mm20.launcher2.search.data.CalendarEvent
@ -16,8 +17,10 @@ import de.mm20.launcher2.search.data.MissingPermission
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
import de.mm20.launcher2.ui.legacy.search.SearchListView
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
class CalendarView : FrameLayout {
class CalendarView : FrameLayout, KoinComponent {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
@ -30,6 +33,7 @@ class CalendarView : FrameLayout {
private val calendarEvents: LiveData<List<CalendarEvent>?>
init {
val permissionsManager: PermissionsManager = get()
View.inflate(context, R.layout.view_search_category_list, this)
layoutTransition = LayoutTransition()
layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
@ -43,9 +47,8 @@ class CalendarView : FrameLayout {
visibility = View.GONE
return@observe
}
if (it.isEmpty() && LauncherPreferences.instance.searchCalendars && !PermissionsManager.checkPermission(
context,
PermissionsManager.CALENDAR
if (it.isEmpty() && LauncherPreferences.instance.searchCalendars && !permissionsManager.checkPermission(
PermissionGroup.Calendar
)
) {
visibility = View.VISIBLE
@ -53,7 +56,7 @@ class CalendarView : FrameLayout {
listOf(
MissingPermission(
context.getString(R.string.permission_calendar_search),
PermissionsManager.CALENDAR
PermissionGroup.Calendar
)
)
)

View File

@ -10,6 +10,7 @@ import android.widget.SearchView
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.LiveData
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherPreferences
import de.mm20.launcher2.search.data.Contact
@ -17,8 +18,10 @@ import de.mm20.launcher2.search.data.MissingPermission
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
import de.mm20.launcher2.ui.legacy.search.SearchListView
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
class ContactView : FrameLayout {
class ContactView : FrameLayout, KoinComponent {
private val contacts: LiveData<List<Contact>?>
constructor(context: Context) : super(context)
@ -30,6 +33,7 @@ class ContactView : FrameLayout {
)
init {
val permissionsManager: PermissionsManager = get()
View.inflate(context, R.layout.view_search_category_list, this)
layoutTransition = LayoutTransition()
layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
@ -43,9 +47,8 @@ class ContactView : FrameLayout {
visibility = View.GONE
return@observe
}
if (it.isEmpty() && LauncherPreferences.instance.searchContacts && !PermissionsManager.checkPermission(
context,
PermissionsManager.CONTACTS
if (it.isEmpty() && LauncherPreferences.instance.searchContacts && !permissionsManager.checkPermission(
PermissionGroup.Contacts
)
) {
visibility = View.VISIBLE
@ -53,7 +56,7 @@ class ContactView : FrameLayout {
listOf(
MissingPermission(
context.getString(R.string.permission_contact_search),
PermissionsManager.CONTACTS
PermissionGroup.Contacts
)
)
)

View File

@ -9,14 +9,17 @@ import android.widget.FrameLayout
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.LiveData
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.search.data.File
import de.mm20.launcher2.search.data.MissingPermission
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
import de.mm20.launcher2.ui.legacy.search.SearchListView
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
class FileView : FrameLayout {
class FileView : FrameLayout, KoinComponent {
private val files: LiveData<List<File>?>
constructor(context: Context) : super(context)
@ -28,6 +31,7 @@ class FileView : FrameLayout {
)
init {
val permissionsManager: PermissionsManager = get()
View.inflate(context, R.layout.view_search_category_list, this)
layoutTransition = LayoutTransition()
layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
@ -41,9 +45,8 @@ class FileView : FrameLayout {
visibility = View.GONE
return@observe
}
if (it.isEmpty() && !PermissionsManager.checkPermission(
context,
PermissionsManager.EXTERNAL_STORAGE
if (it.isEmpty() && !permissionsManager.checkPermission(
PermissionGroup.ExternalStorage
)
) {
visibility = View.VISIBLE
@ -51,7 +54,7 @@ class FileView : FrameLayout {
listOf(
MissingPermission(
context.getString(R.string.permission_files_search),
PermissionsManager.EXTERNAL_STORAGE
PermissionGroup.ExternalStorage
)
)
)

View File

@ -2,6 +2,7 @@ package de.mm20.launcher2.ui.legacy.search
import android.app.Activity
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.transition.Scene
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.search.data.MissingPermission
@ -10,17 +11,20 @@ import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
import de.mm20.launcher2.ui.legacy.view.InnerCardView
import de.mm20.launcher2.ui.legacy.view.LauncherIconView
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
class PermissionListRepresentation : Representation {
class PermissionListRepresentation : Representation, KoinComponent {
override fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?): Scene {
val missingPermission = searchable as MissingPermission
val context = rootView.context
val scene = Scene.getSceneForLayout(rootView, R.layout.view_permission_list, rootView.context)
scene.setEnterAction {
val permissionsManager: PermissionsManager = get()
rootView.findViewById<TextView>(R.id.permissionText).text = missingPermission.label
rootView.findViewById<LauncherIconView>(R.id.permissionIcon).icon = missingPermission.getPlaceholderIcon(context)
rootView.findViewById<InnerCardView>(R.id.card).setOnClickListener {
PermissionsManager.requestPermission(context as Activity, missingPermission.permissionGroup)
permissionsManager.requestPermission(context as AppCompatActivity, missingPermission.permissionGroup)
}
}
return scene