Skip to content

Commit

Permalink
Fixed SCHEDULE_EXACT_ALARM handling
Browse files Browse the repository at this point in the history
SDK 31..32 - check permission, request if revoked

SDK 33+ - use USE_EXACT_ALARM
  • Loading branch information
yuriykulikov committed Aug 5, 2023
1 parent 59f5db2 commit 43aff1a
Show file tree
Hide file tree
Showing 12 changed files with 195 additions and 128 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ val acraEmail =
android {
compileSdk = 33
defaultConfig {
versionCode = 31501
versionName = "3.15.01"
versionCode = 31502
versionName = "3.15.02"
applicationId = "com.better.alarm"
minSdk = 16
targetSdk = 33
Expand Down
249 changes: 128 additions & 121 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,121 +1,128 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission
android:name="android.permission.WRITE_SETTINGS"
android:maxSdkVersion="19" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

<application
android:name=".configuration.AlarmApplication"
android:allowBackup="true"
android:fullBackupOnly="true"
android:icon="@mipmap/ic_launcher"
android:installLocation="internalOnly"
android:label="@string/simple_alarm_clock"
tools:targetApi="lollipop">

<!-- Activity to set a new or modify existing alarm -->
<activity
android:name=".presenter.AlarmsListActivity"
android:configChanges="orientation|keyboardHidden|keyboard|navigation"
android:exported="true"
android:label="@string/alarm_list_title"
android:launchMode="singleTask"
android:theme="@style/DefaultDarkTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<!-- Application Settings -->
<activity
android:name=".presenter.SettingsActivity"
android:excludeFromRecents="true"
android:exported="false"
android:label="@string/settings"
android:theme="@style/DefaultDarkTheme" />

<activity
android:name=".presenter.HandleSetAlarm"
android:excludeFromRecents="true"
android:exported="true"
android:permission="com.android.alarm.permission.SET_ALARM"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.SET_ALARM" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- This activity is basically the same as AlarmAlert
but full-screen so that it can turn the display on. -->
<activity
android:name=".alert.AlarmAlertFullScreen"
android:configChanges="orientation|keyboardHidden|keyboard|navigation"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
android:taskAffinity=""
android:theme="@style/DefaultDarkTheme" />

<!-- Transparent activity with TimPickerFragment -->
<activity
android:name=".presenter.TransparentActivity"
android:configChanges="orientation|keyboardHidden|keyboard|navigation"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
android:taskAffinity=""
android:theme="@android:style/Theme.Holo.Dialog.NoActionBar" />

<!-- KlaxonPresenter plays sound -->
<service
android:name=".background.AlertServiceWrapper"
android:description="@string/alarm_klaxon_service_desc"
android:exported="false" />

<!-- Model -->
<receiver
android:name=".model.AlarmsReceiver"
android:exported="false">
<intent-filter>
<action android:name="${applicationId}.ACTION_FIRED" />
<action android:name="${applicationId}.ACTION_SNOOZED_FIRED" />
<action android:name="${applicationId}.ACTION_SOUND_EXPIRED" />
<action android:name="${applicationId}.ACTION_CANCEL_NOTIFICATION" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.TIME_SET" />
<action android:name="android.intent.action.TIMEZONE_CHANGED" />
<action android:name="android.intent.action.LOCALE_CHANGED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
<receiver
android:name=".model.TestReceiver"
android:exported="false"></receiver>
<!-- Persistence -->
<provider
android:name=".persistance.AlarmProvider"
android:authorities="${applicationId}.model"
android:exported="false" />
</application>
<!-- https://github.com/ACRA/acra/issues/787 -->
<queries>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="message/rfc822" />
</intent>
<intent>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<data android:mimeType="message/rfc822" />
</intent>
</queries>
</manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission
android:name="android.permission.WRITE_SETTINGS"
android:maxSdkVersion="19" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<uses-permission
android:name="android.permission.SCHEDULE_EXACT_ALARM"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<application
android:name=".configuration.AlarmApplication"
android:allowBackup="true"
android:fullBackupOnly="true"
android:icon="@mipmap/ic_launcher"
android:installLocation="internalOnly"
android:label="@string/simple_alarm_clock"
tools:targetApi="lollipop">

<!-- Activity to set a new or modify existing alarm -->
<activity
android:name=".presenter.AlarmsListActivity"
android:configChanges="orientation|keyboardHidden|keyboard|navigation"
android:exported="true"
android:label="@string/alarm_list_title"
android:launchMode="singleTask"
android:theme="@style/DefaultDarkTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<!-- Application Settings -->
<activity
android:name=".presenter.SettingsActivity"
android:excludeFromRecents="true"
android:exported="false"
android:label="@string/settings"
android:theme="@style/DefaultDarkTheme" />

<activity
android:name=".presenter.HandleSetAlarm"
android:excludeFromRecents="true"
android:exported="true"
android:permission="com.android.alarm.permission.SET_ALARM"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.SET_ALARM" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- This activity is basically the same as AlarmAlert
but full-screen so that it can turn the display on. -->
<activity
android:name=".alert.AlarmAlertFullScreen"
android:configChanges="orientation|keyboardHidden|keyboard|navigation"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
android:taskAffinity=""
android:theme="@style/DefaultDarkTheme" />

<!-- Transparent activity with TimPickerFragment -->
<activity
android:name=".presenter.TransparentActivity"
android:configChanges="orientation|keyboardHidden|keyboard|navigation"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
android:taskAffinity=""
android:theme="@android:style/Theme.Holo.Dialog.NoActionBar" />

<!-- KlaxonPresenter plays sound -->
<service
android:name=".background.AlertServiceWrapper"
android:description="@string/alarm_klaxon_service_desc"
android:exported="false" />

<!-- Model -->
<receiver
android:name=".model.AlarmsReceiver"
android:exported="false">
<intent-filter>
<action android:name="${applicationId}.ACTION_FIRED" />
<action android:name="${applicationId}.ACTION_SNOOZED_FIRED" />
<action android:name="${applicationId}.ACTION_SOUND_EXPIRED" />
<action android:name="${applicationId}.ACTION_CANCEL_NOTIFICATION" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.TIME_SET" />
<action android:name="android.intent.action.TIMEZONE_CHANGED" />
<action android:name="android.intent.action.LOCALE_CHANGED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED" />
</intent-filter>
</receiver>
<receiver
android:name=".model.TestReceiver"
android:exported="false" />
<!-- Persistence -->
<provider
android:name=".persistance.AlarmProvider"
android:authorities="${applicationId}.model"
android:exported="false" />
</application>
<!-- https://github.com/ACRA/acra/issues/787 -->
<queries>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="message/rfc822" />
</intent>
<intent>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<data android:mimeType="message/rfc822" />
</intent>
</queries>
</manifest>
43 changes: 38 additions & 5 deletions app/src/main/java/com/better/alarm/Permissions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ package com.better.alarm

import android.Manifest
import android.app.Activity
import android.app.AlarmManager
import android.app.AlertDialog
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.provider.Settings
import androidx.core.content.getSystemService
import androidx.core.net.toUri
import com.better.alarm.background.PlayerWrapper
import com.better.alarm.configuration.globalLogger
import com.better.alarm.logger.Logger
Expand All @@ -14,9 +19,20 @@ import com.better.alarm.presenter.userFriendlyTitle

/** Checks if all ringtones can be played, and requests permissions if it is not the case */
fun checkPermissions(activity: Activity, tones: List<Alarmtone>) {
if (Build.VERSION.SDK_INT >= 23 &&
activity.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) !=
PackageManager.PERMISSION_GRANTED) {
checkSetAlarmPermissions(activity)
checkMediaPermissions(activity, tones)
}

private fun checkMediaPermissions(activity: Activity, tones: List<Alarmtone>) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return
val mediaPermission =
when {
Build.VERSION.SDK_INT in 23..31 -> Manifest.permission.READ_EXTERNAL_STORAGE
Build.VERSION.SDK_INT >= 33 -> Manifest.permission.READ_MEDIA_AUDIO
else -> null
}
if (mediaPermission != null &&
activity.checkSelfPermission(mediaPermission) != PackageManager.PERMISSION_GRANTED) {
val logger: Logger by globalLogger("checkPermissions")

val unplayable =
Expand Down Expand Up @@ -44,14 +60,31 @@ fun checkPermissions(activity: Activity, tones: List<Alarmtone>) {
activity.getString(
R.string.permissions_external_storage_text, unplayable.joinToString(", ")))
.setPositiveButton(android.R.string.ok) { _, _ ->
activity.requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 3)
activity.requestPermissions(arrayOf(mediaPermission), 3)
}
.setNegativeButton(android.R.string.cancel, null)
.show()
} catch (e: Exception) {
logger.e("Was not able to show dialog to request permission, continue without the dialog")
activity.requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 3)
activity.requestPermissions(arrayOf(mediaPermission), 3)
}
}
}
}

fun checkSetAlarmPermissions(activity: Activity) {
if (Build.VERSION.SDK_INT in 31..33 &&
activity.getSystemService<AlarmManager>()?.canScheduleExactAlarms() != true) {
AlertDialog.Builder(activity)
.setTitle(activity.getString(R.string.set_exact_alarm_permission_title))
.setMessage(activity.getString(R.string.set_exact_alarm_permission_text))
.setPositiveButton(android.R.string.ok) { _, _ ->
activity.startActivity(
Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM).apply {
data = ("package:${BuildConfig.APPLICATION_ID}").toUri()
})
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
}
6 changes: 6 additions & 0 deletions app/src/main/java/com/better/alarm/model/AlarmSetter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ interface AlarmSetter {
@TargetApi(Build.VERSION_CODES.O)
private inner class OreoSetter : ISetAlarmStrategy {
override fun setRTCAlarm(calendar: Calendar, pendingIntent: PendingIntent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !am.canScheduleExactAlarms()) {
log.warning { "Permission denied!" }
return
}
val pendingShowList =
PendingIntent.getActivity(
mContext,
Expand All @@ -159,6 +163,8 @@ interface AlarmSetter {
}

override fun setInexactAlarm(calendar: Calendar, pendingIntent: PendingIntent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !am.canScheduleExactAlarms())
return
am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pendingIntent)
}
}
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/better/alarm/model/AlarmsReceiver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.better.alarm.model

import android.app.AlarmManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
Expand Down Expand Up @@ -44,6 +45,7 @@ class AlarmsReceiver : BroadcastReceiver() {
alarms.getAlarm(id)?.onInexactAlarmFired()
}
Intent.ACTION_BOOT_COMPLETED,
AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
Intent.ACTION_TIMEZONE_CHANGED,
Intent.ACTION_LOCALE_CHANGED,
Intent.ACTION_MY_PACKAGE_REPLACED -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ private void doForPreLollipop(Context context, Optional<Store.Next> nextOptional

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void doForLollipop(Context context, Optional<Store.Next> nextOptional) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !am.canScheduleExactAlarms()) {
return;
}

if (nextOptional.isPresent()) {
int id = nextOptional.get().alarm().getId();

Expand All @@ -110,6 +114,7 @@ private void doForLollipop(Context context, Optional<Store.Next> nextOptional) {
PendingIntent.getActivity(context, id, showList, OreoKt.pendingIntentUpdateCurrentFlag());

long milliseconds = nextOptional.get().nextNonPrealarmTime();

am.setAlarmClock(
new AlarmClockInfo(milliseconds, showIntent),
PendingIntent.getBroadcast(
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,6 @@
<string name="alert_permission_post_notifications_title">Berechtigung zur Anzeige von Benachrichtigungen</string>
<string name="alert_permission_post_notifications_text">Um ein Popup anzuzeigen, wenn der Alarm klingelt, ist eine Berechtigung zur Anzeige von Benachrichtigungen erforderlich.</string>
<string name="app_default_ringtone">Standard-App (%s)</string>
<string name="set_exact_alarm_permission_title">Erlaubnis zur Einstellung genauer Alarmzeit</string>
<string name="set_exact_alarm_permission_text">Um einen Alarm zur genauen Zeit einzustellen, ist die Erlaubnis zur Einstellung der genauen Alarmzeit erforderlich.</string>
</resources>
Loading

0 comments on commit 43aff1a

Please sign in to comment.