How to bring app to foreground from within ForegroundService - android

I have a ForegroundService that takes a little while to run. In the meantime the user may move to the app to the background while it is running, such as pressing the home button or enter a different app. When the ForegroundService reaches a "trigger", it attempts to start a new intent. This works great if the app is in the foreground, but doesn't work if it is in the background. The goal is when a "trigger" is reached, the app will come back to the foreground so the user can provide needed input in the new intent. In an attempt to do so I have added the flags FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_SINGLE_TOP as it looked like they would accomplish this. But the app is not brought back to the foreground. Is this something that can be done?
Below is the relevant parts of code in question:
MainActivity.kt
...
private fun startService() {
val serviceIntent = Intent(this, ForegroundService::class.java)
ContextCompat.startForegroundService(this, serviceIntent)
}
...
ForegroundService.kt
...
//When trigger is reached, start the new activity and bring app to foreground if it was moved to background
private fun startNewActivity() {
//First two lines are to shut down the foreground service since it has reached a trigger and is complete.
super.onDestroy()
stopSelf()
//Start the new intent
val startNewIntent = Intent(this, NewIntent:: class.java)
startNewIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP //Assumption was these flags would bring app to foreground
startActivity(startNewIntent)
}
...

In order to make the intent be registered into the system, you need to use a PendingIntent instead of a normal Intent; when the Foreground Service is over (when your trigger is fired), you can send this pending intent using send() method
ForegroundService.kt
...
//When trigger is reached, start the new activity and bring app to foreground if it was moved to background
private fun startNewActivity() {
//First two lines are to shut down the foreground service since it has reached a trigger and is complete.
super.onDestroy()
stopSelf()
//Start the new intent
val startNewIntent = Intent(this, NewIntent:: class.java)
val pendingIntent = PendingIntent.getActivity(
this, 0, startNewIntent,
PendingIntent.FLAG_UPDATE_CURRENT
or PendingIntent.FLAG_ONE_SHOT
)
try {
pendingIntent.send() // this should start `NewIntent` activity
} catch (e: CanceledException) {
e.printStackTrace()
}
}
...

Related

AlarmManager not triggered when app manually closed

I want to make sure AlarmManager is triggered even when my app is manually closed, the same way a messaging app still displayed messages even when closed (swipe or press the "X"). This is my code:
class MainActivity : ComponentActivity() {
private lateinit var alarmManager: AlarmManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val mp = MediaPlayer.create(context,R.raw.music)
mp.start()
}
}
this.registerReceiver(receiver, IntentFilter("SET_ALARM"))
alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager
var calendar = Calendar.getInstance()
calendar.add(Calendar.MINUTE,1)
val alarmIntent = Intent()
alarmIntent.action = "SET_ALARM"
var pendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, 0)
Column( Modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally) {
Button(onClick = { alarmManager.setAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent)}) {Text("play song in 1 minute")
}
}
}
}
}
It works fine when the app is open, but not when I close manually it . What do I need to add?
(It is not a battery management problem, as it does not work in the emulator either)
In your AndroidManifest.xml, make sure you have a receiver definition within the tags, something like:
<receiver android:name="AlarmReceiver"
You said in a comment that you're registering your broadcast receiver through an activity, and not the manifest. That will only receive broadcasts while your app is actually running - specifically as long as the Context you used to register the receiver is valid. If you used an Activity as that context, once that activity is destroyed the receiver won't get broadcasts (even if the app is running, e.g. with another activity). Even if you use the application context, once the app is destroyed, that's cleared.
From the docs:
Manifest-declared receivers
If you declare a broadcast receiver in your manifest, the system launches your app (if the app is not already running) when the broadcast is sent.
Context-registered receivers
Context-registered receivers receive broadcasts as long as their registering context is valid. For an example, if you register within an Activity context, you receive broadcasts as long as the activity is not destroyed. If you register with the Application context, you receive broadcasts as long as the app is running.
If you need your app to launch in the background (which you do if you're relying on alarms, which implies your app isn't in the foreground and could be destroyed when the alarm runs) you need to register the receiver in your manifest, so the system knows it's something your app handles

What happens to a PendingIntent if the target app was force-closed?

I'm actually working on an app that should post a notification 5 days in the future.
Using AlarmManager, I send a PendingIntent to my Receiver class.
Everything works fine until I force close my app. In this case, the notification doesn't appear.
So my question:
What happens to this PendingIntent, which was fired and did not reach its target?
When my app is finally restarted, can I check for PendingIntents, that did not reach its target?
EDIT 1:
These are the essential parts of my Broadcast Receiver:
override fun onReceive(context: Context?, intent: Intent?) {
if (context != null && intent?.action != null) {
when (intent.action) {
INTENT_ACTION_BOOT_COMPLETED -> handleDeviceBoot()
INTENT_ACTION_REMINDER -> handleReminder(context, intent.getLongExtra(EXTRA_ITEM_ID, -1))
}
}
}
private suspend fun schedule(context: Context, itemId: Long, fireDate: LocalDateTime) = withContext(Dispatchers.IO) {
AlarmManagerCompat.setAndAllowWhileIdle(
getAlarmManager(context),
AlarmManager.RTC,
fireDate.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(),
makePendingIntent(context, itemId)
)
with(AppDatabase.get(context).reminderDao()) {
val oldReminder = getItemReminder(itemId)
if (oldReminder == null) {
insert(Reminder(itemId = itemId, fireDate = fireDate))
} else {
update(Reminder(id = oldReminder.id, itemId = itemId, fireDate = fireDate))
}
}
}
private suspend fun cancel(context: Context, itemId: Long) = withContext(Dispatchers.IO) {
val reminderDao = AppDatabase.get(context).reminderDao()
val reminder = reminderDao.getItemReminder(itemId)
reminder?.let {
getAlarmManager(context).cancel(makePendingIntent(context, itemId))
reminderDao.delete(it)
}
}
private fun getAlarmManager(context: Context) = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
private fun makePendingIntent(context: Context, itemId: Long): PendingIntent {
val alarmIntent = Intent(context, ReminderManager::class.java).apply {
action = INTENT_ACTION_REMINDER
putExtra(EXTRA_ITEM_ID, itemId)
}
return PendingIntent.getBroadcast(context, itemId.toInt(), alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
As defined in Official Android Documentation
A PendingIntent itself is simply a reference to a token maintained by the system describing the original data used to retrieve it. This means that, even if its owning application's process is killed, the PendingIntent itself will remain usable from other processes that have been given it. If the creating application later re-retrieves the same kind of PendingIntent (same operation, same Intent action, data, categories, and components, and same flags), it will receive a PendingIntent representing the same token if that is still valid, and can thus call cancel() to remove it.
Revisit your code to check if there is anything else that would be causing this issue.
When you "force close" an application, the application gets set to the "stopped state". In the "stopped state" your application will NOT be automatically started by Android until the user manually restarts the application. This means that if you "force close" your app, your app will not receive any broadcast Intents until it is manually restarted by the user.
I expect (although I have not tried it myself), that if you schedule an alarm to go off at time X and before time X you "force close" the app, when time X happens, the alarm manager will try to send the PendingIntent, however Android will refuse to actually execute the BroadcastReceiver because the app is in the "stopped state". In this case I expect the trigger is lost. Android will not retry or reschedule it.
Basically, when a user "force close"s an app, he is telling Android that he doesn't want that app to run anymore, including any background processes that the app might have, or want to start in the future.
The answer is short: Active PendingIntents are cancelled on an application force-stop.

Android Notification Action is not fired (PendingIntent)

I am trying to add an Notification action item in my app which is a music player. When a stream is started a notification should be triggered and an stop button for the stream should be displayed in the notfication. The notification working fine so far, I am having trouble with the stop action item. Here is how it is declared in the service starting the stream:
Intent stopIntent = new Intent(this, MusicPlayerNew.class);
stopIntent.putExtra("STOP", "STOP");
PendingIntent stopPendingIntent = PendingIntent.getActivity(this, 0,
stopIntent, PendingIntent.FLAG_UPDATE_CURRENT, null);
mBuilder.addAction(R.drawable.ic_stat_stop, "Stop", stopPendingIntent);
Now in the onResume()-method of my activity I check with getIntent().getStringExtra() for the "STOP" extra, but the intent I retrieved via getIntent() has no extras set :(
I also tried to check to send an broadcast (i have a broadcast receiver working to communicate from the service to the activity)
Intent stopIntent2 = new Intent(MusicPlayerNew.STOP_MEDIAPLAYER);
PendingIntent stopPendingIntent2 = PendingIntent.getBroadcast(this, 0,
stopIntent2, PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder.addAction(R.drawable.ic_stat_stop, "Stop", stopPendingIntent2);
Now this works if the activity is currently in the foreground. If the activity is in the background the stop button does nothing :(
EDIT:
I have the BroadcastReceiver in my Activity as a private class
private class DataUpdateReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
..
}}
In the onResume() register my app for this receiver:
intentFilter = new IntentFilter(STOP_MEDIAPLAYER);
registerReceiver(dataUpdateReceiver, intentFilter);
onPause()
unregisterReceiver(dataUpdateReceiver);
Now if I remove the unregistering from the onPause()-method the broadcast is received even if the app/activity is not in the foreground anymore. But is this the right way to do it? I got this register/unregister-stuff from a tutorial on the web i think..
This is very late answer but it may help someone:
You should choose the right kind of Pending intent based on the intent you want to run. Here are some Examples:
For Activity use below:
Intent i = new Intent(this, YourActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, i, 0);
For Service use below:
Intent i = new Intent(this, YourService.class);
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
For Broadcast Receiver use below:
Intent i = new Intent(this, YourReciver.class);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
You may need to change the request code and Flags if required
I find solution in this thread on google code https://code.google.com/p/android/issues/detail?id=61850
To fix it you must add PendingIntent.FLAG_CANCEL_CURRENT flag to your PendingIntent.
PendingIntent stopPendingIntent2 = PendingIntent.getBroadcast(this, 0,
stopIntent2, PendingIntent.FLAG_CANCEL_CURRENT);
I ran into this problem today. In my case it was using cached intent extras from a previous instance of the intent as all the parameters for the pendingIntent constructors was same. I found two solutions for this...
Using FLAG_CANCEL_CURRENT as mentioned by Nik.
Passing an unique requestCode to the pendingIntent as follows
PendingIntent pi = PendingIntent.getService(this, UNIQUE_ID, pi, 0);
In my case, the second method solved the problem as I need to keep the previous notifications alive. May be this will help someone with similar issue.
I ran into this problem today and it was caused by the activity not being registered or added to AndroidManifest.xml. I thought I had it in there but it wasn't. Also, no errors were being logged by trying to invoke the action with its intent.
I figured this out by creating an intent and then calling startAcitivty(intent) without using a notification. It then gave me an error stating the activity was likely missing from the manifest.
If none of the other answers solve your problem then hopefully this will. Usually tricky problems are the result of something simple and silly.
Do not use explicit Intent
In my case, I created a dynamically context registered BroadcastReceiver within my Service class for listening the notification actions.
class MyService:Service(){
private val receiver: BroadcastReceiver = NotificationActionReceiver()
...
inner class NotificationActionReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
...
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
...
registerReceiver(receiver,IntentFilter("SOME_ACTION"))
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
...
unregisterReceiver(receiver)
super.onDestroy()
}
PendingIntent with explicit Intent
val nextIntent = Intent(this, NotificationActionReceiver::class.java) //Explicit Intent
val nextPendingIntent: PendingIntent= PendingIntent.getBroadcast(this,0x11,nextIntent, PendingIntent.FLAG_CANCEL_CURRENT)
However with this setup, the BroadcastReceiver never triggered.
In order to make it work I need to replace my explicit intent with the implicit one
So all I did was,
val nextIntent = Intent("SOME_ACTION") //Implicit Intent
val nextPendingIntent: PendingIntent= PendingIntent.getBroadcast(this,0x11,nextIntent, PendingIntent.FLAG_CANCEL_CURRENT)
NOTE: Since the BroadcastReceiver is dynamically context registered, you don't have to worry about restrictions on implicit intents
More than using broadcast receiver, you should use a service and declare a new action in your service this way:
public final String ACTION_STOP = "yourpackagename.ACTION_STOP";
And then create your intents like this:
Intent stopIntent = new Intent(this, YourService.class).setAction(YourService.ACTION_STOP);
PendingIntent stopPendingIntent = PendingIntent.getService(this, 0, stopIntent, 0);
Of course, stop playback in your service's function startCommand, if the intent's action equals ACTION_STOP.
This should do the trick ;)
You do not receive the broadcast when it is in the background because you are unregistering in onPause. Move the unregisterReceiver code to onDestroy function. This will be called only when the activity is destroyed. Or you can unregister once the expected event has occurred.
There are multiple questions here:
Part 1: Why is your intent not receiving the "STOP" extra?
Though it is not seen in the code you have provided, I wanted to confirm if you are using the flag FLAG_ACTIVITY_SINGLE_TOP for the notification intent ? If so, the intent you receive in your activity would be the intent that started the activity and hence the "STOP" extra will not be available. You will need to extend the onNewIntent() is this case (where the new intent is sent). More info here.
If you have not used FLAG_ACTIVITY_SINGLE_TOP, then it means that a new activity is created when notification is tapped, in which case the Intent must have the "STOP" parameter. If you can provide me all relevant code, I can help you better.
Part 2: Using Broadcast Receiver
This is not straight forward, as you already discovered. You would need to unregister in onDestroy and if your activity is closed by the user, the onDestroy may be called and your broadcast receiver may not active at the time the notification is tapped by the user. If you dont unregister at all, it may seem to be working, but this is a memory leak, GC may clean up anytime which could lead to a crash in your program, ie., you MUST unregister. If you need to go with broadcast receiver approach, you need to have a service to do this and service comes with its own pitfalls -> restart by system, battery drain etc. I would strongly recommend you go with your first approach.
I had a very similar issue but a very different solution. Pending intent is also not fired if you have declared <service android:enabled="false"></service> in your manifest.xml file.
Replace from android:enabled="false" to android:enabled="true"
This might not be a direct issue of the problem. But if you create the service in android studio using default template it automatically adds these properties to the service.
For me, the solution was to set the flags of the intent :
resultIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TASK);

Stopping a background service from notification

I have a background service in which I want to show a notification which allows the user to stop it.
In the android SDK docs it says an activity is used to normally launch an Activity. So I am wondering if I need to create an activity to stop the service or can I directly stop the service when user selects the notification,
So how would the intend call back the service to stop it..
Thanks,
So I am wondering if I need to create an activity to stop the service or can I directly stop the service when user selects the notification,
You cannot directly stop the service from a Notification. You can start the service, using an Intent that has an action string or extra or something that the service sees in onStartCommand() and triggers it to call stopSelf().
The question is already old, but since there is still no solution with code, I simply share my code as an example for solving the problem:
You cannot directly stop the service from a Notification. You can
start the service, using an Intent that has an action string or extra
or something that the service sees in onStartCommand() and triggers it
to call stopSelf().
That's the right solution so let's jump in code (this code is all in your ExampleService class):
#RequiresApi(Build.VERSION_CODES.O)
private void startForegroundService() {
// create PendingIntend to open MainActivity (this is when the notification gets clicked) //
Intent tabIntent = new Intent(this, MainActivity.class);
tabIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent tabPendingIntent = PendingIntent.getActivity(this, 0, tabIntent, 0);
// create PendingIntend to open ExampleService (this is when the notification BUTTON gets clicked) //
Intent closeIntent = new Intent(this, ExampleService.class);
closeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
closeIntent.putExtra("destroyCode", 666); // this is the important line //
PendingIntent closePendingIntent = PendingIntent.getService(this, 0, closeIntent, 0);
createNotificationChannel(); // this is only the default code to create notification channel. I just outsourced? it //
Now the Intent has additional data (the "destroy code" -> 666). Notice that we have created 2 pendingIntents: closePendingIntent (stop Service) and tabPendingIntent (start Activity)
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
// get extras to know if Intent has destroyCode (666)
Bundle extras = intent.getExtras();
if (extras == null) {
// extras is null which means there is no destroyCode (666)
exampleMethod();
} else {
// Intent has destroyCode (666) -> Intent comes from notification -> stop the service and close notification
stopSelf();
}
return START_STICKY;
}
Now we have the code to check if there is a destroyCode or not. The last step is to create a notification with a button:
// set attributes for notification //
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "channelID_2");
Notification notification = builder.setOngoing(true)
.setSmallIcon(R.drawable.example)
.setContentTitle(getText(R.string.notificationTitle))
.setContentText(getText(R.string.notificationText))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(tabPendingIntent) //this is when notification is clicked which only opens ExampleActivity
.addAction(R.drawable.example, getString(R.string.notificationButtonText), closePendingIntent) // here is our closePendingIntent with the destroyCode .addAction is "the onClickListener for the notification button"//
.build();
startForeground(2, notification);
In onCreate you start your service
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O)
startForegroundService();
else
startForeground(1, new Notification());
// Toast Message that service has started
Toast.makeText(this, R.string.serviceStarted, Toast.LENGTH_SHORT).show();
That's it
You can't start an Acitivty from a Service just like that. What you can do is create a callback to an Activity in the Service and let the callback start new activities. But having a notification means you don't have to go through the Service. When the notification is clicked, you can start an activity that's specified in the Intent you supply to the notification. It's really very simple.
Do read the reference docs on notifications for examples.

How to stop multiple instances of an Android Service?

I am still working on my location-based alarm android application. I have an AlarmService class that starts notifications and proximity alerts. I am starting this service with:
startService(intentAlarmService);
I try to stop the service using:
Intent intentAlarmService = new Intent(this, AlarmService.class);
stopService(intentAlarmService);
This is what happens: The service stops, but then, when I start another instance of the service (i.e. exit the app, launch the app, start the service) - I discover (through Toasts) that the previous instances of the service are still running. For example, in the AlarmService class, there is a LocationListener with the onLocationChanged method. So, in this method, I put:
Toast.makeText(AlarmService.this, "AlarmTitle: " + mAlarmTitle, Toast.LENGTH_SHORT).show();
And when I re-start the service, Toasts keep showing up with the previous AlarmTitles, and the current AlarmTitle.
So, something is not working when I try to stop the AlarmService - what could this be?
Note: when I re-install the application, the service stops for real. Then when I start the service, only the current AlarmTitle shows in the Toast (I want this to happen every time).
Something is wrong with my service. Any ideas what I can do?
thanks.
CODE FROM MY APP:
public void onDestroy() {
super.onDestroy();
Intent alarmIntent = new Intent(getApplicationContext(), AlarmReceiver.class);
PendingIntent pendingIntentAlarm = PendingIntent.getBroadcast(getApplicationContext(), PENDING_INTENT_REQUEST_CODE1, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);
pendingIntentAlarm.cancel();
Intent intentAlarmService = new Intent(getApplicationContext(), AlarmService.class);
stopService(intentAlarmService);
mNtf.cancel(NOTIFICATION_ID1);
mNtf.cancelAll();
}
I discover (through Toasts) that the previous instances of the service are still running.
My guess is that you are leaking services, probably by failing to call removeUpdates() to detach your LocationListener. There is only one true running copy of the service (from an Android lifecycle standpoint), but you are preventing the other services from being garbage collected through your leak.
Also, please replace all occurrences of getApplicationContext() with this.
test this :
private Boolean Tb = true;
if(condition)
{
if(Tb)
{
Toast.makeText(getApplicationContext(),"content...", Toast.LENGTH_LONG).show();
}
Tb =false;
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
Tb =true;
}
}, Toast.LENGTH_LONG);
}
}

Categories

Resources