I am trying to reboot my device and relaunch my alarm following this https://developer.android.com/training/scheduling/alarms#boot.
But unfortunately only the BOOT_COMPLETED action is received. Other intents from Alarm is not received.
During on BOOT_COMPLETED . I schedule my task for the alarm once again.
I also have confirmed using the command below
adb shell dumpsys alarm
That I still have the alarm after rebooting. but the when the alarm fires Broadcast receiver does not receive it.
Does anyone have an idea what I might missing? Thank you.
NOTE: If I use the same code without restarting the device. The alarm will be received by the Broadcast receiver. Issue only happens if I restart.
Here is the code for the Playground App im testing it on.
MainActivity
class MainActivity : Activity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val receiver = ComponentName(applicationContext, BootReceiver::class.java)
applicationContext.packageManager.setComponentEnabledSetting(
receiver,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP
)
val prefs = getSharedPreferences("TEST_PREF", MODE_PRIVATE)
binding.text.text = "Hello!! " + prefs.getString("message", "No message defined") //"No name defined" is the default value.
Log.d("TESTER", "app loaded")
}
}
BroadcastReceiver
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if(context == null) return
if(intent == null) return
if (intent.action == "android.intent.action.BOOT_COMPLETED") {
// Set the alarm here.
ScheduleManager.scheduleAlarm(context, TEST_TIME)
Log.d("TESTER", "android.intent.action.BOOT_COMPLETED")
} else {
val message = intent.getStringExtra(MESSAGE_KEY)
val editor: SharedPreferences.Editor = context.applicationContext.getSharedPreferences("TEST_PREF", MODE_PRIVATE).edit()
editor.putString("message", message)
editor.apply()
Log.d("TESTER", "message from alarm received")
}
}
}
Alarm Manager
object ScheduleManager {
const val MESSAGE_KEY = "message"
const val TEST_TIME = 1647915300000L //2022-03-22 10:15:00
fun scheduleAlarm(context: Context, time: Long) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, BootReceiver::class.java)
intent.putExtra(MESSAGE_KEY, "This is from the alarm $time")
val pendingIntent = PendingIntent.getBroadcast(
context,
1111,
intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
time,
pendingIntent
)
}
}
Log Results
Launched the app at 2022-03-22 10:11:17.414
2022-03-22 10:11:17.414 4360-4360/com.wearosplayground D/TESTER: app loaded
proceeds rebooting WearOs device
Boot complete action received at 2022-03-22 10:13:49.586
2022-03-22 10:13:49.586 3576-3576/com.ausom.wearosplayground D/TESTER: android.intent.action.BOOT_COMPLETED
I have set the alarm at this time to be at 2022-03-22 10:15:00
but nothing happens
const val TEST_TIME = 1647915300L //2022-03-22 10:15:00
Is specified as seconds.
You should change this to 1647915300000L
See
println(java.util.Date(1647915300L)) //Tue Jan 20 01:45:15 UTC 1970
Using this link as a reference, the solution using accessibility works.
BUT, this is not advisable as it accessibility is for disabled users. So for now the real solution I can think of is communicate with your WearOS device manufacturer and ask for whitelisting. Other than that we will need to continue resorting to tricks listed in the link I have shared.
Related
Installing App Manually
val install = Intent(Intent.ACTION_VIEW)
install.setDataAndType(
uri,
APP_INSTALL_PATH
)
// install.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
install.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
}
startActivity(install)
Handling Broadcast Receiver
val intentFilter = IntentFilter()
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED)
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED)
intentFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED)
intentFilter.addAction(Intent.ACTION_PACKAGE_INSTALL)
intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED)
intentFilter.addAction(Intent.ACTION_MY_PACKAGE_REPLACED)
intentFilter.addDataScheme("package")
registerReceiver(restartAppReceiver, intentFilter)
private val restartAppReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Toast.makeText(this#LoginActivity, getString(R.string.sign_out), Toast.LENGTH_LONG)
.show()
//start activity
val i = Intent()
Log.i("App_started", "Yes")
i.setClassName(packageName, packageName+".screen.activity.LoginActivity")
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context?.startActivity(i)
}
}
Problem Facing
Here I'm not able to receive anything on receiver when app gets installed manually. I need to open the app automatically when it gets installed manually without any action from users end.
thats simple: you can't. user must open it manually. starting Android 10 you can't start any Activity from BroadcastReceiver. besides that: most of your IntentFilter entries are no-op (why are you calling addDataScheme)
I am trying to create a basic alarm clock app. I set the alarm using the setExact method of the AlarmManager. And then I created a BroadcastReceiver that will display a toast and will start a new activity when the previously set alarm goes off. I also have a WakeLock in my BroadcastReceiver to try to turn on the screen. The BroadcastReceiver is working. It is able to receive the event and then created the activity. But the screen is not turning on. I've read and followed a lot of answers of posts like this in SO but I still cant make it work.
This is my code for setting the alarm:
alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent ->
PendingIntent.getBroadcast(context, 1, intent, 0)
}
val alarmTime = Calendar.getInstance().apply {
timeInMillis = System.currentTimeMillis()
set(Calendar.HOUR_OF_DAY, alarmForm.time.get(Calendar.HOUR_OF_DAY))
set(Calendar.MINUTE, alarmForm.time.get(Calendar.MINUTE))
set(Calendar.SECOND, 0)
}
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
alarmTime.timeInMillis,
alarmIntent
)
This is my BroadcastReceiver:
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val wakeLock: PowerManager.WakeLock =
(context.getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag").apply {
acquire(10*60*1000L /*10 minutes*/)
}
}
Toast.makeText(context, "Wake Up!", Toast.LENGTH_LONG).show()
val alarmDisplayIntent = Intent(context, AlarmDisplayActivity::class.java)
alarmDisplayIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(alarmDisplayIntent)
wakeLock.release()
}
}
And this is the activity I display when alarm goes off:
class AlarmDisplayActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_alarm_display)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
override fun onResume() {
super.onResume()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setTurnScreenOn(true)
}
}
}
I am using a phone with android version 9. My minSdkVersion is 21.
What do you think I am doing wrong or am I missing something?
My application gives user an option to schedule daily notifications on a specific time. I use AlarmManager to achieve this behavior.
On a click of a button I execute the following code:
val datetimeToAlarm = Calendar.getInstance(Locale.getDefault())
/*datetimeToAlarm.set(HOUR_OF_DAY, 21)
datetimeToAlarm.set(MINUTE, 0)
datetimeToAlarm.set(SECOND, 0)
datetimeToAlarm.set(MILLISECOND, 0)*/
NotificationHelper.createNotificationChannel(
requireContext(),
NotificationManagerCompat.IMPORTANCE_DEFAULT,
false,
weather,
"Daily weather notifications for ${weather.cityName}"
)
NotificationHelper.scheduleNotification(
requireContext(),
datetimeToAlarm,
weather
)
For testing purposes I do not set the exact time it must fire at just yet.
The two methods of NotificationHelper class that I use above:
fun createNotificationChannel(
context: Context,
importance: Int,
showBadge: Boolean,
data: Weather,
description: String
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Format: $placeId-$cityName
val channelId = "${data.placeId}-${data.cityName}"
val channel = NotificationChannel(channelId, "Weather for ${data.cityName}", importance)
channel.description = description
channel.setShowBadge(showBadge)
val notificationManager = context.getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(channel)
Log.v("Notifications", "Created channel $channelId")
}
}
fun scheduleNotification(context: Context, timeOfNotification: Calendar, data: Weather) {
val intent = Intent(context, AlarmReceiver::class.java)
intent.putExtra(EXTRA_TITLE, "Weather for ${data.cityName}")
intent.putExtra(EXTRA_TEXT, "Temperature: ${data.daily[0].temperature.min.roundToInt()}°/${data.daily[0].temperature.max.roundToInt()}°")
intent.putExtra(EXTRA_ID, data.id)
intent.putExtra(EXTRA_PLACE_ID, data.placeId)
intent.putExtra(EXTRA_CITY_NAME, data.cityName)
val pending =
PendingIntent.getBroadcast(context, data.id, intent, PendingIntent.FLAG_UPDATE_CURRENT)
// Schedule notification
val manager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val repeatInterval: Long = 1000 * 60 * 15 // 15 minutes
manager.setRepeating(AlarmManager.RTC_WAKEUP, timeOfNotification.timeInMillis, repeatInterval, pending)
// start time is in AM/PM format
Log.v("Notifications", "Scheduled notification id ${data.id} with start at " +
"${SimpleDateFormat("yyyy-MM-dd hh:mm:ss", Locale.getDefault()).format(timeOfNotification.timeInMillis)} and interval " +
SimpleDateFormat("MM-dd hh:mm:ss", Locale.getDefault()).format(repeatInterval))
}
Again, for testing purposes I set repeating interval to 15 minutes just to see if it works.
In my BroadcastReciever's onReceive method I just call createNotification of my NotificationHelper class
override fun onReceive(context: Context, intent: Intent) {
// Deliver the notification.
Log.v("Notifications", "Receiver received call")
if (intent.extras == null) return
val title = intent.getStringExtra(NotificationHelper.EXTRA_TITLE)!!
val text = intent.getStringExtra(NotificationHelper.EXTRA_TEXT)!!
val id = intent.getIntExtra(NotificationHelper.EXTRA_ID, -1)
val placeId = intent.getStringExtra(NotificationHelper.EXTRA_PLACE_ID)
val cityName = intent.getStringExtra(NotificationHelper.EXTRA_CITY_NAME)
NotificationHelper.createNotification(
context,
title,
text,
"",
"$placeId-$cityName",
id
)
}
fun createNotification(
context: Context,
title: String,
message: String,
bigText: String,
channelId: String,
id: Int,
autoCancel: Boolean = true
) {
Log.v("Notifications", "Fired notification creation with following parameters: $title, $message, $channelId, $id")
val notificationBuilder = NotificationCompat.Builder(context, channelId).apply {
setSmallIcon(R.drawable.ic_launcher_foreground)
setContentTitle(title)
setContentText(message)
setStyle(NotificationCompat.BigTextStyle().bigText(bigText))
priority = NotificationCompat.PRIORITY_DEFAULT
setAutoCancel(autoCancel)
val intent = Intent(context, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
setContentIntent(pendingIntent)
}
val notificationManager = NotificationManagerCompat.from(context)
notificationManager.notify(id, notificationBuilder.build())
}
And of course, my receiver is registered in the manifest:
<receiver
android:name=".utils.notifications.AlarmReceiver"
android:enabled="true"
android:exported="false"/>
When I launch my app on an emulator API 30 and click the button, I get the following logs:
2021-03-06 20:13:11.615 11229-11229/com.calamity.weather V/Notifications: Created channel location_place-Mountain View
2021-03-06 20:13:11.626 11229-11229/com.calamity.weather V/Notifications: Scheduled notification id 1 with start at 2021-03-06 08:13:11 and interval 01-01 03:15:00
2021-03-06 20:13:16.624 11229-11229/com.calamity.weather V/Notifications: Receiver received call
2021-03-06 20:13:16.624 11229-11229/com.calamity.weather V/Notifications: Fired notification creation with following parameters: Weather for Mountain View, Temperature: 9°/15°, location_place-Mountain View, 1
My notification is shown right away, and if I go and close my app, it will be delivered again after 15 minutes.
However, if I launch it on a real device (Xiaomi Redmi Note 9, MIUI Global 12.0.4, API 29), on a click of a button my logs are:
2021-03-06 20:16:50.945 19673-19673/com.calamity.weather V/Notifications: Created channel ChIJiQHsW0m3j4ARm69rRkrUF3w-Mountain View
2021-03-06 20:16:50.951 19673-19673/com.calamity.weather V/Notifications: Scheduled notification id 2 with start at 2021-03-06 08:16:50 and interval 01-01 03:15:00
As you can see, alarm is not fired even if my app is still in the foreground, not to mention if I swipe it out of recent apps list. So the questions are:
Why doesn't it fire even with the app still running?
How do I make it behave how it's supposed to and deliver my notifications even with closed app?
I did my research and found out that Chinese ROMs aggressively restrict Services and Jobs, but it seems like AlarmManager should work regardless.
Hı Calamity I encountered same problem yesterday. Also I am using Redmi Note 9. After searching all net, I found that setRepeating and setInexactRepeating methods doesnt work. if you change manager.setRepeating(AlarmManager.RTC_WAKEUP, timeOfNotification.timeInMillis, repeatInterval, pending) to manager.setExact(AlarmManager.RTC_WAKEUP, timeOfNotification.timeInMillis, pending) you will see that your codes work. to work setRepeating and setInexactRepeating methods, you need to disable Battery optimization for your app after that open your app you will see that those methods will work. Also here my asked question android BroadcastReceiver doesn't initiliaze
Use-case: I want my app to announce the current time ("the current time is $hour $minute") at 5 minute intervals. The idea is to help me stay on time while getting ready in the morning, so it needs to fire at the exact time, every 5 minutes, without fail, even with the screen turned off.
Problem: Since Android alarmManager's SetRepeating is no longer exact, I am using SetExactandAllowWhileIdle. I can't figure out how to set a new alarm within my broadcast receiver to make it repeating. Is it possible to set an alarm within a running alarm class in Kotlin? The only examples I can find are in Java.
I've also read that Android limits alarms, so even with SetExactandAllowWhileIdle you are limited to one alarm every 1-15 minutes, so it's possible this may still not work. Is there a better way to do all this that I am missing?
class MainActivity : AppCompatActivity(), TextToSpeech.OnInitListener {
var isRunning: Boolean = false
var tts: TextToSpeech? = null
private lateinit var alarmMgr: AlarmManager
private lateinit var pendingIntent: PendingIntent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var alarmMgr = getSystemService(Context.ALARM_SERVICE) as AlarmManager
var intent = Intent(this, MyAlarm::class.java)
var pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0)
button.setOnClickListener {
if (!isRunning) {
button.text = getString(R.string.stop)
button.setBackgroundColor(resources.getColor(R.color.colorRed))
isRunning = true
val calendar = Calendar.getInstance()
calendar.set(
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH),
calendar.get(HOUR_OF_DAY),
calendar.get(MINUTE),
0
)
startAnnouncing(calendar)
} //end if !isRunning
else if (isRunning) {
button.text = getString(R.string.start)
button.setBackgroundColor(resources.getColor(R.color.colorGreen))
isRunning = false
alarmMgr.cancel(pendingIntent)
Toast.makeText(this, "Announcing stopped!", Toast.LENGTH_SHORT).show()
} //end if isRunning
} //end button OnClickListener
tts = TextToSpeech(this, this)
} //end onCreate
private fun startAnnouncing(calendar: Calendar) {
var alarmMgr = getSystemService(Context.ALARM_SERVICE) as AlarmManager
var intent = Intent(this, MyAlarm::class.java)
var pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0)
alarmMgr.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis + 1000*60*1,
pendingIntent
)
Toast.makeText(this, "Announcing started!", Toast.LENGTH_SHORT).show()
} //end startAnnouncing
class MyAlarm : BroadcastReceiver() {
override fun onReceive(
context: Context,
intent: Intent
) {
val ttsService = Intent(context, TTS::class.java)
context.startService(ttsService)
val calendar = Calendar.getInstance()
calendar.set(
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH),
calendar.get(HOUR_OF_DAY),
calendar.get(MINUTE),
0
)
//THIS is where I'd like to call a new alarm to make this a repeating alarm cycle
} //end onReceive
} //end MyAlarm
override fun onInit(status: Int) {
if (status == TextToSpeech.SUCCESS) {
// set US English as language for tts
val result = tts!!.setLanguage(Locale.US)
}
} //end onInit
public override fun onDestroy() {
// Shutdown TTS
if (tts != null) {
tts!!.stop()
tts!!.shutdown()
}
super.onDestroy()
//alarmMgr.cancel(pendingIntent)
} //end onDestroy
} //end MainActivity
I've also read that Android limits alarms, so even with SetExactandAllowWhileIdle you are limited to one alarm every 1-15 minutes
This is correct, according the the documentation "Under normal system operation, it will not dispatch these alarms more than about every minute (at which point every such pending alarm is dispatched); when in low-power idle modes this duration may be significantly longer, such as 15 minutes"
You may be able to get away with using setExactandAllowWhileIdle, but I don't recall if there is documentation on how long it takes before the device goes into low power/doze mode. Also, it might be even more likely to work how you want if you add the app to the battery optimization exception list. See these docs for Google's suggestions on dealing with doze/app standby.
Another option appears to be setAlarmClock (docs) but I recommend against this as the intended use of this is for creating an alarm clock style application
Background
Android Q seems to have plenty of new restrictions, but alarms shouldn't be one of them:
https://developer.android.com/guide/components/activities/background-starts
The problem
It seems that old code that I made for setting an alarm, which worked fine on P, can't work well anymore:
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var manager: AlarmManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
manager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
button.setOnClickListener {
Log.d("AppLog", "alarm set")
Toast.makeText(this, "alarm set", Toast.LENGTH_SHORT).show()
val timeToTrigger = System.currentTimeMillis() + 10 * 1000
setAlarm(this, timeToTrigger, 1)
}
}
companion object {
fun setAlarm(context: Context, timeToTrigger: Long, requestId: Int) {
val manager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val pendingIntent = PendingIntent.getBroadcast(context, requestId, Intent(context, AlarmReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
when {
VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP -> manager.setAlarmClock(AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)
VERSION.SDK_INT >= VERSION_CODES.KITKAT -> manager.setExact(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
else -> manager.set(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
}
}
}
}
The receiver does get the Intent, but when it tries to open the Activity, sometimes nothing occurs:
AlarmReceiver.kt
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("AppLog", "AlarmReceiver onReceive")
context.startActivity(Intent(context, Main2Activity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}
}
Seeing this as a bug, I reported here (including sample code)
What I've tried
I tried to find what's new on Q, to see what could cause it, and I couldn't find it.
I also tried (if you look at the code) to directly open the Activity instead of via a BroadcastReceiver.
And, I tried to set the BroadcastReceiver to run on a different process.
All of those didn't help.
What I have found is that while some alarm clock apps fail to work properly (such as Timely), some apps work just fine (such as "Alarm Clock Xtreme").
The questions
On Android Q, is there an official way to let alarms work correctly? To open an Activity that will be shown to the user, exactly as an alarm clock app should?
What's wrong in the code I've made? How come it works on P but not always on Q?
EDIT: OK after being adviced to have a notification shown while I start the Activity, and also use FullScreenIntent, I got something to work, but it's only working when the screen is turned off. When the screen is turned on, it only shows the notification, which is a bad thing because the whole point is to have an alarm being shown to the user, and some users (like me) don't want to have heads-up-notification for alarms, popping out in the middle of something and not pausing anything. I hope someone can help with this, as this used to be a very easy thing to do, and now it got way too complex...
Here's the current code (available here) :
NotificationId
object NotificationId {
const val ALARM_TRIGGERED = 1
#JvmStatic
private var hasInitialized = false
#UiThread
#JvmStatic
fun initNotificationsChannels(context: Context) {
if (hasInitialized || Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
return
hasInitialized = true
val channelsToUpdateOrAdd = HashMap<String, NotificationChannel>()
val channel = NotificationChannel(context.getString(R.string.channel_id__alarm_triggered), context.getString(R.string.channel_name__alarm_triggered), NotificationManager.IMPORTANCE_HIGH)
channel.description = context.getString(R.string.channel_description__alarm_triggered)
channel.enableLights(true)
channel.setSound(null, null)
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
channel.enableVibration(false)
channel.setShowBadge(false)
channelsToUpdateOrAdd[channel.id] = channel
//
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val existingChannels = notificationManager.notificationChannels
if (existingChannels != null)
for (existingChannel in existingChannels) {
// The importance of an existing channel will only be changed if the new importance is lower than the current value and the user has not altered any settings on this channel.
// The group an existing channel will only be changed if the channel does not already belong to a group. All other fields are ignored for channels that already exist.
val channelToUpdateOrAdd = channelsToUpdateOrAdd[existingChannel.id]
if (channelToUpdateOrAdd == null) //|| channelToUpdateOrAdd.importance > existingChannel.importance || (existingChannel.group != null && channelToUpdateOrAdd.group != existingChannel.group))
notificationManager.deleteNotificationChannel(existingChannel.id)
}
for (notificationChannel in channelsToUpdateOrAdd.values) {
notificationManager.createNotificationChannel(notificationChannel)
}
}
}
MyService.kt
class MyService : Service() {
override fun onBind(p0: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("AppLog", "MyService onStartCommand")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationId.initNotificationsChannels(this)
val builder = NotificationCompat.Builder(this, getString(R.string.channel_id__alarm_triggered)).setSmallIcon(android.R.drawable.sym_def_app_icon) //
.setPriority(NotificationCompat.PRIORITY_HIGH).setCategory(NotificationCompat.CATEGORY_ALARM)
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
builder.setShowWhen(false)
builder.setContentText("Alarm is triggered!")
builder.setContentTitle("Alarm!!!")
val fullScreenIntent = Intent(this, Main2Activity::class.java)
val fullScreenPendingIntent = PendingIntent.getActivity(this, 0,
fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT)
builder.setFullScreenIntent(fullScreenPendingIntent, true)
startForeground(NotificationId.ALARM_TRIGGERED, builder.build())
startActivity(Intent(this, Main2Activity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
Handler().postDelayed({
stopForeground(true)
stopSelf()
}, 2000L)
} else {
startActivity(Intent(this, Main2Activity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}
return super.onStartCommand(intent, flags, startId)
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var manager: AlarmManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
manager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
button.setOnClickListener {
Log.d("AppLog", "alarm set")
Toast.makeText(this, "alarm set", Toast.LENGTH_SHORT).show()
val timeToTrigger = System.currentTimeMillis() + 10 * 1000
setAlarm(this, timeToTrigger, 1)
}
}
companion object {
fun setAlarm(context: Context, timeToTrigger: Long, requestId: Int) {
val manager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val pendingIntent = PendingIntent.getBroadcast(context, requestId, Intent(context, AlarmReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
// val pendingIntent = PendingIntent.getBroadcast(context, requestId, Intent(context, Main2Activity::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
// val pendingIntent = PendingIntent.getService(context, requestId, Intent(context, MyService::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
when {
VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP -> manager.setAlarmClock(AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)
VERSION.SDK_INT >= VERSION_CODES.KITKAT -> manager.setExact(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
else -> manager.set(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
}
}
}
}
AlarmReceiver.kt
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("AppLog", "AlarmReceiver onReceive")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(Intent(context, MyService::class.java))
} else context.startService(Intent(context, MyService::class.java))
}
}
What's wrong in the code I've made? How come it works on P but not always on Q?
You are attempting to start an activity from the background. That is banned on Android 10+ for the most part.
According to the docs, alarms shouldn't be harmed.
From the material that you quoted, with emphasis added: "The app receives a notification PendingIntent from the system". You are not using notifications. And, therefore, this exception does not apply.
On Android Q, is there an official way to let alarms work correctly? To open an Activity that will be shown to the user, exactly as an alarm clock app should?
Use a notification with a full-screen Intent, as is covered in the documentation. If the screen is locked, your activity will be displayed when the notification is raised. If the screen is unlocked, a high-priority ("heads up") notification will be displayed instead. In other words:
If the device is not being used, you get what you want
If the device is probably being used, the user find out about the event without your taking over the screen, so you do not interfere with whatever the user is doing (e.g., relying on a navigation app while driving)