Make AlarmManager trigger service to update db - android

So i'm trying to run a service that sends certain room data to a server every midnight(roughly) and deletes it from the local db. Reading the android docs i came to the conclusion that the best approach currently was to setup an alarm that triggers at midnight and starts a Service that executes the syncing). While i'm not entirely sure of what type of Service i should i use my files look like this:
MainActivity.kt
alarmMgr = applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager?
alarmIntent = Intent(this, SendTakesReceiver::class.java).let { intent ->
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
// Set the alarm to send takes at approximately 00:00 a.m.
val calendar: Calendar = Calendar.getInstance().apply {
timeInMillis = System.currentTimeMillis()
set(Calendar.HOUR_OF_DAY, 0)
}
// With setInexactRepeating(), you have to use one of the AlarmManager interval
// constants--in this case, AlarmManager.INTERVAL_DAY.
alarmMgr?.setInexactRepeating(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
AlarmManager.INTERVAL_DAY,
alarmIntent
)
SendtakesReceiver.kt
class SendTakesReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
//these following logs are always shown
Log.d("TAKES INTENT", intent.toString())
if (intent != null) {
context?.run {
Log.d("TAKES SERVICE", intent.toString())
startService(intent)
}
}
}
}
SendTakesService.kt
class SendTakesService : Service() {
val sp: SharedPreferences = getSharedPreferences("login", Context.MODE_PRIVATE)
private var prescriptionTakeDao =
AppDatabase.getDatabase(applicationContext).prescriptionTakeDao()
override fun onCreate() {
super.onCreate()
Log.d("TakesService", "onCreate()")
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
And finally in my manifest file i have (inside the application tag):
<receiver
android:name=".src.ui.prescriptions.SendTakesReceiver"
android:enabled="true" />
<service
android:name="com.glik.glik.src.services.SendTakesService"
android:enabled="true"
android:process=":SendTakes">
</service>
But the problem is that none of the service Classes(Service, IntentService, JobIntentService) that i've tried using are calling their main method. For example here the onCreate() log is never shown. I've seen countless of similar questions regarding services not being started but i can't seem to pinpoint the exact mistake that i am making here that causes the service to not be started. If any one could help me find it or maybe recommend a more elegant approach to do the db sync i would be really grateful. Thanks in advance.

Depending on the OEM of the phone you are on, you will face various challenges with this approach. See https://dontkillmyapp.com/
You should use WorkManager in order to decrease battery usage and to avoid having to manually handle many corner cases: https://developer.android.com/topic/libraries/architecture/workmanager/basics

Related

Alarm manager not triggered when using WIFI in redmi Note 8?

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

After ACTION_BOOT_COMPLETED, why is AlarmManager running right away?

BootReceiver
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
val array = MyDatabase(context).getAllAlarm()
val receiver = Intent(context, BootReceiver::class.java)
receiver.action = "ALARM_SETTING"
val trigger = Calendar.getInstance()
for (i in array) {
trigger.time = SimpleDateFormat("yyyy/MM/dd hh:mm", Locale.getDefault()).parse(i.time)!!
receiver.putExtra("trigger", trigger.timeInMillis)
AlarmUtil.setAlarm(context, receiver, i.id, trigger.timeInMillis)
}
} else (intent.action == "ALARM_SETTING") {
val trigger = intent.getLongExtra("trigger", 0)
NotificationUtil.sendNotification(context, name, id, trigger)
}
}
AlarmUtil
class AlarmUtil {
companion object {
fun setAlarm(context: Context, intent: Intent, id: Int, calendar: Long) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val pendingIntent = PendingIntent.getBroadcast(context.applicationContext, id, intent, PendingIntent.FLAG_UPDATE_CURRENT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar, pendingIntent)
} else {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar, pendingIntent)
}
}
}
}
As you can see, when the mobile phone reboots, this code brings the alarm array stored in SQLiteDatabase and makes the alarm sound. If you look at setAlarm() in AlarmUtil, I wrote a code that makes it cry at the time specified in AlarmManager. The problem is that all the alarms stored in the array will be cleared at once regardless of the time specified in the alarm array (I implemented it as Notification). Isn't it normal to cry at the calendar time you put in the second argument of setExactAndAllowWhileIdle or setExact? Why do all the alarms cry at once as soon as the phone turns on again?
[EDIT]
The following picture is a situation where the phone is turned off and on after setting the alarm to ring at 02:22. The system time is 2020/11/02 02:21, but the trigger time I set to ring is 2020/11/02 02:22. In other words, the alarm goes off as soon as the time runs out. I want to take a log and show it, but the moment I turn off my phone, the app dies, so I couldn't see the log.
Hi you are passing older time as trigger time.
You should pass time in the future.
Check db time if it is less than now() than add a day

Android AlarmManager does not trigger when app is killed

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.

How to monitor geofences in background on Oreo?

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
}
}

AlarmManager is not triggering Intent

I want to use AlarmService to trigger a notification at a certain time. Think of it as something similar as a calendar app that is showing a reminder as notification for an upcoming event.
The code to schedule the intent (via alarm service) looks like this:
fun scheduleNotification(event : CalendarEvent)
val startTime : Instant = event.startTime
val intent = buildPendingIntent(event)
val notificationTime = startTime.minusMillis(TimeUnit.MINUTES.toMillis(10)) // 10 Minutes earlier
if (Build.VERSION.SDK_INT < 23) {
alarmService().setExact(AlarmManager.RTC_WAKEUP,
notificationTime.toEpochMilli(), intent)
} else {
alarmService().setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
notificationTime.toEpochMilli(), intent)
}
}
fun buildPendingIntent(event : CalendarEvent){
val intent = Intent(context, NotificationReceiver::class.java)
intent.putExtra(EVENT_ID, event.id)
return PendingIntent.getBroadcast(context, 0, realIntent, 0)
}
class NotificationReceiver : WakefulBroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// build and display the notification
}
}
So 1 of 10 times the notification is tirggered and shown (by NotificationReceiver) correctly, also at the desired time. So I think the scheduling part is working properly.
Which leads me to another question: Whenever the user creates a new CalendarEvent the method scheduleNotification(newEvent) is called. It seems to me that AlarmService is internally updating the PendingIntents of existing and that this is the reason why 1 of 10 (usually the first scheduled PendingIntent) is triggered, but the others are not.
How many alarms can I schedule for an Android App? Do you spot any other issue with my code?

Categories

Resources