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:
parent
7b4dd70b41
commit
53d9d5e60a
5 changed files with 94 additions and 33 deletions
|
@ -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
|
||||
|
|
|
@ -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?) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in a new issue