Foreground service doesn't start activity - android

I want to start an activity even if the app is killed. Below API 29 there is no problem but above 29 even though service is triggered in background startActivity isn't working.
Here is my service class
class MyService : Service() {
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (Build.VERSION.SDK_INT >= 26) {
val channel = NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
}
val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("setContentTitle")
.setContentText("setContentText")
.build()
val mIntent = Intent(applicationContext, MyActivity::class.java)
mIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(mIntent)
startForeground(NOTIFICATION_ID, notificationBuilder)
return START_NOT_STICKY
}
companion object {
const val CHANNEL_ID = "channel_id"
const val NOTIFICATION_ID = 5
const val CHANNEL_NAME = "channel_name"
}
}
in Manifest I added FOREGROUND_SERVICE permission
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<service android:name=".service.MyService" />
<receiver android:name=".receiver.MyReceiver" />
and here is my receiver class, above API 29 I want to start a service
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent?.let {
if (Build.VERSION.SDK_INT >= 29) {
context?.startForegroundService(Intent(context, MyService::class.java))
} else {
val intentActivity = Intent(context, MyActivity::class.java)
intentActivity.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context?.startActivity(intentActivity)
}
}
}
}

You can use PendingIntent here.
PendingIntent is an intent that will perform at a later time or in other words PendingIntent specifies an action to take in future. The main differences between a pendingIntent and regular intent is pendingIntent will perform at a later time where Normal/Regular intent starts immediately.
It comes with different use cases like this.
PendingIntent.getActivity() — This will start an Activity like calling context.startActivity()
PendingIntent.getBroadcast() — This will perform a Broadcast like calling context.sendBroadcast()
PendingIntent.getService() — This will start a Services like calling context.startService()
PendingIntent.getForegroundService() — This will start a foregroundService like calling context.startForegroundService()
There are tons of examples you'll find on the internet about it. Just google it & it'll get you work done. :)

Related

How to invoke a method on a foreground service by clicking on a notification action?

I have a foreground service. It does some async work in the background and periodically issues a notification asking the user if the work should be stopped.
The notification has a button "Yes, please" and when clicked it must invoke stopAction method.
The code below is where I'm stuck. I'm maybe way off and this can't be done. Any advice?
MainService.kt
...
override fun onCreate() {
subscribeToStopActionRequest()
}
private fun subscribeToStopActionRequest () {
var eventReceiverHelper = EventReceiverHelper { stopAction() }
val filter = IntentFilter().apply {
addAction("${packageName}.stop_action_request")
}
registerReceiver(eventReceiverHelper, filter)
}
private fun stopAction () {
...
}
private fun showNotification () {
val intent = Intent(this, EventService::class.java)
val pendingIntent: PendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
var notification = NotificationCompat.Builder(this, state.notificationChannelId)
.setContentTitle("Want to stop?")
.addAction(R.drawable.stop_icon, "Yes, please", pendingIntent)
.build()
with(NotificationManagerCompat.from(this)) {
notify(1, notification)
}
}
Event receiver helper
class EventReceiverHelper(val cb: () -> Unit): BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
cb()
}
}
Define a constant:
private const val EXTRA_STOP = "stop";
Then create an intent for your service and put an extra flag:
val intent = Intent(context, YourService::class.java);
intent.putExtra(EXTRA_STOP, true);
Now you can create a pending intent as your handler:
val pendingIntent: PendingIntent = PendingIntent.getService(context, YOUR_REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE)
This pending intent will trigger the onStartCommand method on your service, where you can check whether the stop flag was set.
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent != null && intent.getBooleanExtra(EXTRA_STOP, false)) {
stopAction()
}
...
}

Dynamically register BroadcastReceiver with IntentFilter and let AlarmManager handle PendingIntent

Using a virtual Pixel 5 (API 28 Android 9.0), I would like to pass the instance of my class MainActivity to the inner class MyAlarm, but since statically declaring the receiver in the manifest would result in an instantiation error, I decided to create it dynamically. I don't know what the argument action of IntentFilter is supposed to be in my case. I tried several things, but it is hard to troubleshoot without an error. onReceive() just doesn't get executed. Also, is IntentFilter even usable, because I want to set an exact alarm using AlarmManager? Here is a shorter version of my code:
class MainActivity : AppCompatActivity() {
companion object {
const val REQUEST_ID = 843
const val NOTIFICATION_ID = 349
const val NOTIFICATION_CHANNEL_ID = "9552"
}
lateinit var myAlarm : MyAlarm
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
myAlarm = MyAlarm(this)
val intentFilter = IntentFilter("")
registerReceiver(myAlarm, intentFilter)
setAlarm(System.currentTimeMillis() + 4000)
private fun setAlarm(timeInMillis: Long) {
val intent = Intent(this, myAlarm::class.java)
val pendingIntent = PendingIntent.getBroadcast(this, REQUEST_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeInMillis, pendingIntent)
override fun onStop() {
super.onStop()
unregisterReceiver(myAlarm)
}
class MyAlarm(var mainActivityInstance: MainActivity) : BroadcastReceiver() {
lateinit var mediaPlayer : MediaPlayer
override fun onReceive(
context: Context,
intent: Intent
) {
if (!this::mediaPlayer.isInitialized) {
mediaPlayer = MediaPlayer.create(context, R.raw.sea_waves)
mediaPlayer.isLooping = true
}
createNotificationChannel(context)
sendNotification(context, "ALARM", "Wake up", "")
mediaPlayer.start()
// Calling method of mainActivityInstance here
private fun createNotificationChannel(context: Context) {
val name = "Alarm Clock Light Channel"
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance)
NotificationManagerCompat.from(context).createNotificationChannel(channel)
}
private fun sendNotification(context: Context, title: String, text: String, detail_text: String) {
val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(androidx.core.R.drawable.notification_icon_background)
.setContentTitle(title)
.setContentText(text)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setStyle(NotificationCompat.BigTextStyle().bigText(detail_text))
NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, builder.build())
}
}
Not sure what you want to do. Set an alarm and when it goes off open your media player activity to play a song or something?
Don't pass activity instances to broadcast receivers. They have very different lifecycle. Secondly dynamic broadcast receivers only exist while your process is alive. Use a fullscreen intent in your notification to let NotificationManager launch your activity.
See here: https://developer.android.com/training/notify-user/time-sensitive
Create a static receiver that reacts to your alarm. There post your notification with the fullscreen intent.

How to show multiple notifications in Foreground Service?

I'm making a note app that has an option for pinning notes in the notifications.
I'm using foreground service but the problem is when I want to pin more than one note, the notification for the second one replaces the first one.
I'm using each note's unique ID as notificationId. Here's my code :
class MyService : Service() {
lateinit var note: Note
override fun onBind(p0: Intent?): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
createNotificationChannel()
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
note = intent.getParcelableExtra(NOTIFICATION_MESSAGE_EXTRA)!!
showNotification()
return START_STICKY
}
private fun showNotification() {
val notificationIntent = Intent(this, MyService::class.java)
val pendingIntent = PendingIntent.getActivity(
this, 0, notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("Title")
.setContentText(note.noteText)
.setContentIntent(pendingIntent)
.setGroup(CHANNEL_GROUP_KEY)
.build()
startForeground(note.id, notification)
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
)
val notificationManager =
getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(channel)
}
}
}
You can done this by using
startForeground(notifyId.toInt(), notificationBuilder) in onCreate method of service and then in onStartCommand use
notificationManager.notify(notifyId.toInt(), notificationBuilder);
Basically you need to only use startForeground once and then you need to use notification manager for showing notification. That way you are able to show all the notifications and all are using foreground service

How to handle services for Android older than O?

Is the following the correct way to create a service for Android version O and newer, as well as handle older versions? For O and newer I'd like to create a foreground service. For older versions, I assume a regular service needs to be created? Is the following code correct to handle this or is there a better way?
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Intent(this, ForegroundService::class.java).also { intent ->
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
}
}
}
ForegroundService.kt
class ForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel("channel_service", "Foreground Service Channel", NotificationManager.IMPORTANCE_DEFAULT)
channel.lightColor = Color.BLUE
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(channel)
val notification: Notification = Notification.Builder(this, "channel_service")
.setContentTitle("Title")
.setContentText("Text")
.setSmallIcon(R.drawable.fancy_small_icon)
.build()
startForeground(1, notification)
}
/**
* Do work
*/
stopSelf()
return START_STICKY
}
override fun onBind(intent: Intent): IBinder? {
return null
}
}

Foreground service content intent not resuming the app but relaunching it

I've been browsing many topics about resuming an activity from a foreground service without finding any concrete answer to my problem.
I'm trying to put a foreground service in my app, and I want the app to be resumed when clicking on the service notification instead of relaunching it. I've tried using the getLaunchIntentForPackage() method from PackageManager, which is the closest to what I want to do.
However, the activity's onCreate is still being called when resuming the app by clicking on the notification.
So here is my question, how to resume an app from a notification's content intent?
I'm starting my ForegroundService in the activity's onStop so it gets called when the app is killed or sent to background.
override fun onStop() {
super.onStop()
Log.v(TAG, "onStop")
ForegroundService.startService(this, "Hellooooooo, here is the background")
}
ForegroundService
class ForegroundService: Service() {
companion object {
private const val CHANNEL_ID = "ForegroundServiceChannel"
fun startService(context: Context, message: String) {
val startIntent = Intent(context, ForegroundService::class.java)
startIntent.putExtra("inputExtra", message)
ContextCompat.startForegroundService(context, startIntent)
}
fun stopService(context: Context) {
val stopIntent = Intent(context, ForegroundService::class.java)
context.stopService(stopIntent)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val input = intent!!.getStringExtra("inputExtra")
val launchIntent = packageManager.getLaunchIntentForPackage(APP_PACKAGE)
val contentIntent = PendingIntent.getActivity(applicationContext, 0,
launchIntent, 0)
val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Foreground Service")
.setContentText(input)
.setContentIntent(contentIntent)
.setSmallIcon(R.drawable.ic_call_to_action)
.setOngoing(true)
.build()
startForeground(1, notification)
createNotificationChannel()
return START_NOT_STICKY
}
override fun onBind(p0: Intent?): IBinder? {
return null
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(
CHANNEL_ID,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
)
val manager = getSystemService(
NotificationManager::class.java
)
manager?.createNotificationChannel(serviceChannel)
}
}
}
Try set in the manifest for activity android:launchMode="singleInstance".
Do not forget set some your action to your activity intent:
activityIntent.setAction(ACTION_STARTED_FROM_NOTIFICATION);
Override onNewIntent in the activity.
in onCreate and in onNewIntent do check
if(ACTION_STARTED_FROM_NOTIFICATION.equalsIgnoreCase(intent.getAction()))
{ do what you need }

Categories

Resources