diff --git a/calendar/src/main/java/de/mm20/launcher2/search/data/CalendarEvent.kt b/calendar/src/main/java/de/mm20/launcher2/search/data/CalendarEvent.kt index 4e3f7d7e..72083ca5 100644 --- a/calendar/src/main/java/de/mm20/launcher2/search/data/CalendarEvent.kt +++ b/calendar/src/main/java/de/mm20/launcher2/search/data/CalendarEvent.kt @@ -94,7 +94,7 @@ class CalendarEvent( val results = mutableListOf() if (!query.isEmpty() && query.length < 3) return results if (!LauncherPreferences.instance.searchCalendars) return listOf() - if (!permissionsManager.checkPermission(PermissionGroup.Calendar)) { + if (!permissionsManager.checkPermissionOnce(PermissionGroup.Calendar)) { return emptyList() } val builder = CalendarContract.Instances.CONTENT_URI.buildUpon() diff --git a/contacts/src/main/java/de/mm20/launcher2/search/data/Contact.kt b/contacts/src/main/java/de/mm20/launcher2/search/data/Contact.kt index ef9e915a..991ddf71 100644 --- a/contacts/src/main/java/de/mm20/launcher2/search/data/Contact.kt +++ b/contacts/src/main/java/de/mm20/launcher2/search/data/Contact.kt @@ -81,7 +81,7 @@ class Contact( return mutableListOf() } val permissionsManager: PermissionsManager = get() - if (!permissionsManager.checkPermission(PermissionGroup.Contacts)) { + if (!permissionsManager.checkPermissionOnce(PermissionGroup.Contacts)) { return mutableListOf() } val proj = arrayOf( diff --git a/files/src/main/java/de/mm20/launcher2/files/FileSerialization.kt b/files/src/main/java/de/mm20/launcher2/files/FileSerialization.kt index c09d6bfd..cde0af33 100644 --- a/files/src/main/java/de/mm20/launcher2/files/FileSerialization.kt +++ b/files/src/main/java/de/mm20/launcher2/files/FileSerialization.kt @@ -30,7 +30,7 @@ class LocalFileDeserializer( ) : SearchableDeserializer, KoinComponent { override fun deserialize(serialized: String): Searchable? { val permissionsManager: PermissionsManager = get() - if (!permissionsManager.checkPermission( + if (!permissionsManager.checkPermissionOnce( PermissionGroup.ExternalStorage ) ) return null diff --git a/files/src/main/java/de/mm20/launcher2/search/data/LocalFile.kt b/files/src/main/java/de/mm20/launcher2/search/data/LocalFile.kt index 357d0722..4544fb60 100644 --- a/files/src/main/java/de/mm20/launcher2/search/data/LocalFile.kt +++ b/files/src/main/java/de/mm20/launcher2/search/data/LocalFile.kt @@ -5,7 +5,6 @@ import android.content.Intent import android.graphics.BitmapFactory import android.graphics.drawable.AdaptiveIconDrawable import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.ColorDrawable import android.location.Geocoder import android.media.MediaMetadataRetriever import android.media.ThumbnailUtils @@ -13,7 +12,6 @@ import android.os.Build import android.provider.MediaStore import android.text.format.DateUtils import android.util.Size -import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.core.database.getStringOrNull import androidx.exifinterface.media.ExifInterface @@ -170,7 +168,7 @@ open class LocalFile( if (!LauncherPreferences.instance.searchFiles) return results if (query.isBlank()) return results val permissionsManager: PermissionsManager = get() - if (!permissionsManager.checkPermission( + if (!permissionsManager.checkPermissionOnce( PermissionGroup.ExternalStorage ) ) return results diff --git a/permissions/build.gradle.kts b/permissions/build.gradle.kts index e1697b17..cb67f6f6 100644 --- a/permissions/build.gradle.kts +++ b/permissions/build.gradle.kts @@ -35,7 +35,7 @@ android { } dependencies { - implementation(libs.kotlin.stdlib) + implementation(libs.bundles.kotlin) implementation(libs.androidx.core) implementation(libs.androidx.appcompat) diff --git a/permissions/src/main/java/de/mm20/launcher2/permissions/PermissionsManager.kt b/permissions/src/main/java/de/mm20/launcher2/permissions/PermissionsManager.kt index 26e29888..11ee6487 100644 --- a/permissions/src/main/java/de/mm20/launcher2/permissions/PermissionsManager.kt +++ b/permissions/src/main/java/de/mm20/launcher2/permissions/PermissionsManager.kt @@ -3,6 +3,7 @@ package de.mm20.launcher2.permissions import android.Manifest import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.Environment @@ -11,11 +12,26 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import de.mm20.launcher2.ktx.checkPermission import de.mm20.launcher2.ktx.isAtLeastApiLevel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow interface PermissionsManager { fun requestPermission(context: AppCompatActivity, permissionGroup: PermissionGroup) - fun checkPermission(permissionGroup: PermissionGroup): Boolean + /** + * Check if this permission is granted right now without receiving further updates + * about the granted state. + * @return true if the given permission group is fully granted + */ + fun checkPermissionOnce(permissionGroup: PermissionGroup): Boolean + + fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) + + fun hasPermission(permissionGroup: PermissionGroup): Flow } enum class PermissionGroup { @@ -27,18 +43,43 @@ enum class PermissionGroup { class PermissionsManagerImpl( private val context: Context -): PermissionsManager { +) : PermissionsManager { + + private val calendarPermissionState = MutableStateFlow( + checkPermissionOnce(PermissionGroup.Calendar) + ) + private val contactsPermissionState = MutableStateFlow( + checkPermissionOnce(PermissionGroup.Contacts) + ) + private val externalStoragePermissionState = MutableStateFlow( + checkPermissionOnce(PermissionGroup.ExternalStorage) + ) + private val locationPermissionState = MutableStateFlow( + checkPermissionOnce(PermissionGroup.Location) + ) override fun requestPermission(activity: AppCompatActivity, permissionGroup: PermissionGroup) { when (permissionGroup) { PermissionGroup.Calendar -> { - ActivityCompat.requestPermissions(activity, calendarPermissions, permissionGroup.ordinal) + ActivityCompat.requestPermissions( + activity, + calendarPermissions, + permissionGroup.ordinal + ) } PermissionGroup.Location -> { - ActivityCompat.requestPermissions(activity, locationPermissions, permissionGroup.ordinal) + ActivityCompat.requestPermissions( + activity, + locationPermissions, + permissionGroup.ordinal + ) } PermissionGroup.Contacts -> { - ActivityCompat.requestPermissions(activity, contactPermissions, permissionGroup.ordinal) + ActivityCompat.requestPermissions( + activity, + contactPermissions, + permissionGroup.ordinal + ) } PermissionGroup.ExternalStorage -> { if (isAtLeastApiLevel(Build.VERSION_CODES.R)) { @@ -58,7 +99,7 @@ class PermissionsManagerImpl( } } - override fun checkPermission(permissionGroup: PermissionGroup): Boolean { + override fun checkPermissionOnce(permissionGroup: PermissionGroup): Boolean { return when (permissionGroup) { PermissionGroup.Calendar -> { calendarPermissions.all { context.checkPermission(it) } @@ -76,7 +117,30 @@ class PermissionsManagerImpl( externalStoragePermissions.all { context.checkPermission(it) } } } - else -> false + } + } + + override fun hasPermission(permissionGroup: PermissionGroup): Flow { + return when (permissionGroup) { + PermissionGroup.Calendar -> calendarPermissionState + PermissionGroup.Location -> locationPermissionState + PermissionGroup.Contacts -> contactsPermissionState + PermissionGroup.ExternalStorage -> externalStoragePermissionState + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + val permissionGroup = PermissionGroup.values().getOrNull(requestCode) ?: return + val granted = grantResults.all { it == PackageManager.PERMISSION_GRANTED } + when (permissionGroup) { + PermissionGroup.Calendar -> calendarPermissionState.value = granted + PermissionGroup.Location -> locationPermissionState.value = granted + PermissionGroup.Contacts -> contactsPermissionState.value = granted + PermissionGroup.ExternalStorage -> externalStoragePermissionState.value = granted } } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/base/BaseActivity.kt b/ui/src/main/java/de/mm20/launcher2/ui/base/BaseActivity.kt new file mode 100644 index 00000000..fc71dac9 --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/base/BaseActivity.kt @@ -0,0 +1,19 @@ +package de.mm20.launcher2.ui.base + +import androidx.appcompat.app.AppCompatActivity +import de.mm20.launcher2.permissions.PermissionsManager +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +abstract class BaseActivity : AppCompatActivity(), KoinComponent { + private val permissionsManager: PermissionsManager by inject() + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults) + } +} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/activity/LauncherActivity.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/activity/LauncherActivity.kt index 510fc712..85989d44 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/activity/LauncherActivity.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/activity/LauncherActivity.kt @@ -1,6 +1,5 @@ package de.mm20.launcher2.ui.legacy.activity -import android.Manifest import android.animation.AnimatorSet import android.animation.LayoutTransition import android.animation.ObjectAnimator @@ -25,15 +24,12 @@ import android.view.inputmethod.InputMethodManager import android.widget.LinearLayout import android.widget.Toast import androidx.activity.viewModels -import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.PopupMenu import androidx.core.animation.doOnEnd -import androidx.core.app.ActivityCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.core.view.* import androidx.core.widget.NestedScrollView -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import com.afollestad.materialdialogs.LayoutMode import com.afollestad.materialdialogs.MaterialDialog @@ -48,11 +44,11 @@ import de.mm20.launcher2.ktx.dp import de.mm20.launcher2.ktx.isAtLeastApiLevel import de.mm20.launcher2.ktx.isBrightColor import de.mm20.launcher2.legacy.helper.ActivityStarter -import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.preferences.LauncherPreferences import de.mm20.launcher2.transition.ChangingLayoutTransition import de.mm20.launcher2.transition.OneShotLayoutTransition import de.mm20.launcher2.ui.R +import de.mm20.launcher2.ui.base.BaseActivity import de.mm20.launcher2.ui.databinding.ActivityLauncherBinding import de.mm20.launcher2.ui.launcher.modals.EditFavoritesView import de.mm20.launcher2.ui.launcher.modals.HiddenItemsView @@ -60,7 +56,6 @@ import de.mm20.launcher2.ui.launcher.search.SearchViewModel import de.mm20.launcher2.ui.legacy.component.WidgetView import de.mm20.launcher2.ui.legacy.helper.ThemeHelper import de.mm20.launcher2.ui.settings.SettingsActivity -import de.mm20.launcher2.weather.WeatherViewModel import de.mm20.launcher2.widgets.Widget import de.mm20.launcher2.widgets.WidgetType import de.mm20.launcher2.widgets.WidgetViewModel @@ -71,7 +66,7 @@ import java.util.* import kotlin.math.roundToInt -class LauncherActivity : AppCompatActivity() { +class LauncherActivity : BaseActivity() { /** * True if the search result list is visible diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalendarView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalendarView.kt index 98f9ed5b..3c9209d3 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalendarView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalendarView.kt @@ -47,7 +47,7 @@ class CalendarView : FrameLayout, KoinComponent { visibility = View.GONE return@observe } - if (it.isEmpty() && LauncherPreferences.instance.searchCalendars && !permissionsManager.checkPermission( + if (it.isEmpty() && LauncherPreferences.instance.searchCalendars && !permissionsManager.checkPermissionOnce( PermissionGroup.Calendar ) ) { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ContactView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ContactView.kt index cb363337..f6664e0e 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ContactView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ContactView.kt @@ -6,7 +6,6 @@ import android.util.AttributeSet import android.view.View import android.view.ViewGroup import android.widget.FrameLayout -import android.widget.SearchView import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.LiveData @@ -47,7 +46,7 @@ class ContactView : FrameLayout, KoinComponent { visibility = View.GONE return@observe } - if (it.isEmpty() && LauncherPreferences.instance.searchContacts && !permissionsManager.checkPermission( + if (it.isEmpty() && LauncherPreferences.instance.searchContacts && !permissionsManager.checkPermissionOnce( PermissionGroup.Contacts ) ) { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FileView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FileView.kt index a267cc47..8d7699f9 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FileView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FileView.kt @@ -45,7 +45,7 @@ class FileView : FrameLayout, KoinComponent { visibility = View.GONE return@observe } - if (it.isEmpty() && !permissionsManager.checkPermission( + if (it.isEmpty() && !permissionsManager.checkPermissionOnce( PermissionGroup.ExternalStorage ) ) { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt index 2945e4ae..56b1d223 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt @@ -3,7 +3,6 @@ package de.mm20.launcher2.ui.settings import android.os.Bundle import androidx.activity.compose.setContent import androidx.activity.viewModels -import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.tween @@ -20,16 +19,15 @@ import de.mm20.launcher2.licenses.OpenSourceLicenses import de.mm20.launcher2.preferences.Settings import de.mm20.launcher2.ui.LegacyLauncherTheme import de.mm20.launcher2.ui.R -import de.mm20.launcher2.ui.legacy.helper.ThemeHelper +import de.mm20.launcher2.ui.base.BaseActivity import de.mm20.launcher2.ui.locals.LocalNavController -import de.mm20.launcher2.ui.screens.settings.SettingsLicenseScreen import de.mm20.launcher2.ui.settings.about.AboutScreen import de.mm20.launcher2.ui.settings.appearance.AppearanceScreen import de.mm20.launcher2.ui.settings.license.LicenseScreen import de.mm20.launcher2.ui.settings.main.MainScreen import de.mm20.launcher2.ui.settings.weather.WeatherScreen -class SettingsActivity : AppCompatActivity() { +class SettingsActivity : BaseActivity() { private val viewModel: SettingsActivityVM by viewModels()