I want to make sure AlarmManager is triggered even when my app is manually closed, the same way a messaging app still displayed messages even when closed (swipe or press the "X"). This is my code:
class MainActivity : ComponentActivity() {
private lateinit var alarmManager: AlarmManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val mp = MediaPlayer.create(context,R.raw.music)
mp.start()
}
}
this.registerReceiver(receiver, IntentFilter("SET_ALARM"))
alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager
var calendar = Calendar.getInstance()
calendar.add(Calendar.MINUTE,1)
val alarmIntent = Intent()
alarmIntent.action = "SET_ALARM"
var pendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, 0)
Column( Modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally) {
Button(onClick = { alarmManager.setAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent)}) {Text("play song in 1 minute")
}
}
}
}
}
It works fine when the app is open, but not when I close manually it . What do I need to add?
(It is not a battery management problem, as it does not work in the emulator either)
In your AndroidManifest.xml, make sure you have a receiver definition within the tags, something like:
<receiver android:name="AlarmReceiver"
You said in a comment that you're registering your broadcast receiver through an activity, and not the manifest. That will only receive broadcasts while your app is actually running - specifically as long as the Context you used to register the receiver is valid. If you used an Activity as that context, once that activity is destroyed the receiver won't get broadcasts (even if the app is running, e.g. with another activity). Even if you use the application context, once the app is destroyed, that's cleared.
From the docs:
Manifest-declared receivers
If you declare a broadcast receiver in your manifest, the system launches your app (if the app is not already running) when the broadcast is sent.
Context-registered receivers
Context-registered receivers receive broadcasts as long as their registering context is valid. For an example, if you register within an Activity context, you receive broadcasts as long as the activity is not destroyed. If you register with the Application context, you receive broadcasts as long as the app is running.
If you need your app to launch in the background (which you do if you're relying on alarms, which implies your app isn't in the foreground and could be destroyed when the alarm runs) you need to register the receiver in your manifest, so the system knows it's something your app handles
Related
I have implemented alarm manager to trigger every 5 minutes in my application. But when using it on my Redmi note 8 device, alarm manager not triggered in wifi enabled mode and at the same time it works for mobile data enabled mode. For other device's it works fine in both wifi and mobile data.
I know that wifi or mobile-data is not related to alarm manager triggering process. But, I'm facing this weird issue.
Could anyone help me out ?
This is my alarm triggering code.
private var alarmManager: AlarmManager? = null
private lateinit var pendingIntent: PendingIntent
override fun startAlarm(url: String, status: String) {
alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(this, AlarmReceiver::class.java).apply {
action = Constants.ACTION_ALARM_MANAGER
putExtra(KeyConstants.STATUS, status)
putExtra(KeyConstants.URL, url)
}
pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val timeInterval = TimeUnit.MINUTES.toMillis(5)
alarmManager?.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), timeInterval, pendingIntent)
}
override fun cancelAlarm() {
alarmManager?.cancel(pendingIntent)
}
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// Is triggered when alarm goes off, i.e. receiving a system broadcast
if (intent.action == Constants.ACTION_ALARM_MANAGER) {
val tripStatus = intent.getStringExtra(KeyConstants.STATUS)
val directionUrl = intent.getStringExtra(KeyConstants.URL)
// my logic here
}
}
}
and also added receiver in Manifest.xml
<receiver android:name=".AlarmReceiver" />
Every OEM has made it worse to use Alarm manager, mostly it's their battery saving app which kills the app in the background thus no triggering of alarm events.
Android's system makes sure that no alarm should trigger very often, for saving battery and minimizing wakeups, android shifts these alarms for future for this.
As your alarm goes off every 5 minutes, most probably android is shifting your alarm too.
You can check all the scheduled alarm events of the device using following command adb shell dumpsys alarm
I have a ForegroundService that takes a little while to run. In the meantime the user may move to the app to the background while it is running, such as pressing the home button or enter a different app. When the ForegroundService reaches a "trigger", it attempts to start a new intent. This works great if the app is in the foreground, but doesn't work if it is in the background. The goal is when a "trigger" is reached, the app will come back to the foreground so the user can provide needed input in the new intent. In an attempt to do so I have added the flags FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_SINGLE_TOP as it looked like they would accomplish this. But the app is not brought back to the foreground. Is this something that can be done?
Below is the relevant parts of code in question:
MainActivity.kt
...
private fun startService() {
val serviceIntent = Intent(this, ForegroundService::class.java)
ContextCompat.startForegroundService(this, serviceIntent)
}
...
ForegroundService.kt
...
//When trigger is reached, start the new activity and bring app to foreground if it was moved to background
private fun startNewActivity() {
//First two lines are to shut down the foreground service since it has reached a trigger and is complete.
super.onDestroy()
stopSelf()
//Start the new intent
val startNewIntent = Intent(this, NewIntent:: class.java)
startNewIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP //Assumption was these flags would bring app to foreground
startActivity(startNewIntent)
}
...
In order to make the intent be registered into the system, you need to use a PendingIntent instead of a normal Intent; when the Foreground Service is over (when your trigger is fired), you can send this pending intent using send() method
ForegroundService.kt
...
//When trigger is reached, start the new activity and bring app to foreground if it was moved to background
private fun startNewActivity() {
//First two lines are to shut down the foreground service since it has reached a trigger and is complete.
super.onDestroy()
stopSelf()
//Start the new intent
val startNewIntent = Intent(this, NewIntent:: class.java)
val pendingIntent = PendingIntent.getActivity(
this, 0, startNewIntent,
PendingIntent.FLAG_UPDATE_CURRENT
or PendingIntent.FLAG_ONE_SHOT
)
try {
pendingIntent.send() // this should start `NewIntent` activity
} catch (e: CanceledException) {
e.printStackTrace()
}
}
...
I have a requirement to perform a task at exactly every 5 minutes. I have considered multiple options and have attempted to implement this using the AlarmManager class to trigger the task. However I cannot get the alarm to trigger when the application has been killed.
The alarm seems to work flawlessly when the app is open, or running in the background, but as soon as I exit the app, it seems to completely stop.
My implementation is to use the setExactAndAllowWhileIdle() function and to handle the repeating of this myself. The initial alarm is triggered after 5 seconds, then every 5 minutes after this.
I have tested this with 5 and 10 minute increments, but again this never runs when the app is closed.
Please take a look at my implementation:
My Activity.kt:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_landing)
initAlarm()
}
private fun initAlarm() {
val alarm = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(this, AlarmReceiver::class.java).apply { action = "MY_ALARM" }
val sender = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val ALARM_DELAY_IN_SECOND = 5
val alarmTimeAtUTC = System.currentTimeMillis() + ALARM_DELAY_IN_SECOND * 1_000
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.(TAG, "initAlarm() 23+ - $alarmTimeAtUTC")
alarm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, sender)
} else {
alarm.setExact(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, sender)
}
}
AlarmReceiver.kt:
class AlarmReceiver : BroadcastReceiver() {
companion object {
private val TAG = AlarmReceiver::class.java.simpleName
}
override fun onReceive(context: Context?, intent: Intent?) {
Log.d(TAG, "onReceive()")
if (intent?.action == "MY_ALARM") {
Log.d(TAG, "onReceive() - starting service")
context?.startService(Intent(context, MyService::class.java))
initAlarm(context)
}
}
private fun initAlarm(context: Context?) {
val alarm = context?.applicationContext?.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, AlarmReceiver::class.java).apply { action = "MY_ALARM" }
val sender = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val ALARM_DELAY_IN_SECOND = 600
val alarmTimeAtUTC = System.currentTimeMillis() + ALARM_DELAY_IN_SECOND * 1_000
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.d(TAG, "initAlarm() 23+ - $alarmTimeAtUTC")
alarm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, sender)
} else {
alarm.setExact(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, sender)
}
}
}
MyService.kt:
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate()")
doMyTask()
}
private fun doMyTask() {
job = CoroutineScope(Dispatchers.IO).launch {
// Perform task here and once complete stop service
stopSelf()
}
}
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy()")
job?.cancel()
job = null
}
The problem is that the code is calling startService() while the app is in the background.
This was allowed prior to Android 8, but now is subject to the following restrictions:
While an app is in the foreground, it can create and run both
foreground and background services freely. When an app goes into the
background, it has a window of several minutes in which it is still
allowed to create and use services. At the end of that window, the app
is considered to be idle. At this time, the system stops the app's
background services, just as if the app had called the services'
Service.stopSelf() methods.
Background Execution Restrictions
The service created in the sample above is a "Background Service" in the language of the Android docs because it does not call startForeground to post a Notification to make the user aware that it is running.
To start a "Foreground service" from an alarm while the app is in the background, ContextCompat.startForegroundSerivce must be used. It will call startForegroundService on Android 8 and above and startService on older devices.
In the service, you will need to call startForeground to post an ongoing service Notification to make the user aware that the service is running. If the Notification is not posted, the service will be killed after 5 seconds.
It would also worth considering whether the task can be accomplished via WorkManager or with Firebase Cloud Messaging.
Lastly, you probably need to inform your client that running a task "exactly every 5 minutes" is not possible on modern Android devices. I have not looked at the Doze implementation recently, but in the past have observed routine delays of up 10 minutes during maintenance windows when using setExactAndAllowWhileIdle. But the delays can be longer under certain circumstances.
Regarding onReceive not being called in the BroadcastReceiver:
Disable Battery Optimisations
Do not kill my app
Lastly, you could try passing in a unique requestCode in getBroadcast instead of passing 0 each time.
I'm actually working on an app that should post a notification 5 days in the future.
Using AlarmManager, I send a PendingIntent to my Receiver class.
Everything works fine until I force close my app. In this case, the notification doesn't appear.
So my question:
What happens to this PendingIntent, which was fired and did not reach its target?
When my app is finally restarted, can I check for PendingIntents, that did not reach its target?
EDIT 1:
These are the essential parts of my Broadcast Receiver:
override fun onReceive(context: Context?, intent: Intent?) {
if (context != null && intent?.action != null) {
when (intent.action) {
INTENT_ACTION_BOOT_COMPLETED -> handleDeviceBoot()
INTENT_ACTION_REMINDER -> handleReminder(context, intent.getLongExtra(EXTRA_ITEM_ID, -1))
}
}
}
private suspend fun schedule(context: Context, itemId: Long, fireDate: LocalDateTime) = withContext(Dispatchers.IO) {
AlarmManagerCompat.setAndAllowWhileIdle(
getAlarmManager(context),
AlarmManager.RTC,
fireDate.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(),
makePendingIntent(context, itemId)
)
with(AppDatabase.get(context).reminderDao()) {
val oldReminder = getItemReminder(itemId)
if (oldReminder == null) {
insert(Reminder(itemId = itemId, fireDate = fireDate))
} else {
update(Reminder(id = oldReminder.id, itemId = itemId, fireDate = fireDate))
}
}
}
private suspend fun cancel(context: Context, itemId: Long) = withContext(Dispatchers.IO) {
val reminderDao = AppDatabase.get(context).reminderDao()
val reminder = reminderDao.getItemReminder(itemId)
reminder?.let {
getAlarmManager(context).cancel(makePendingIntent(context, itemId))
reminderDao.delete(it)
}
}
private fun getAlarmManager(context: Context) = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
private fun makePendingIntent(context: Context, itemId: Long): PendingIntent {
val alarmIntent = Intent(context, ReminderManager::class.java).apply {
action = INTENT_ACTION_REMINDER
putExtra(EXTRA_ITEM_ID, itemId)
}
return PendingIntent.getBroadcast(context, itemId.toInt(), alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
As defined in Official Android Documentation
A PendingIntent itself is simply a reference to a token maintained by the system describing the original data used to retrieve it. This means that, even if its owning application's process is killed, the PendingIntent itself will remain usable from other processes that have been given it. If the creating application later re-retrieves the same kind of PendingIntent (same operation, same Intent action, data, categories, and components, and same flags), it will receive a PendingIntent representing the same token if that is still valid, and can thus call cancel() to remove it.
Revisit your code to check if there is anything else that would be causing this issue.
When you "force close" an application, the application gets set to the "stopped state". In the "stopped state" your application will NOT be automatically started by Android until the user manually restarts the application. This means that if you "force close" your app, your app will not receive any broadcast Intents until it is manually restarted by the user.
I expect (although I have not tried it myself), that if you schedule an alarm to go off at time X and before time X you "force close" the app, when time X happens, the alarm manager will try to send the PendingIntent, however Android will refuse to actually execute the BroadcastReceiver because the app is in the "stopped state". In this case I expect the trigger is lost. Android will not retry or reschedule it.
Basically, when a user "force close"s an app, he is telling Android that he doesn't want that app to run anymore, including any background processes that the app might have, or want to start in the future.
The answer is short: Active PendingIntents are cancelled on an application force-stop.
I followed this tutorial: https://developer.android.com/training/location/geofencing and works fine on Android < 8, but in Oreo i have problems due to new OS background limitations.
How can I get geofence transition triggers when app is in background?
I also tried to use a BroadcastReceiver instead of IntentService, but the result is the same.
Pending Intent:
private val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(context, GeofenceBroadcastReceiver::class.java)
intent.action = "com.example.GEOFENCE_TRANSITION"
PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
Register geofence:
geofencingClient.addGeofences(request, geofencePendingIntent).run {
addOnSuccessListener {
Log.d(TAG, "Geofence added")
}
addOnFailureListener {
Log.e(TAG, "Failed to create geofence")
}
}
Broadcast Receiver:
class GeofenceBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {
Log.d(TAG, "onReceive")
}
}
Receiver in Manifest:
<receiver android:name=".GeofenceBroadcastReceiver">
<intent-filter>
<action android:name="com.example.GEOFENCE_TRANSITION"/>
</intent-filter>
</receiver>
Thanks
EDIT: IntentService version
Pending Intent:
private val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(context, GeofenceIntentService::class.java)
PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
Intent Service:
class GeofenceIntentService : IntentService("GeofenceIntentService") {
override fun onHandleIntent(p0: Intent?) {
Log.d(TAG, "onHandleIntent")
}
}
Service in Manifest:
<service android:name=".GeofenceIntentService"/>
You should get an Intent every couple of minutes on Android 8 when your geofence transition is reached in background.
See: https://developer.android.com/training/location/geofencing#java
Handle geofence transitions
When Location Services detects that the user has entered or exited a geofence, it sends out the Intent contained in the PendingIntent you included in the request to add geofences. This Intent is received by a service like GeofenceTransitionsIntentService, which obtains the geofencing event from the intent, determines the type of Geofence transition(s), and determines which of the defined geofences was triggered. It then sends a notification as the output.
Note: On Android 8.0 (API level 26) and higher, if an app is running in the background while monitoring a geofence, then the device responds to geofencing events every couple of minutes. To learn how to adapt your app to these response limits, see Background Location Limits.
Once the geofence service is registered it´s still there and you have nothing else to do and only check your IntentService for the specific PendingIntent, exclude when the device is rebooted you need to reregister your geofence service.
Also check: https://developer.android.com/about/versions/oreo/background-location-limits
i use dexter library for permission geofence and this work for android 8 9 10 and above you must add background permission
Dexter.withActivity(this#Activity_Map)
.withPermissions(
Manifest.permission.ACCESS_COARSE_LOCATION
,Manifest.permission.ACCESS_FINE_LOCATION
,Manifest.permission.ACCESS_BACKGROUND_LOCATION
)
.withListener(object: MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let {
if(report.areAllPermissionsGranted()){
//put your code here
}
}