I'm making an alarm clock app on Android. It seems that I am doing everything according to the documentation, but the alarm clock never went off. I don't want to use the SetExact method to set the alarm because, according to the documentation, SetAlarmClock is logical to use in my case. I need a use case that is relevant and works today.
Here, just in case, I will give examples of the code that I wrote - intents are installed, but do not work.
Class for setting signals:
class AlarmManagement private constructor(private val context: Context) {
companion object {
private var instance: AlarmManagement? = null
@JvmStatic
fun getInstance(context: Context): AlarmManagement {
if (instance == null) {
instance = AlarmManagement(context.applicationContext)
}
return instance!!
}
}
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val database = Database.getInstance(MyApplication.getAppContext())
fun setOrUpdateAlarm(clock: Clock) {
fun clockValuesAreCorrectForAlarmSetting(clock: Clock): Boolean {
if(clock == null) {
Camp.log("error alarm", "Clock equal to zero was passed to setOrUpdateAlarm()")
return false
}
if (clock.isActive == false) return false//this is acceptable, just end the method
if (clock.id == null) {
Camp.log("error alarm", "A clock with id = null was passed to set the alarm")
return false
}
return true
}
if (!clockValuesAreCorrectForAlarmSetting(clock)) return
if (!exactAlarmIsAllowed()) return
val alarmIntent = Intent(context, AlarmReceiver::class.java).apply {
}
val pendingIntent = PendingIntent.getBroadcast(
context
clock.id!!.toInt(),//ALARM_REQUEST_CODE
alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
//To use not millisseconds to set the time, but time in a convenient format, use Calendar
val calendar = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, clock.triggeringHour)
set(Calendar.MINUTE, clock.triggeringMinute)
if (clock.alarmRepeatingMode == AlarmRepeatingMode.EVERYDAY || clock.alarmRepeatingMode == AlarmRepeatingMode.SELECTDAYS) {
add(Calendar.DAY_OF_YEAR, 1) // Set the interval for the day
}
}
val info = AlarmManager.AlarmClockInfo(calendar.timeInMillis, pendingIntent)
//Warning: on android below 5.0 you need to use ExactAlarm instead of setAlarmClock (I’m making an application for newer versions)
alarmManager.setAlarmClock(info, pendingIntent)
Camp.log("alarm", "AlarmClock was set for $clock")
}
//Used at application startup to check permission to set fine Alarms (AlarmClock and ExactAlarm)
fun exactAlarmIsAllowed(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (alarmManager.canScheduleExactAlarms() == true) {
return true
} else {
Camp.log(
"alarm info",
"Check result: permission to run alarms is missing (exact alarms)"
)
return false
}
} else {
Camp.log(
"alarm info",
"Check result: there is permission to run alarms (exact alarms)"
)
return true
}
}
//Restarts the intents of all active alarms
fun reloadAllActiveClocksAlarmIntents() {
val clocks = database.getAllClocks()
clocks.forEach {
setOrUpdateAlarm(it)
}
}
/** Cancel AlarmIntent by intentRequestCode
I am using clock.id as requestCode at the same time*/
fun cancelAlarmIntent(intentRequestCode: Int) {
//Create a new empty intent with the same requestCode as the one you want to cancel
//In this case, a replacement occurs (the previous one is deleted)
val alarmIntent = Intent(context, AlarmReceiver::class.java).apply {}
val pendingIntent = PendingIntent.getBroadcast(
context
intentRequestCode,
alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
//Cancel empty intent
pendingIntent.cancel()
}
}
The Receiver class, which receives them when triggered (the intent with SetAlarmClock() is not even not accepted, although SetExact was accepted). It’s probably not necessary to include its code here, because the intent doesn’t reach it, but still:
class AlarmReceiver : BroadcastReceiver() {
/* val alarmsManager = AlarmManagement.getInstance(*//*MyApplication.getAppContext()*//*)*/
lateinit var triggeringClock: Clock
val intentProcessingCompletionMessage = "Intent processing has completed."
// This method is called when the BroadcastReceiver is receiving an Intent broadcast.
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == "ACTION_START_ALARM") {
Camp.log("alarm","AlarmReceiverreceived alarm intent")
val calendar = Calendar.getInstance()
val today = calendar.get(Calendar.DAY_OF_WEEK)
val database = Database.getInstance(MyApplication.getAppContext())
fun intentProcessing() {
fun gettingClock(): Clock? {
val intentClockId = intent.getSerializableExtra("clockId") as Long
if (intentClockId == null) {
Camp.log("error alarm", "Intent clock id == null")
return null
}
val dbClock = database.getClockById(intentClockId)
if (dbClock == null) {
Camp.log("error alarm", "Clock with the same id as intentClock was not found")
return null
}
return dbClock
}
val clock = gettingClock()
if (clock == null) {
Camp.log(
"error alarm",
"An error occurred while retrieving the alarm object. $intentProcessingCompletionMessage"
)
return
}
if(!clock.isActive){
Camp.log(
"error alarm",
"For an unexpected reason, an inactive alarm was triggered. The intent for this alarm has been canceled and will no longer fire. $intentProcessingCompletionMessage"
)
return
}
fun todayIsRightDayForAlarm(): Boolean {
if (clock.alarmRepeatingMode == AlarmRepeatingMode.ONETIME || clock.alarmRepeatingMode == AlarmRepeatingMode.EVERYDAY) {
return true
}
if (clock.alarmRepeatingMode == AlarmRepeatingMode.SELECTDAYS) {
//Calendar.DAY_OF_WEEK week starts on Sun (Sun == 1, Mon == 2, etc.), so we convert it to (1 shl (today - 1))
return (clock.triggeringWeekDays?.and((1 shl (today - 1)))) != 0 //check for the presence of today in the triggeringWeekDays combination using bitwise multiplication
}
Camp.log(
"error alarm",
"Checking for the day of the week to trigger this type of alarm was not provided"
)
return false
}
if (!todayIsRightDayForAlarm()) {
Camp.log(
"Alarm info"
"The current day of the week does not match the days of the received alarm. $intentProcessingCompletionMessage"
)
return
}
fun activateAlarm(triggeringClock: Clock) {
when (triggeringClock.alarmRepeatingMode) {
AlarmRepeatingMode.ONETIME -> {
triggeringClock.isActive = false
database.insertOrUpdateClock_IdOrResult(triggeringClock)
}
AlarmRepeatingMode.EVERYDAY, AlarmRepeatingMode.SELECTDAYS -> {
}
}
Camp.log("Alarm info", "Alarm starting. LockScreenActivity started. $intentProcessingCompletionMessage")
MyApplication.getInstance().startLockScreenActivity(triggeringClock)
}
activateAlarm(clock)
return
}
intentProcessing()
}
//Triggered when the device is rebooted
if (intent.action == "android.intent.action.BOOT_COMPLETED") {
AlarmManagement.getInstance(MyApplication.getAppContext()).reloadAllActiveClocksAlarmIntents()
}
}
}
Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!--To use ExactAlarms and SetAlarmClock-->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<!--AlarmClock needs to continue working even after the device is rebooted-->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!--Permission to display pop-up windows (activity)qa when the application is running in the background-->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:name=".MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Base.Theme.PasswordAlarmClock"
tools:targetApi="31">
<!-- Declare the Worker class for the WorkManager -->
<service
android:name=".MyWorker"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE">
<intent-filter>
<action android:name="androidx.work.Worker" />
</intent-filter>
</service>
<!-- ... -->
<!--Declare an unkillable service for precise notifications-->
<!--android:foregroundServiceType=""//I don’t know what type should be used and whether it is necessary-->
<service
android:name=".AlarmService" />
<!-- <receiver
android:name=".AlarmReceiver"
android:enabled="true"
android:exported="true" />-->
<receiver android:name=".AlarmReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<activity
android:name=".MainActivity"
android:exported="true">
</activity>
<activity
android:name=".ClockSettingActivity"
android:exported="false">
</activity>
<activity
android:name=".SettingsActivity"
android:exported="true">
</activity>
<!-- android:windowSoftInputMode="stateHidden" is used to prevent the keyboard from appearing automatically (due to the use of an invisible zone for the input field)-->
<activity
android:name=".LockScreenActivity"
android:exported="true"
android:windowSoftInputMode="stateHidden">
<!--This block makes the activity start-->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
While trying without Service. Everything works on the emulator, but not on Xiaomi. It may be possible to update using FLAG_UPDATE_CURRENT, and then there will be no need to cancel when starting a new intent.
AlarmManagement
class AlarmManagement private constructor(/*private val context: Context*/) {
companion object {
private var instance: AlarmManagement? = null
@JvmStatic
fun getInstance(): AlarmManagement {
if (instance == null) {
instance = AlarmManagement()
}
return instance!!
}
}
val alarmManager = MyApplication.getAppContext().getSystemService(Context.ALARM_SERVICE) as AlarmManager
val database = Database.getInstance(MyApplication.getAppContext())
val exactAlarmSettingStrategy: ExactAlarmSettingStrategy = SetAlarmClock()
fun setOrUpdateAlarm(clock: Clock) {
fun clockValuesAreCorrectForAlarmSetting(): Boolean {
if (!clock.isActive) return false
if (clock.id == null) {
Camp.log("error alarm", "A clock with id = null was passed for setting the alarm")
return false
}
return true
}
if (!clockValuesAreCorrectForAlarmSetting()) return
val requestCode = clock.id!!.toInt()
cancelAlarmIntent(requestCode)
val alarmIntent = Intent(MyApplication.getAppContext(), AlarmReceiver::class.java).apply {
action = "ALARM"
putExtra("clockId", clock.id)
}
if (!exactAlarmIsAllowed()) return
val pendingIntent = PendingIntent.getBroadcast(
MyApplication.getAppContext(),
requestCode,
alarmIntent,
PendingIntent.FLAG_IMMUTABLE
)
val calendar = java.util.Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, clock.triggeringHour)
set(Calendar.MINUTE, clock.triggeringMinute)
}
if (calendar.timeInMillis <= System.currentTimeMillis()) {
calendar.add(Calendar.DAY_OF_YEAR, 1)
}
exactAlarmSettingStrategy.setExactAlarm(alarmManager, pendingIntent, calendar)
Camp.log("alarm", "AlarmClock was set for $clock")
}
fun exactAlarmIsAllowed(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
return if (alarmManager.canScheduleExactAlarms()) {
true
} else {
Camp.log(
"alarm info",
"Check result: permission for scheduling exact alarms is missing"
)
false
}
} else {
Camp.log(
"alarm info",
"Check result: permission for scheduling exact alarms is present"
)
return true
}
}
fun activateInactiveAlarmIntentsWhoseClocksAreActive() {
val clocks = database.getAllClocks()
clocks.forEach {
setOrUpdateAlarm(it)
}
}
fun cancelAlarmIntent(intentRequestCode: Int) {
val intent = Intent(MyApplication.getAppContext(), AlarmReceiver::class.java)
val pendingIntent =
PendingIntent.getService(MyApplication.getAppContext(), intentRequestCode, intent,
PendingIntent.FLAG_NO_CREATE)
if (pendingIntent != null) {
alarmManager.cancel(pendingIntent)
}
}
}
Receiver
package com.camporation.passwordalarmclock
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import java.util.Calendar
class AlarmReceiver : BroadcastReceiver() {
lateinit var triggeringClock: Clock
val intentProcessingCompletionMessage = "Intent processing is complete. "
override fun onReceive(context: Context, intent: Intent) {
Camp.log("AlarmReceiver received intent")
if (intent.action == "ALARM") {
Camp.log("alarm","AlarmReceiver received alarm intent")
val calendar = Calendar.getInstance()
val today = calendar.get(Calendar.DAY_OF_WEEK)
val database = Database.getInstance(MyApplication.getAppContext())
fun intentProcessing() {
fun gettingClock(): Clock? {
val intentClockId = intent.getSerializableExtra("clockId") as Long?
if (intentClockId == null) {
Camp.log("error alarm", "Intent clock id == null")
return null
}
val dbClock = database.getClockById(intentClockId)
if (dbClock == null) {
Camp.log("error alarm", "Clock with the same id as intentClock was not found")
return null
}
return dbClock
}
val clock = gettingClock()
if (clock == null) {
Camp.log(
"error alarm",
"An error occurred while obtaining the alarm object. $intentProcessingCompletionMessage"
)
return
}
if(!clock.isActive){
Camp.log(
"error alarm",
"For an unforeseen reason, an inactive alarm went off. The intent of this alarm is canceled and will not trigger anymore. $intentProcessingCompletionMessage"
)
return
}
fun todayIsRightDayForAlarm(): Boolean {
if (clock.alarmRepeatingMode == AlarmRepeatingMode.ONETIME || clock.alarmRepeatingMode == AlarmRepeatingMode.EVERYDAY) {
return true
}
if (clock.alarmRepeatingMode == AlarmRepeatingMode.SELECTDAYS) {
return (clock.triggeringWeekDays?.and((1 shl (today - 1)))) != 0
}
Camp.log(
"error alarm",
"Checking the day of the week for triggering this type of alarm was not provided"
)
return false
}
if (!todayIsRightDayForAlarm()) {
Camp.log(
"Alarm info",
"The current day of the week does not match the triggering days of the accepted alarm. $intentProcessingCompletionMessage"
)
return
}
fun updateClockInDatabase(){
when (clock.alarmRepeatingMode) {
AlarmRepeatingMode.ONETIME -> {
clock.isActive = false
val idOrResult = database.insertOrUpdateClock_IdOrResult(clock)
Camp.log("One-time alarm modified: isActive = false. It is added to the database with id/result: $idOrResult")
}
AlarmRepeatingMode.EVERYDAY, AlarmRepeatingMode.SELECTDAYS -> {
// currently not required for these types
}
}
}
updateClockInDatabase()
MainActivity.currentActivity?.onDatabaseUpdatingInReceiver()
AlarmManagement.getInstance().activateInactiveAlarmIntentsWhoseClocksAreActive()
Camp.log("alarm allAlarms receiver","The Receiver activated all inactive intents with active alarms")
fun activateAlarm() {
Camp.log("Alarm info", "Triggering the alarm. Starting the LockScreenActivity. $intentProcessingCompletionMessage")
MyApplication.getInstance().startLockScreenActivity(clock)
}
activateAlarm()
return
}
intentProcessing()
}
if (intent.action == "android.intent.action.BOOT_COMPLETED") {
AlarmManagement.getInstance().activateInactiveAlarmIntentsWhoseClocksAreActive()
}
}
}
ExactAlarmSetting
interface ExactAlarmSettingStrategy {
fun setExactAlarm(alarmManager: AlarmManager, pendingIntent: PendingIntent, calendar: Calendar)
}
class SetAlarmClock : ExactAlarmSettingStrategy {
override fun setExactAlarm(
alarmManager: AlarmManager,
pendingIntent: PendingIntent,
calendar: Calendar
) {
val info = AlarmManager.AlarmClockInfo(calendar.timeInMillis, pendingIntent)
alarmManager.setAlarmClock(info, pendingIntent)
}
}
class SetExactAndAllowWhileIdle : ExactAlarmSettingStrategy {
override fun setExactAlarm(
alarmManager: AlarmManager,
pendingIntent: PendingIntent,
calendar: Calendar
) {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent
)
}
}
Manifest
xml
<!-- For using ExactAlarms and SetAlarmClock -->
<!-- https://developer.android.com/develop/background-work/services/alarms/schedule#exact-permission-declare -->
<!-- Requires either SCHEDULE_EXACT_ALARM or USE_EXACT_ALARM -->
<!-- Check permission existence using canScheduleExactAlarms() -->
<!-- Provided manually for Android 12 and above -->
<!-- May be revoked by the user -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<!-- Provided automatically, available from Android 13 and above -->
<!-- Cannot be revoked by the user -->
<!-- Cannot use (not critical), as working with earlier Android versions -->
<!-- <uses-permission android:name="android.permission.USE_EXACT_ALARM"/> -->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<receiver android:name=".AlarmReceiver" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
```