Notifications per day of week - android

I have never scheduled notifications before and need help.
My app have Notification entity which contains:
#Parcelize
#Entity
data class Notification(
#PrimaryKey(autoGenerate = true)
val id: Int = 0,
var time: Date,
var daysOfWeek: Set<DayOfWeek>,
var enabled: Boolean = false
) : Parcelable
I wish to let this notification trigger selected days of week in specified time. I also have several methods to set notifications:
private fun codeForNotification(notification: Notification, dayOfWeek: DayOfWeek) =
1000 + notification.id * 10 + dayOfWeek.ordinal
fun checkNotification(notification: Notification, isEnabled: Boolean? = null) = with(notification) {
if ((isEnabled == null && enabled) || isEnabled == true) daysOfWeek.forEach { dow ->
setNotification(time, dow, codeForNotification(this, dow))
} else daysOfWeek.forEach { dow ->
Intent(appContext, NotificationReceiver::class.java).let { intent ->
PendingIntent.getBroadcast(
appContext,
codeForNotification(this, dow),
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}.let(alarmManager::cancel)
}
}
fun setNotification(time: Date, dayOfWeek: DayOfWeek, code: Int) {
c2.time = time
c1.apply {
this.time = Date()
set(Calendar.HOUR_OF_DAY, c2.get(Calendar.HOUR_OF_DAY))
set(Calendar.MINUTE, c2.get(Calendar.MINUTE))
}
c1.set(Calendar.DAY_OF_WEEK, dayOfWeek.calendarValue)
if (c1.timeInMillis < System.currentTimeMillis()) {
c1.add(Calendar.DAY_OF_YEAR, 7)
}
Intent(appContext, NotificationReceiver::class.java).let {
PendingIntent.getBroadcast(appContext, code, it, PendingIntent.FLAG_CANCEL_CURRENT)
}.let {
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP,
c1.timeInMillis,
AlarmManager.INTERVAL_DAY * DayOfWeek.values().size,
it
)
}
}
Notifications are set similarly to the question (using check notification), but they do not fire.
My receiver class:
class NotificationReceiver : BroadcastReceiver() {
companion object {
const val CHANNEL_ID = "CHANNEL"
const val NOTIFY_ID = 1111
}
override fun onReceive(context: Context?, intent: Intent?) = context?.let{
val builder: NotificationCompat.Builder = NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle("Notification")
.setContentText("Hello world")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
val notificationManager =
NotificationManagerCompat.from(context)
notificationManager.notify(NOTIFY_ID, builder.build())
} ?: Unit
}
And manifest have :
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<receiver android:name=".main.notification.NotificationReceiver" />
Please help me!

I think I had the same problem. It helped to set the receiver to "exported" in the manifest. Also you should restrict the receiver to NOTIFY actions:
<receiver android:name=".main.notification.NotificationReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.NOTIFY" />
</intent-filter>
</receiver>

Setting notification channel fixed this issue:
override fun onReceive(context: Context?, intent: Intent?) = context?.run {
val notificationManager: NotificationManager? =
getSystemService(this, NotificationManager::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationManager?.let(::setNotificationChanel)
Notification.Builder(context, CHANNEL_ID)
} else {
Notification.Builder(context)
}.setSmallIcon(R.drawable.ic_access_alarm_24px)
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.notification_description))
.setContentIntent(getActivityIntent(this))
.setAutoCancel(true)
.setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.ic_access_alarm_24px))
.build().let { notification ->
notificationManager?.notify(NOTIFICATION_ID, notification)
}
} ?: Unit
#RequiresApi(Build.VERSION_CODES.O)
private fun setNotificationChanel(notificationManager: NotificationManager) =
NotificationChannel(CHANNEL_ID, CHANNEL_ID, NotificationManager.IMPORTANCE_HIGH).apply {
description = CHANNEL_DESC
enableLights(true)
lightColor = Color.RED
enableVibration(true)
setShowBadge(true)
}.let(notificationManager::createNotificationChannel)

Related

How to create multiple notifications in Kotlin in foreground service

I am working on a parental control app which notify parent multiple times but when I try to create notification with a background service it generates only one 1.
Here is how I do it:
fun createNotification(parent_name: String, notificationText:String, id: Int){
val MchannelId = channelId+id.toString()
if (Build.VERSION.SDK_INT >= 26) {
val channel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel(
MchannelId,
"Channel human readable title",
NotificationManager.IMPORTANCE_DEFAULT
)
} else {
TODO("VERSION.SDK_INT < O")
}
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(
channel
)
}
val notificationIntent = Intent(this, TabbedActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
id, notificationIntent, 0
)
val notification: Notification = NotificationCompat.Builder(this, "$MchannelId")
.setContentTitle("Hi $parent_name")
.setContentText(notificationText)
.setSmallIcon(R.drawable.icon_child)
//.setContentIntent(pendingIntent)
.build()
startForeground(random_number, notification)
}
My Full-Service Class:
const val TAG2 = "Child Service"
class ParentService: Service() {
val db = FirebaseFirestore.getInstance()
private val channelId = "Notification from Service"
var parent_name = userName
override fun onBind(intent: Intent?): IBinder? = null
//OnBind Function Implementation
init {
Log.d(TAG2, "Started Service!")
}
//onCreate Method Implementation
override fun onCreate() {
super.onCreate()
}
//OnStartCommand Override
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Thread{
while (true){
checkStatus()
Thread.sleep(PARENT_CHECK_TIME)
}
}.start()
return START_STICKY
}
private fun checkStatus() {
var listOfNames = ""
var i = 1
val calendar: Calendar = Calendar.getInstance()
var list = ArrayList<String>()
db.collection(LINKED_CHILDS)
.whereEqualTo(USER_PHONE, userPhone)
.get()
.addOnSuccessListener { documents ->
for (document in documents){
val startTime: Long = calendar.getTimeInMillis()
val diff = startTime - (document.data[ACTIVE_STATUS] as Long)
Log.d("TAG", "Time Difference : $diff")
Log.d("TAG", "${document.data[USER_NAME].toString()}")
if (diff> MAX_GAP_TIME){
Log.d("TAG", "Entered IFF")
list.add(document.data[USER_NAME].toString())
}
}
for (name in list){
listOfNames = listOfNames + "$i. Your child $name is not active\n"
i++
createNotification(parent_name, listOfNames, i)
Log.d("TAG Notification ID:", "ID: $i")
}
Log.d("TAG: ", "$listOfNames")
}
}
fun createNotification(parent_name: String, notificationText:String, id: Int){
val MchannelId = channelId+id.toString()
if (Build.VERSION.SDK_INT >= 26) {
val channel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel(
MchannelId,
"Channel human readable title",
NotificationManager.IMPORTANCE_DEFAULT
)
} else {
TODO("VERSION.SDK_INT < O")
}
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(
channel
)
}
val notificationIntent = Intent(this, TabbedActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
id, notificationIntent, 0
)
val notification: Notification = NotificationCompat.Builder(this, "$MchannelId")
.setContentTitle("Hi $parent_name")
.setContentText(notificationText)
.setSmallIcon(R.drawable.icon_child)
//.setContentIntent(pendingIntent)
.build()
startForeground(id, notification)
}
}
Kinldy let me know how I can create multiple Notifications using this background service. Thank You so much in advance!
Kinldy let me know how I can create multiple Notifications using this background service. Thank You so much in advance!
Kinldy let me know how I can create multiple Notifications using this background service. Thank You so much in advance!
If you create a non-persistent notification, it will show your notifications. The permanent notification will be used for your service to run in the background.
#RequiresApi(Build.VERSION_CODES.O)
private fun createNotification() {
val intent = Intent(this, TabbedActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent =
PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val notification = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.icon_child)
.setContentTitle("Hi $parent_name")
.setContentText(notificationText)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
with(NotificationManagerCompat.from(this)) {
notify(notifManagerId, notification.build())
notifManagerId++
}
parmanentNotification()
}
this is a permanent notification will not be lost and destroyed will keep the service running permanently
private fun parmanentNotification() {
val notification=NotificationCompat.Builder(this,channelId)
.setSmallIcon(R.drawable.icon_child)
.setContentTitle("Hi $parent_name")
.setContentText("Application service running in the background")
.build()
startForeground(1,notification)
}
you aren't creating a common Notification in this scenario, you are running a Service, which must have a foreground representation on screen. So Activity visible or sticked, fixed Notification, and you are showing it
Now you can have much Notifications using similar code, but don't show them using startForeground, instead use NotificationManager, preferably compat version
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(uniqueId, notification);
or just like you are using it already when creating channel inside if: (getSystemService(NOTIFICATION_SERVICE) as NotificationManager).notify(...)
foreground-related Notification is sticky and lives as long as Service works in background, they are "tied". other Notifications may be configured to be sticky or swipeable, also should be posted on some own Channel (per child? per action?). Note that if you show yet another sticky Notification then you have to release it by own through code, just killing Service won't dismiss it as it does with foreground-related Notification
some DOC in here, read carefully, all answers are there

Notification works in Activity but not in Service

I am trying to integrate Firebase Cloud Messages into my app. The code I used in the showNotification function is from the Android User Interface Samples. I tried it in the Activity and it worked but am not sure why it's not working in the service. The println is showing the function is getting called and values are coming as expected. Is there anything am missing from the function?
class MessagingService : FirebaseMessagingService() {
override fun onMessageReceived(remoteMessage: RemoteMessage) {
if (remoteMessage.data.isNotEmpty()) {
val message = Gson().fromJson(remoteMessage.data.toString(), Message.Res::class.java).payload!!
showNotification(message.title, message.body, message.count)
}
}
private fun showNotification(title: String?, body: String?, count: Int = 0) {
println("_print::showNotification(title:$title, body:$body, count:$count)")
val mainPendingIntent = PendingIntent.getActivity(
applicationContext, BuildConfig.REQUEST_CODE,
Intent(applicationContext, MainActivity::class.java),
PendingIntent.FLAG_UPDATE_CURRENT
)
val builder = NotificationCompat.Builder(applicationContext, "channel_email_1")
.setContentTitle(title)
.setContentText(body)
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher_round))
.setContentIntent(mainPendingIntent)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setColor(ContextCompat.getColor(applicationContext, R.color.primary))
.setSubText(if (count > 1) "You have $count pending notifications" else title)
.setCategory(Notification.CATEGORY_EMAIL)
.setPriority(1)
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
NotificationManagerCompat.from(applicationContext)
.notify(BuildConfig.NOTIFICATION_ID, builder.build())
}
}
Instead of
val mainPendingIntent = PendingIntent.getActivity(
applicationContext, BuildConfig.REQUEST_CODE,
Intent(applicationContext, MainActivity::class.java),
PendingIntent.FLAG_UPDATE_CURRENT
)
NotificationManagerCompat.from(applicationContext)
.notify(BuildConfig.NOTIFICATION_ID, builder.build())
I use:
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Since android Oreo notification channel is needed.
setupChannels(notificationManager, builder)
// Create pending intent, mention the Activity which needs to be triggered
// when user clicks on notification.
val notificationId = Random.nextInt()
navigateToScreen(builder, notificationId)
val notification = builder.build()
notificationManager.notify(notificationId, notification)
}
private fun setupChannels(notificationManager: NotificationManager,
builder: NotificationCompat.Builder) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channelId = "channel_email_1"
val channel = NotificationChannel(channelId, "title",
NotificationManager.IMPORTANCE_DEFAULT).apply {
description = "body"
// Add other settings.
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
canShowBadge()
setShowBadge(true)
}
notificationManager.createNotificationChannel(channel)
builder.setChannelId(channelId)
}
}
private fun navigateToScreen(builder: NotificationCompat.Builder,
notificationId: Int) {
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this, notificationId, intent,
PendingIntent.FLAG_UPDATE_CURRENT)
builder.setContentIntent(pendingIntent)
}

FCM Service gets killed after i swipe my app from multi task tray

I have an app that triggers incoming calls by FCM data message(priority: high, no notification payload).
When the app is in the foreground or background, the app receives calls.
For the above case, the notification(Incoming Call) is received when in Locked Screen.
But for some reason, when the app is closed or swiped from the multi-task tray, calls are no more received. I think that the service is killed. what can I do?
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
Firebase Messaging class manifest declaration
<service
android:name=".firebase.MyFirebaseMessagingService"
android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
Workflow
When FCM notification is received, a background service is started, then the service changes itself to the foreground service with "Incoming call" notification. I'm also using full-screen intent. The below class is what I use as Service
class IncomingCallNotificationService : Service() {
private val TAG = IncomingCallNotificationService::class.java.simpleName
var isRunning = false
private lateinit var soundUri: Uri
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + applicationContext.packageName + "/" + R.raw.incoming)
val videoCallDetails: VideoNotification?= intent.getParcelableExtra(Constants.VIDEO_CALL_DETAILS)
val notificationId = intent.getIntExtra(Constants.INCOMING_CALL_NOTIFICATION_ID, 0)
val action = intent.action
if (action != null) {
when(action){
ACTION_INCOMING_CALL_NOTIFICATION -> { handleIncomingCall(videoCallDetails!!, notificationId) }
ACTION_ACCEPT -> {
endForeground()
val intent = Intent(this, VideoActivity::class.java)
intent.action = ACTION_INCOMING_CALL_NOTIFICATION
intent.putExtra(Constants.ROOM_CODE, videoCallDetails?.roomCode)
intent.putExtra(Constants.ROOM_NAME, videoCallDetails?.roomName)
intent.putExtra(Constants.ACCESS_TOKEN, videoCallDetails?.accessToken)
intent.putExtra(Constants.CALLER_NAME, videoCallDetails?.userName)
intent.putExtra(
Constants.CALL_RESPONSE_ACTION_KEY,
Constants.INCOMING_CALL_SCREEN
)
intent.putExtra(Constants.CALL_TYPE, Constants.VIDEO_CALL)
intent.putExtra(Constants.NOTIFCATION_ID, notificationId)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.action = ACTION_ACCEPT
startActivity(intent)
}
ACTION_REJECT -> {
endForeground()
val it = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
applicationContext.sendBroadcast(it)
}
}
}
return START_NOT_STICKY
}
override fun onBind(intent: Intent): IBinder? {
return null
}
private fun createNotification(
videoCallDetails: VideoNotification,
notificationId: Int,
channelImportance: Int
): Notification? {
val intent = Intent(this, VideoActivity::class.java)
intent.action = ACTION_INCOMING_CALL_NOTIFICATION
intent.putExtra(Constants.ROOM_CODE, videoCallDetails.roomCode)
intent.putExtra(Constants.ROOM_NAME, videoCallDetails.roomName)
intent.putExtra(Constants.ACCESS_TOKEN, videoCallDetails.accessToken)
intent.putExtra(Constants.CALLER_NAME, videoCallDetails.userName)
intent.putExtra(Constants.CALL_RESPONSE_ACTION_KEY, Constants.INCOMING_CALL_SCREEN)
intent.putExtra(Constants.CALL_TYPE, Constants.VIDEO_CALL)
intent.putExtra(Constants.INCOMING_VIDEO_CALL, true)
intent.putExtra(Constants.NOTIFCATION_ID, notificationId)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
val pendingIntent = PendingIntent.getActivity(
this,
notificationId,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
/*
* Pass the notification id and call sid to use as an identifier to cancel the
* notification later
*/
val extras = Bundle()
extras.putString(Constants.ROOM_CODE, videoCallDetails.roomCode)
extras.putString(Constants.ROOM_NAME, videoCallDetails.roomName)
extras.putString(Constants.ACCESS_TOKEN, videoCallDetails.accessToken)
extras.putString(Constants.CALLER_NAME, videoCallDetails.userName)
extras.putString(Constants.CALL_RESPONSE_ACTION_KEY, Constants.INCOMING_CALL_SCREEN)
extras.putString(Constants.CALL_TYPE, Constants.VIDEO_CALL)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
buildNotification(
videoCallDetails.userName + " is calling.",
pendingIntent,
extras,
videoCallDetails,
notificationId,
createChannel(channelImportance)!!
)
} else {
val builder = NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_call_end_white_24dp)
.setContentTitle(INCOMING_VIDEO_CALL_TEXT)
.setContentText(videoCallDetails.userName + " is calling.")
.setAutoCancel(true)
.setExtras(extras)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
.setTimeoutAfter(MomsezeApplication.notificationTimeOut)
.setGroup("test_app_notification")
.setColor(Notification.DEFAULT_LIGHTS)
.setSound(soundUri)
val notification = builder.build()
notification.flags = Notification.FLAG_INSISTENT
return notification
}
}
#TargetApi(Build.VERSION_CODES.O)
private fun buildNotification(
text: String, pendingIntent: PendingIntent, extras: Bundle,
videoCallDetails: VideoNotification,
notificationId: Int,
channelId: String
): Notification? {
val rejectIntent = Intent(applicationContext, IncomingCallNotificationService::class.java)
rejectIntent.action = Constants.ACTION_REJECT
rejectIntent.putExtra(Constants.VIDEO_CALL_DETAILS, videoCallDetails)
rejectIntent.putExtra(Constants.INCOMING_CALL_NOTIFICATION_ID, notificationId)
val piRejectIntent = PendingIntent.getService(
applicationContext,
0,
rejectIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
val acceptIntent = Intent(applicationContext, IncomingCallNotificationService::class.java)
acceptIntent.action = ACTION_ACCEPT
acceptIntent.putExtra(Constants.VIDEO_CALL_DETAILS, videoCallDetails)
acceptIntent.putExtra(Constants.INCOMING_CALL_NOTIFICATION_ID, notificationId)
val piAcceptIntent = PendingIntent.getService(
applicationContext,
0,
acceptIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
val builder = NotificationCompat.Builder(applicationContext, channelId)
.setSmallIcon(R.drawable.ic_call_end_white_24dp)
.setContentTitle(INCOMING_VIDEO_CALL_TEXT)
.setContentText(text)
.setCategory(Notification.CATEGORY_CALL)
.setFullScreenIntent(pendingIntent, true)
.setExtras(extras)
.setSound(soundUri)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.addAction(android.R.drawable.ic_menu_delete, getString(R.string.decline), piRejectIntent)
.addAction(android.R.drawable.ic_menu_call, getString(R.string.answer), piAcceptIntent)
.setFullScreenIntent(pendingIntent, true)
.setTimeoutAfter(MomsezeApplication.notificationTimeOut)
val notification = builder.build()
notification.flags = Notification.FLAG_INSISTENT
return notification
}
#TargetApi(Build.VERSION_CODES.O)
private fun createChannel(channelImportance: Int): String? {
var callInviteChannel = NotificationChannel(
Constants.VOICE_CHANNEL_HIGH_IMPORTANCE,
"Primary Voice Channel", NotificationManager.IMPORTANCE_HIGH
)
var channelId = Constants.VOICE_CHANNEL_HIGH_IMPORTANCE
if (channelImportance == NotificationManager.IMPORTANCE_LOW) {
callInviteChannel = NotificationChannel(
Constants.VOICE_CHANNEL_LOW_IMPORTANCE,
"Primary Voice Channel",
NotificationManager.IMPORTANCE_DEFAULT
)
channelId = Constants.VOICE_CHANNEL_LOW_IMPORTANCE
}
val audioAttributes = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.build()
callInviteChannel.setSound(soundUri, audioAttributes)
callInviteChannel.lightColor = Color.GREEN
callInviteChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(callInviteChannel)
return channelId
}
private fun handleIncomingCall(videoCallDetails: VideoNotification, notificationId: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setCallInProgressNotification(videoCallDetails, notificationId)
}
sendCallInviteToActivity(videoCallDetails, notificationId)
}
private fun endForeground() {
stopForeground(true)
}
#TargetApi(Build.VERSION_CODES.O)
private fun setCallInProgressNotification(
videoCallDetails: VideoNotification,
notificationId: Int
) {
if (isAppVisible()) {
startForeground(
notificationId, createNotification(
videoCallDetails,
notificationId,
NotificationManager.IMPORTANCE_LOW
)
)
} else {
startForeground(
notificationId, createNotification(
videoCallDetails,
notificationId,
NotificationManager.IMPORTANCE_HIGH
)
)
}
}
/*
* Send the CallInvite to the VoiceActivity. Start the activity if it is not running already.
*/
private fun sendCallInviteToActivity(videoCallDetails: VideoNotification, notificationId: Int) {
if (Build.VERSION.SDK_INT >= 29 && !isAppVisible()) {
return
}
val intent = Intent(this, VideoActivity::class.java)
intent.action = ACTION_INCOMING_CALL_NOTIFICATION
intent.putExtra(Constants.ROOM_CODE, videoCallDetails.roomCode)
intent.putExtra(Constants.ROOM_NAME, videoCallDetails.roomName)
intent.putExtra(Constants.ACCESS_TOKEN, videoCallDetails.accessToken)
intent.putExtra(Constants.CALLER_NAME, videoCallDetails.userName)
intent.putExtra(Constants.CALL_RESPONSE_ACTION_KEY, Constants.INCOMING_CALL_SCREEN)
intent.putExtra(Constants.CALL_TYPE, Constants.VIDEO_CALL)
intent.putExtra(Constants.INCOMING_VIDEO_CALL, true)
intent.putExtra(Constants.NOTIFCATION_ID, notificationId)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
this.startActivity(intent)
}
private fun isAppVisible(): Boolean {
return ProcessLifecycleOwner
.get()
.lifecycle
.currentState
.isAtLeast(Lifecycle.State.STARTED)
}
private fun createRandomCode(codeLength: Int): Int {
val chars = "1234567890".toCharArray()
val sb = StringBuilder()
val random: Random = SecureRandom()
for (i in 0 until codeLength) {
val c = chars[random.nextInt(chars.size)]
sb.append(c)
}
return sb.toString().toInt()
}
override fun onCreate() {
super.onCreate()
isRunning = true
LocalBroadcastManager.getInstance(this).registerReceiver(
broadCastReceiver, IntentFilter(
Constants.NOTIFCATION_ID
)
)
}
override fun onDestroy() {
super.onDestroy()
isRunning = false
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadCastReceiver)
}
private val broadCastReceiver = object : BroadcastReceiver() {
override fun onReceive(contxt: Context?, intent: Intent?) {
endForeground()
}
}
}
You return START_NOT_STICKY from onStartCommand(). When you swipe the app from the recent tasks list, Android kills the OS process hosting your app (and all the components Activity, Service, etc.). If you want Android to restart your Service automatically in this case, you need to return START_STICKY from onStartCommand().

Android Kotlin Service issue

I have implemented a service that runs in the background of the application. The service calls the function every minute, which checks if the time has come to trigger the notification - reminder (retrieves the current date and time and compares it with the data saved in the database. If the date and time match, the notification is triggered. The function is called every 1 minute. Function this one must check every 1 minute the condition when the user uses the application, the application runs in the background, does not use the application and how the phone is asleep. By implementing reminders the previous day evening to the evening and the next day. Reminders appear for the same day for the evening and the first reminder for the next day. The next ones do not appear. On the other hand, the reminder set for 5 minutes works and for 30 no longer. Looking for information I found on the internet that the problem lies in:
time drift - When the screen is on and the phone is fully awake, the interval between two consecutive events will remain constant most of the time, but may jump from time to time (lengthen)
the phone goes into sleep mode
Any of you have an idea how to solve this, get around it?
ForegroundService
class ForegroundService : Service() {
companion object {
val CHANNEL_ID = "ForegroundServiceChannel"
val CHANNEL_ID_CHILD = "ForegroundServiceChannelCHILD"
private var isRunning = true
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val input = intent.getIntExtra("time",15)
createNotificationChannel()
val notificationIntent = Intent(this, Menu::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
0, notificationIntent, 0
)
val wakeLock: PowerManager.WakeLock =
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::AR_Apteczka").apply {
acquire()
}
}
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Twoja Elektroniczna Apteczka")
.setContentText("Dbamy o Twoje zdrowie")
.setSmallIcon(com.example.agnieszka.ar_apteczka.R.drawable.pills)
.setOnlyAlertOnce(true)
//.setContentIntent(pendingIntent)
//.setSound(null)
.build()
startForeground(1, notification)
isRunning = true
val context = this
val intent = Intent(this, ShowAllTodaysMedicines::class.java)
val pendingIntentNotification = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
doAsync {
while(true)
{
var message= createReminderMessage(context)
uiThread {
if(true) {
if (message != "Nadszedł czas by zażyć: ") {
val notification =
NotificationCompat.Builder(context, CHANNEL_ID_CHILD)
.setContentTitle("Zażyj leki")
.setContentText(message)
.setSmallIcon(com.example.agnieszka.ar_apteczka.R.drawable.pills)
.setContentIntent(pendingIntentNotification)
.setAutoCancel(true)
.build()
with(NotificationManagerCompat.from(context)) {
notificationManager.notify(2, notification)
}
}
}
}
//SystemClock.sleep(60000)
SystemClock.sleep(60*1000-SystemClock.elapsedRealtime()%1000)
// SystemClock.sleep(50000) //10*1000-SystemClock.elapsedRealtime()%1000
}
}
return START_NOT_STICKY
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onDestroy() {
super.onDestroy()
isRunning = false
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(
CHANNEL_ID,
"Foreground Service Channel",
//NotificationManager.IMPORTANCE_DEFAULT
NotificationManager.IMPORTANCE_DEFAULT
)
//serviceChannel.setSound(null, null) //
val serviceChannel2 = NotificationChannel(
CHANNEL_ID_CHILD,
"Foreground Service ChannelChild ",
NotificationManager.IMPORTANCE_DEFAULT
//NotificationManager.IMPORTANCE_LOW
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(serviceChannel)
manager.createNotificationChannel(serviceChannel2)
}
}
fun reminderForNow(context: Context) : ArrayList<Reminder> {
var listOfReminder : ArrayList<Reminder> = ArrayList()
var timetoday = takeTimeNow()
var dateToday = takeTodayDate()
val dbHelper = SQLConector(context)
val allRemindersList = dbHelper.getAllReminders()
for (i: Reminder in allRemindersList) {
if (i.reminderDate == dateToday && i.ReminderTime == timetoday) {
var reminder = Reminder(
i.idReminder,
i.medicineName,
i.reminderDate,
i.ReminderTime
)
listOfReminder.add(reminder)
}
}
return listOfReminder
}
private fun createReminderMessage(p0: Context) : String{
var message = "Nadszedł czas by zażyć: "
var listOfReminders = reminderForNow(p0)
if(listOfReminders.count() > 0){
for (i: Reminder in listOfReminders) {
message += i.medicineName + ", "
}
}
return message
}
private fun takeTodayDate():String{
val current = LocalDateTime.now()
val formatDate = DateTimeFormatter.ofPattern("yyyy-MM-dd")
var currentDate = current.format(formatDate).toString()
return currentDate
}
private fun takeTimeNow() : String{
val current = LocalDateTime.now()
val formatTime = DateTimeFormatter.ofPattern("HH:mm")
return current.format(formatTime).toString()
}
}
**Main Activity**
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
binding.buttonStart.setOnClickListener { startService() }
binding.buttonStop.setOnClickListener {stopService() }
startService()
}
private fun startService() {
val serviceIntent = Intent(this, ForegroundService::class.java)
serviceIntent.putExtra("time", 1)
ContextCompat.startForegroundService(this, serviceIntent)
}
private fun stopService() {
val serviceIntent = Intent(this, ForegroundService::class.java)
stopService(serviceIntent)
}
}
*Manifest*
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:allowBackup="false"
android:icon="#drawable/pills"
android:label="#string/nameOfApplications"
android:roundIcon="#drawable/icon"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<service
android:name=".ForegroundService"
android:enabled="true"
android:exported="true"/>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>

Android Kotlin Foregeground Service + Notifications, Why works wrong?

In my application, I need a foreground service that will check a certain condition every minute, and if it is correct, it triggers a notification reminder. The user determines in advance what time and day he would like to have the reminder to. Data is saved in the database. Then the service every minute checks if it has a reminder for a given hour and day and if so sends a notification. The service must work when the user uses the application, when the application runs in the background and when it is closed. Could someone tell me why this code works on one phone but not on others? So-called, if I set a reminder, up to 20 minutes, it works (in all 3 states that I wrote about earlier), but once I set the reminder for the next days, it doesn't work anymore. I am surprised that sometimes the reminder on another phone works and sometimes it doesn't. I checked, the permission for the application to run in the background is selected in the settings. Please help.
Manifest
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:allowBackup="false"
android:icon="#drawable/pills"
android:label="#string/nameOfApplications"
android:roundIcon="#drawable/icon"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<service
android:name=".ForegroundService"
android:enabled="true"
android:exported="true"/>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
ForegroundService
class ForegroundService : Service() {
companion object {
val CHANNEL_ID = "ForegroundServiceChannel"
val CHANNEL_ID_CHILD = "ForegroundServiceChannelCHILD"
private var isRunning = false
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val input = intent.getIntExtra("time",15)
createNotificationChannel()
val notificationIntent = Intent(this, Menu::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
0, notificationIntent, 0
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("MotherNotification")
.setContentText("Message")
.setOnlyAlertOnce(true)
.build()
startForeground(1, notification)
isRunning = true
val context = this
val intent = Intent(this, ShowAll::class.java)
val pendingIntentNotification = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
doAsync {
while(isRunning)
{
var message : String = createReminderMessage(context)
//SystemClock.sleep(input * 10_000L)
SystemClock.sleep(50000)
uiThread {
if(isRunning && (message != "Nadszedł czas by zażyć: ")) {
val notification = NotificationCompat.Builder(context, CHANNEL_ID_CHILD)
.setContentTitle("Title")
.setContentText(message)
.setContentIntent(pendingIntentNotification)
.setAutoCancel(true)
.build()
with(NotificationManagerCompat.from(context)) {
notificationManager.notify(2, notification)
}
}
}
}
}
return START_NOT_STICKY
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onDestroy() {
super.onDestroy()
isRunning = false
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(
CHANNEL_ID,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
)
val serviceChannel2 = NotificationChannel(
CHANNEL_ID_CHILD,
"Foreground Service ChannelChild ",
NotificationManager.IMPORTANCE_DEFAULT
//NotificationManager.IMPORTANCE_LOW
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(serviceChannel)
manager.createNotificationChannel(serviceChannel2)
}
}
fun reminderForNow(context: Context) : ArrayList<Reminder> {
var listOfReminder : ArrayList<Reminder> = ArrayList()
var timetoday = takeTimeNow()
var dateToday = takeTodayDate()
val dbHelper = SQLConector(context)
val allRemindersList = dbHelper.getAllReminders()
for (i: Reminder in allRemindersList) {
if (i.reminderDate == dateToday && i.ReminderTime == timetoday) {
var reminder = Reminder(
i.id,
i.Name,
i.reminderDate,
i.ReminderTime
)
listOfReminder.add(reminder)
}
}
return listOfReminder
}
private fun createReminderMessage(p0: Context) : String{
var message : String = "title : "
var listOfReminders = reminderForNow(p0)
if(listOfReminders.count() > 0){
for (i: Reminder in listOfReminders) {
message += i.Name + ", "
}
}
return message
}
private fun takeTodayDate():String{
val current = LocalDateTime.now()
val formatDate = DateTimeFormatter.ofPattern("yyyy-MM-dd")
var dateResult = current.format(formatDate).toString()
return dateResult
}
private fun takeTimeNow() : String{
val current = LocalDateTime.now()
val formatTime = DateTimeFormatter.ofPattern("HH:mm")
var timeResult = current.format(formatTime).toString()
return timeResult
}
}
Main Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
binding.buttonStart.setOnClickListener { startService() }
binding.buttonStop.setOnClickListener {stopService() }
startService()
}
private fun startService() {
val serviceIntent = Intent(this, ForegroundService::class.java)
serviceIntent.putExtra("time", 1)
ContextCompat.startForegroundService(this, serviceIntent)
}
private fun stopService() {
val serviceIntent = Intent(this, ForegroundService::class.java)
stopService(serviceIntent)
}
}
The correct way to handle tasks which require exact fire time is to use the AlarmManagerCompat class.
You can use setExactAndAllowWhileIdle(...) method to force the alarm to start your service even when the device is in Doze mode and you will need a BroadcastReceiver to re-schedule the alarms if the device is rebooted.
You can find some references online on how to implement that.

Categories

Resources