Android Kotlin Foregeground Service + Notifications, Why works wrong? - android

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.

Related

Media Notification not showed

I'm trying to implement a notification always visible to play and stop a radio. I don't understand what I'm doing wrong. So Here i post my code:
I don't understand if something is missing for the notification to be shown or if I build the notification badly. In any case, the service works correctly if I activate the radio from the button I inserted in the Compose part, and if I close the app everything continues to work correctly, but I would like to understand why there is no notification at the top which allows you to activate or deactivate radio
Manifest
<service
android:name="com.example.appradio.services.MediaSessionService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON"/>
</intent-filter>
</service>
<receiver android:name="androidx.media.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON"/>
</intent-filter>
</receiver>
MainActivity
Inside on create method:
ContextCompat.startForegroundService(
this#MainActivity.applicationContext,
Intent(this#MainActivity.applicationContext, MediaSessionService::class.java)
)
Service
class MediaSessionService : Service() {
var mediaPlayer: MediaPlayer? = null
private var mMediaNotificationManager: MediaNotificationManager? = null
private var mediaSession: MediaSessionCompat? = null
override fun onCreate() {
super.onCreate()
mediaPlayer = MediaPlayer()
mMediaNotificationManager = MediaNotificationManager(this)
mediaSession = MediaSessionCompat(this, "SOME_TAG")
mediaSession!!.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)
mediaSession!!.setCallback(object : MediaSessionCompat.Callback() {
override fun onPlay() {
mediaPlayer!!.start()
}
override fun onPause() {
mediaPlayer!!.pause()
}
})
val notification = mMediaNotificationManager!!.getNotification(
metadata, state, mediaSession!!.sessionToken
)
startForeground(NOTIFICATION_ID, notification)
}
val metadata: MediaMetadataCompat
get() {
val builder = MediaMetadataCompat.Builder()
builder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "artist")
builder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title")
builder.putLong(
MediaMetadataCompat.METADATA_KEY_DURATION, mediaPlayer!!.duration
.toLong()
)
return builder.build()
}
private val state: PlaybackStateCompat
private get() {
val actions =
if (mediaPlayer!!.isPlaying) PlaybackStateCompat.ACTION_PAUSE else PlaybackStateCompat.ACTION_PLAY
val state =
if (mediaPlayer!!.isPlaying) PlaybackStateCompat.STATE_PLAYING else PlaybackStateCompat.STATE_PAUSED
val stateBuilder = PlaybackStateCompat.Builder()
stateBuilder.setActions(actions)
stateBuilder.setState(
state,
mediaPlayer!!.currentPosition.toLong(),
1.0f,
SystemClock.elapsedRealtime()
)
return stateBuilder.build()
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
if ("android.intent.action.MEDIA_BUTTON" == intent.action) {
val keyEvent = intent.extras!!["android.intent.extra.KEY_EVENT"] as KeyEvent?
if (keyEvent!!.keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
mediaPlayer!!.pause()
} else {
mediaPlayer!!.start()
}
}
return super.onStartCommand(intent, flags, startId)
}
override fun onBind(intent: Intent): IBinder? {
return null
}
companion object {
const val NOTIFICATION_ID = 888
}
}
Notification Manager
class MediaNotificationManager(private val mService: MediaSessionService) {
private val mPlayAction: NotificationCompat.Action = NotificationCompat.Action(
R.drawable.ic_play,
"play",
MediaButtonReceiver.buildMediaButtonPendingIntent(
mService,
PlaybackStateCompat.ACTION_PLAY
)
)
private val mPauseAction: NotificationCompat.Action = NotificationCompat.Action(
R.drawable.ic_stop,
"pause",
MediaButtonReceiver.buildMediaButtonPendingIntent(
mService,
PlaybackStateCompat.ACTION_PAUSE
)
)
val notificationManager: NotificationManager = mService.getSystemService(Service.NOTIFICATION_SERVICE) as NotificationManager
fun getNotification(
metadata: MediaMetadataCompat,
state: PlaybackStateCompat,
token: MediaSessionCompat.Token
): Notification {
val isPlaying = state.state == PlaybackStateCompat.STATE_PLAYING
val description = metadata.description
val builder = buildNotification(state, token, isPlaying, description)
return builder.build()
}
private fun buildNotification(
state: PlaybackStateCompat,
token: MediaSessionCompat.Token,
isPlaying: Boolean,
description: MediaDescriptionCompat
): NotificationCompat.Builder {
if (isAndroidOOrHigher) {
createChannel()
}
val builder = NotificationCompat.Builder(mService, CHANNEL_ID)
builder.setStyle(
androidx.media.app.NotificationCompat.MediaStyle()
.setMediaSession(token)
.setShowActionsInCompactView(0) // For backwards compatibility with Android L and earlier.
.setShowCancelButton(true)
.setCancelButtonIntent(
MediaButtonReceiver.buildMediaButtonPendingIntent(
mService,
PlaybackStateCompat.ACTION_STOP
)
)
)
.setColor(ContextCompat.getColor(mService, R.color.black))
.setSmallIcon(R.drawable.ic_play) // Pending intent that is fired when user clicks on notification.
.setContentIntent(createContentIntent()) // Title - Usually Song name.
.setContentTitle(description.title) // When notification is deleted (when playback is paused and notification can be
// deleted) fire MediaButtonPendingIntent with ACTION_PAUSE.
.setDeleteIntent(
MediaButtonReceiver.buildMediaButtonPendingIntent(
mService, PlaybackStateCompat.ACTION_PAUSE
)
)
builder.addAction(if (isPlaying) mPauseAction else mPlayAction)
return builder
}
#RequiresApi(Build.VERSION_CODES.O)
private fun createChannel() {
if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) {
val name: CharSequence = "MediaSession"
val description = "MediaSession and MediaPlayer"
val importance = NotificationManager.IMPORTANCE_LOW
val mChannel = NotificationChannel(CHANNEL_ID, name, importance)
mChannel.description = description
mChannel.enableLights(true)
mChannel.lightColor = Color.RED
mChannel.enableVibration(true)
mChannel.vibrationPattern = longArrayOf(100, 200, 300, 400, 500, 400, 300, 200, 400)
notificationManager.createNotificationChannel(mChannel)
Timber.d("createChannel: New channel created")
} else {
Timber.d("createChannel: Existing channel reused")
}
}
private val isAndroidOOrHigher: Boolean
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
private fun createContentIntent(): PendingIntent {
val openUI = Intent(mService, MainActivity::class.java)
openUI.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
return PendingIntent.getActivity(
mService, REQUEST_CODE, openUI, PendingIntent.FLAG_CANCEL_CURRENT
)
}
companion object {
const val NOTIFICATION_ID = 412
private const val CHANNEL_ID = "com.example.appradio.musicplayer.channel"
private const val REQUEST_CODE = 501
}
init {
notificationManager.cancelAll()
}
}

Android Doze mode kills foregreound MediaPlayer service

I've been researching awhile about how to keep active a constantly running audio playback in the background (online radio). For last I made a foreground service for it and its works for the most phones, but not on Samsung Android P and above... (as this article show in the "Foreground service limitations" section: https://proandroiddev.com/android-foreground-service-restrictions-d3baa93b2f70)
I heard that there is a advanced tool for audio playback called ExoPlayer. Could this lib help me out?
I'v been tried these solutions:
ping google.com every 2 sec
request battery optimization ignoring
set wake lock for mediplayer with: setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK) (still in use)
Starting the service:
viewModel.isMusicControlServiceNeedToStart.observe(this, Observer {
if (it) {
val intent = Intent(this, MusicControlForegroundServiceImpl::class.java).apply { action = ACTION_SHOW_MUSIC_CONTROL }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) startForegroundService(intent) else startService(intent)
} else {
stopService(Intent(this, MusicControlForegroundServiceImpl::class.java))
}
})
The service itself:
class MusicControlForegroundServiceImpl : Service(), KoinComponent {
private val notificationManager: NotificationManager by inject()
private val radioManager: RadioManager by inject()
private val context: Context by inject()
private val preferences: Preferences by inject()
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent != null && intent.action == ACTION_SHOW_MUSIC_CONTROL) {
val lastSelectedRadio = preferences.getJSON(Globals.LAST_SELECTED_RADIO_KEY, Radio::class.java)
?: return START_NOT_STICKY
val notification = notificationManager.createMediaControlNotificationIfNeeded(context, lastSelectedRadio)
startForeground(1, notification)
}
return START_NOT_STICKY
}
override fun onTaskRemoved(rootIntent: Intent?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) startForegroundService(rootIntent) else startService(rootIntent)
super.onTaskRemoved(rootIntent)
}
override fun onDestroy() {
if (!notificationManager.musicControlServiceRestart) radioManager.release()
synchronized(MUSIC_CONTROL_SERVICE_LOCK) { notificationManager.musicControlServiceRestart = false }
synchronized(MEDIA_PLAYER_LOCK) { radioManager.lastPlayedMediaUrl = null }
stopForeground(true)
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? = null
}
The notification creation:
override fun createMediaControlNotificationIfNeeded(context: Context, selectedRadio: Radio): Notification {
val resultIntent = Intent(context, RadioDetailActivity::class.java)
val resultPendingIntent: PendingIntent? = TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(resultIntent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
val playIntent = Intent(context, NotificationReceiver::class.java).apply {
putExtra(MusicState::class.java.name, MusicState.PLAY)
}
val pauseIntent = Intent(context, NotificationReceiver::class.java).apply {
putExtra(MusicState::class.java.name, MusicState.PAUSE)
}
val notificationManager = NotificationManagerCompat.from(context)
#Suppress("DEPRECATION") val builder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification_icon)
.setContentTitle(selectedRadio.name)
.setDefaults(0)
.setOngoing(true)
.setNotificationSilent()
.addAction(
R.drawable.ic_notification_pause,
context.getString(R.string.pause),
PendingIntent.getBroadcast(context, 1, pauseIntent, 0)
)
.addAction(
R.drawable.ic_notification_play,
context.getString(R.string.play),
PendingIntent.getBroadcast(context, 2, playIntent, 0)
)
.setStyle(
androidx.media.app.NotificationCompat.MediaStyle().setMediaSession(
MediaSessionCompat(
context,
RadioDetailActivity::class.java.name
).sessionToken
)
)
.setContentIntent(resultPendingIntent)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
context.getString(R.string.app_name),
NotificationManager.IMPORTANCE_LOW
)
notificationManager.createNotificationChannel(channel)
builder.setChannelId(CHANNEL_ID)
}
return builder.build()
}
If you need any other resources please let me know and help if you can! I'm struggling with this problem for weeks now.. :(
UPDATE
Now I throw my media control notification in every 2 minutes to update previous, so the app can survive like 30 minutes on the affected phone, but still not a working solution...

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().

Notifications per day of week

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)

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>

Categories

Resources