I'm making an APK file download screen and i show progress which is the percent of the file downloaded in the screen and also i have a notification to use a Foreground service because the file download must be downloaded from a service. During downloading, users can leave the app and can go back to my app through the notification. But the problem is, to achieve this logic, i use PendingIntent and when user click my app's notification, it recreates the app instead of going back to the previous screen earlier. I don't know why. please check my codes below.
File Download Fragment
class FileDownloadFragment(private val uri: String) : DialogFragment() {
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
startDownloadService()
}
private fun startDownloadService() {
val downloadService = AppUpdateAPKDownloadService()
val startIntent = Intent(context, downloadService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(startIntent)
} else {
context.startService(startIntent)
}
}
...
}
Foreground Service
class AppUpdateAPKDownloadService: LifecycleService() {
...
/** Dispatcher */
private val ioDispatcher = Dispatchers.IO
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
if (intent != null) {
createNotificationChannel()
val notification = getNotification()
startForeground(NOTIFICATION_ID, notification)
lifecycleScope.launch{
downloadAPK(intent.getStringExtra(UPDATE_APK_URI).toString())
}
}
return START_NOT_STICKY
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
).apply {
enableVibration(true)
enableLights(true)
}
val manager = this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
}
}
private fun getNotification(): Notification {
val intent = Intent(applicationContext, SplashActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
val pendingIntent = TaskStackBuilder.create(applicationContext).run {
addNextIntentWithParentStack(intent)
getPendingIntent(APP_UPDATE_PENDING_INTENT_REQUEST_CODE, PendingIntent.FLAG_IMMUTABLE)
}
// val pendingIntent = PendingIntent.getActivity(
// applicationContext,
// APP_UPDATE_PENDING_INTENT_REQUEST_CODE,
// intent,
// PendingIntent.FLAG_IMMUTABLE
// )
val notification = NotificationCompat.Builder(this, CHANNEL_ID).apply {
setContentTitle("DownloadApp")
setContentText("Downloading...")
setSmallIcon(R.drawable.icon)
setLargeIcon(BitmapFactory.decodeResource(this#AppUpdateAPKDownloadService.applicationContext.resources, R.mipmap.icon))
priority = NotificationCompat.PRIORITY_HIGH
setContentIntent(pendingIntent)
setOngoing(true)
setAutoCancel(true)
}.build()
val notificationManager = NotificationManagerCompat.from(this)
notificationManager.notify(NOTIFICATION_ID, notification)
return notification
}
private suspend fun downloadAPK(uri: String) = withContext(ioDispatcher) {
kotlin.runCatching {
URL(uri)
}.onSuccess { url ->
val connection: URLConnection = url.openConnection()
connection.connect()
val fileFullSize = connection.contentLength
val directory = applicationContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
directory?.let {
if (!directory.exists()) directory.mkdirs()
}
val inputDataStream = DataInputStream(BufferedInputStream(url.openStream(), fileFullSize))
val file = File(directory, "my_app.apk")
val outputDataStream = DataOutputStream(BufferedOutputStream(FileOutputStream(file), fileFullSize))
processDownload(
inputStream = inputDataStream,
outputStream = outputDataStream,
fileFullSize = fileFullSize
)
val bundle = Bundle().apply {
putInt(UPDATE_APK_RECEIVER_MODE, UPDATE_APK_DOWNLOAD_COMPLETE)
}
uiUpdateReceiver?.send(Activity.RESULT_OK, bundle)
}.onFailure {
}
}
private fun processDownload(
inputStream: DataInputStream,
outputStream: DataOutputStream,
fileFullSize: Int
) {
val data = ByteArray(fileFullSize)
var downloadSize: Long = 0
var count: Int
while (inputStream.read(data).also { count = it } != -1) {
downloadSize += count
val percent = (downloadSize.toFloat() / fileFullSize) * 100
Log.d("TEST", "writing...$percent% in Service")
val bundle = Bundle().apply {
putInt(UPDATE_APK_RECEIVER_MODE, UPDATE_APK_UI_UPDATE)
putFloat(UPDATE_API_UI_PERCENT, percent)
}
uiUpdateReceiver?.send(Activity.RESULT_OK, bundle)
outputStream.write(data, 0, count)
}
outputStream.flush()
outputStream.close()
inputStream.close()
}
...
}
I also add android:launchMode="singleTop" to SplashActivity in AndroidManifest.xml
but it's still not working...
What mistake did I make?
TaskStackBuilder will always recreate the activities. That's the way it is designed. You don't want to use it if you want to return to an existing task.
Instead of your code to create the PendingIntent, use this:
val intent = PackageManager.getLaunchIntentForPackage("your.package.name")
val pendingIntent = PendingIntent.getActivity(applicationContext,
APP_UPDATE_PENDING_INTENT_REQUEST_CODE,
intent,
PendingIntent.FLAG_IMMUTABLE)
This will just cause your existing task to be brought to the foreground in whatever state it was in last. If your app is not running, this will launch your app as it would if you tapped the app icon on the HOME screen.
Okay, I solved this problem. it's 3 steps.
use PendingIntent.getActivity() instead of TaskStackBuilder.create(applicationContext).
use PendingIntent.FLAG_IMMUTABLE if you target Android M(API23) or above, or PendingIntent.FLAG_UPDATE_CURRENT if you target Android Lollipop(API21) or below.
add these flags Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP to the Intent.
Related
I'm trying stopping running coroutineWorker from notification button. I tried 3 methods and 2 of them calls "Result.failure()" & working fine. However another one doesn't.
Below CoroutineWorker shows foregroundInfo and Starts ringtone.
class RingWork(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
companion object {
val ALARM_CHANNEL_ID = "alarm_channel6"
}
lateinit var ringtoneSound: Ringtone
val context = applicationContext
#RequiresApi(Build.VERSION_CODES.Q)
override suspend fun doWork(): Result {
return try {
val alarmId = inputData.getInt("alarmId", 0)
val notificationMgr =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
//CHANNEL
val alarmChannel = NotificationChannel(
ALARM_CHANNEL_ID, "alarm" ,NotificationManager.IMPORTANCE_HIGH
)
alarmChannel.setSound(null, null)
alarmChannel.enableVibration(false)
alarmChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
notificationMgr.createNotificationChannel(alarmChannel)
val fullScreenIntent = Intent(context, LockscreenActivity::class.java).putExtra("alarmId", alarmId)
//This calls "failure" properly
val fullScreenPendingIntent = PendingIntent.getActivity(context, 0, fullScreenIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
//This calls "failure" properly
val stop1PendingIntent =
WorkManager.getInstance(context).createCancelPendingIntent(getId())
val s2Intent = Intent(context, StopAlarmReceiver::class.java).putExtra("alarmId", alarmId)
//This is not.
val stop2PendingIntent = PendingIntent.getBroadcast(context, 1, s2Intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
val builder = NotificationCompat.Builder(context, ALARM_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_stat_name)
.setContentTitle("title")
.setFullScreenIntent(fullScreenPendingIntent, true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_ALARM)
.setAutoCancel(true)
.setSound(null)
.setVibrate(null)
.addAction(R.drawable.ic_stat_name, "Stop1", stop1PendingIntent)
.addAction(R.drawable.ic_stat_name, "Stop2", stop2PendingIntent)
setForeground(
ForegroundInfo(1999999, builder.build(), FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK)
)
ringtoneSound =
RingtoneManager.getRingtone(context, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM))
ringtoneSound.play()
delay(30000L)
ringtoneSound.stop()
Result.success()
} catch (e: Exception) {
Result.failure()
} finally {
cleanup()
}
}
fun cleanup(){
ringtoneSound.stop()
}
}
In LockScreenActivity, there is a button to stop ringtone.
binding.stoppingbutton.setOnClickListener {
val workMgr = WorkManager.getInstance(applicationContext)
workMgr.cancelUniqueWork("RingWork-$alarmId")
finish()
}
This calls "result.failure" and "finally" then ringtone will stop, notification will disapear. working fine.
However, if I press "Stop2" button on the notification.
class StopAlarmReceiver: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val alarmId = intent.getIntExtra("alarmId", 0)
val workMgr = WorkManager.getInstance(context)
workMgr.cancelUniqueWork("RingWork-$alarmId")
}
}
It cancels worker, but it won't call "result.failure" and "finally", so ringtone won't stop. Notification also won't disappear.
fullScreenPendingIntent and stop2PendingIntent are doing the same thing, but why it won't behave same?
You can edit your PendingIntent like this and it will trigger onReceive:
val stop2PendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
PendingIntent.getBroadcast(context, downloadFile.productId, cancelIntent, PendingIntent.FLAG_IMMUTABLE)
else
PendingIntent.getBroadcast(context, downloadFile.productId, cancelIntent, 0)
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
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...
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().
I am trying to create service which will be fetch data from server (periodicaly) and send notification to user. I achieved almost my goal, but service stops work when screen is off. When I turn on my phone I get plenty of notifications from previous time.
Service code:
class MyService: Service() {
override fun onCreate() {
super.onCreate()
val handler = Handler()
val runnable = object: Runnable {
override fun run() {
getDataFromServer()
handler.postDelayed(this, 1800000)
}
}
handler.post(runnable)
}
private fun getDataFromServer() {
// fetch data
displayNotification(description)
}
private fun displayNotification(description: String) {
val intent = Intent(this, MainActivity::class.java)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notificationId = (0..300).random()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val adminChannelName = "Messages channel"
val adminChannelDescription = "Notification about new data"
NotificationChannel(ADMIN_CHANNEL_ID, adminChannelName, NotificationManager.IMPORTANCE_HIGH).apply {
description = adminChannelDescription
enableLights(true)
lightColor = Color.RED
enableVibration(true)
notificationManager.createNotificationChannel(this)
}
}
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
val pendingIntent = PendingIntent.getActivity(
this, 0, intent, PendingIntent.FLAG_ONE_SHOT
)
val notificationSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val notificationBuilder = NotificationCompat.Builder(this, ADMIN_CHANNEL_ID)
.setSmallIcon(R.drawable.icon_small)
.setContentTitle(getText(R.string.notification_new_data_title))
.setContentText(getString(R.string.notification_new_data_text, description))
.setAutoCancel(true)
.setSound(notificationSoundUri)
.setContentIntent(pendingIntent)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notificationBuilder.color = ContextCompat.getColor(context, R.color.icon_background)
}
notificationManager.notify(notificationId, notificationBuilder.build())
}
override fun onBind(intent: Intent): IBinder ? {
return null
}
}
Thank you for your help!
Try to start you service using the startForegroundService instead of startService, whether you need a background service, you can convert you Service to JobIntentService:
https://developer.android.com/reference/androidx/core/app/JobIntentService