1
0
Fork 0
mirror of https://github.com/MatomoCamp/matomocamp-companion-android.git synced 2024-09-19 16:13:46 +02:00

fix: properly handle the case of missing "schedule exact alarm" permission on Android 12+

This commit is contained in:
Christophe Beyls 2022-09-07 17:00:46 +02:00
parent 7b4dd70b41
commit 53d9d5e60a
5 changed files with 94 additions and 33 deletions

View file

@ -8,8 +8,9 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />
<!-- Permission required for alarms -->
<!-- Permissions required for alarms -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<!-- Make touch screen optional since all screens can be used with a pad -->
<uses-feature

View file

@ -4,7 +4,9 @@ import android.os.Bundle
import androidx.fragment.app.commit
import be.digitalia.fosdem.R
import be.digitalia.fosdem.fragments.SettingsFragment
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class SettingsActivity : SimpleToolbarActivity() {
override fun onCreate(savedInstanceState: Bundle?) {

View file

@ -35,6 +35,7 @@ import be.digitalia.fosdem.settings.UserSettingsProvider
import be.digitalia.fosdem.utils.BackgroundWorkScope
import be.digitalia.fosdem.utils.roomNameToResourceName
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.collectIndexed
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
@ -59,17 +60,29 @@ class AppAlarmManager @Inject constructor(
}
private val queueMutex = Mutex()
private suspend fun isNotificationsEnabled(): Boolean =
userSettingsProvider.isNotificationsEnabled.first()
private suspend fun isNotificationsEnabled(): Boolean {
return canScheduleExactAlarms && userSettingsProvider.isNotificationsEnabled.first()
}
init {
// Skip initial values and only act on changes
BackgroundWorkScope.launch {
userSettingsProvider.isNotificationsEnabled.drop(1).collect { isEnabled ->
onEnabledChanged(isEnabled)
userSettingsProvider.isNotificationsEnabled.collectIndexed { index, isEnabled ->
if (index == 0) {
// On app launch, switch off the preference if we don't have the required permission
if (isEnabled && !canScheduleExactAlarms) {
userSettingsProvider.updateNotificationsEnabled(false)
}
} else {
if (isEnabled) {
updateAlarms()
} else {
disableAlarms(true)
}
}
}
}
BackgroundWorkScope.launch {
// Skip initial values and only act on changes
userSettingsProvider.notificationsDelayInMillis.drop(1).collect {
if (isNotificationsEnabled()) {
updateAlarms()
@ -78,8 +91,15 @@ class AppAlarmManager @Inject constructor(
}
}
val canScheduleExactAlarms
get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.S || alarmManager.canScheduleExactAlarms()
suspend fun onBootCompleted() {
onEnabledChanged(isNotificationsEnabled())
if (isNotificationsEnabled()) {
updateAlarms()
} else {
disableAlarms(false)
}
}
suspend fun onScheduleRefreshed() {
@ -132,14 +152,6 @@ class AppAlarmManager @Inject constructor(
}
}
private suspend fun onEnabledChanged(isEnabled: Boolean) {
return if (isEnabled) {
updateAlarms()
} else {
disableAlarms()
}
}
private suspend fun updateAlarms() {
queueMutex.withLock {
// Create/update all alarms
@ -168,11 +180,13 @@ class AppAlarmManager @Inject constructor(
}
}
private suspend fun disableAlarms() {
private suspend fun disableAlarms(cancelExistingAlarms: Boolean) {
queueMutex.withLock {
// Cancel alarms of every bookmark in the future
for (info in bookmarksDao.getBookmarksAlarmInfo(Instant.now())) {
alarmManager.cancel(getAlarmPendingIntent(info.eventId))
if (cancelExistingAlarms) {
// Cancel alarms of every bookmark in the future
for (info in bookmarksDao.getBookmarksAlarmInfo(Instant.now())) {
alarmManager.cancel(getAlarmPendingIntent(info.eventId))
}
}
setAlarmReceiverEnabled(false)
}

View file

@ -1,24 +1,47 @@
package be.digitalia.fosdem.fragments
import android.app.Activity
import android.app.Dialog
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.text.method.LinkMovementMethod
import android.widget.TextView
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.core.net.toUri
import androidx.fragment.app.DialogFragment
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.TwoStatePreference
import be.digitalia.fosdem.BuildConfig
import be.digitalia.fosdem.R
import be.digitalia.fosdem.alarms.AppAlarmManager
import be.digitalia.fosdem.settings.PreferenceKeys
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class SettingsFragment : PreferenceFragmentCompat() {
@Inject
lateinit var alarmManager: AppAlarmManager
private val requestExactAlarmsLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
val preference =
findPreference<Preference>(PreferenceKeys.NOTIFICATIONS_ENABLED) as? TwoStatePreference
preference?.isChecked = true
}
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.settings, rootKey)
setupNotifications()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setupNotificationsChannel()
}
@ -26,35 +49,50 @@ class SettingsFragment : PreferenceFragmentCompat() {
populateVersion()
}
private fun setupNotifications() {
findPreference<Preference>(PreferenceKeys.NOTIFICATIONS_ENABLED)?.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, newValue ->
if (newValue as Boolean && !alarmManager.canScheduleExactAlarms) {
val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
.setData("package:${BuildConfig.APPLICATION_ID}".toUri())
requestExactAlarmsLauncher.launch(intent)
false
} else true
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private fun setupNotificationsChannel() {
findPreference<Preference>(PreferenceKeys.NOTIFICATIONS_CHANNEL)?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
AppAlarmManager.startChannelNotificationSettingsActivity(requireContext())
true
}
findPreference<Preference>(PreferenceKeys.NOTIFICATIONS_CHANNEL)?.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
AppAlarmManager.startChannelNotificationSettingsActivity(requireContext())
true
}
}
private fun setupAboutDialog() {
findPreference<Preference>(PreferenceKeys.ABOUT)?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
AboutDialogFragment().show(parentFragmentManager, "about")
true
}
findPreference<Preference>(PreferenceKeys.ABOUT)?.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
AboutDialogFragment().show(parentFragmentManager, "about")
true
}
}
class AboutDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.app_name)
.setIcon(R.mipmap.ic_launcher)
.setMessage(resources.getText(R.string.about_text))
.setPositiveButton(android.R.string.ok, null)
.create()
.setTitle(R.string.app_name)
.setIcon(R.mipmap.ic_launcher)
.setMessage(resources.getText(R.string.about_text))
.setPositiveButton(android.R.string.ok, null)
.create()
}
override fun onStart() {
super.onStart()
// Make links clickable; must be called after the dialog is shown
requireDialog().findViewById<TextView>(android.R.id.message).movementMethod = LinkMovementMethod.getInstance()
requireDialog().findViewById<TextView>(android.R.id.message).movementMethod =
LinkMovementMethod.getInstance()
}
}

View file

@ -52,6 +52,12 @@ class UserSettingsProvider @Inject constructor(@ApplicationContext context: Cont
val isNotificationsEnabled: Flow<Boolean>
get() = sharedPreferences.getBooleanAsFlow(PreferenceKeys.NOTIFICATIONS_ENABLED)
fun updateNotificationsEnabled(notificationsEnabled: Boolean) {
sharedPreferences.edit()
.putBoolean(PreferenceKeys.NOTIFICATIONS_ENABLED, notificationsEnabled)
.apply()
}
val isNotificationsVibrationEnabled: Flow<Boolean>
get() = sharedPreferences.getBooleanAsFlow(PreferenceKeys.NOTIFICATIONS_VIBRATE)