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
Receiver
ExactAlarmSetting
Manifest
xml