Stopping a background service from notification - android

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.

Related

Open activity from foreground notification when firebase data has changed

I feel like I am doing something stupid here. I have set up a service which listens to changes in a collection in my firebase database, and when there has been a change, the app is meant to open, except the activity doesn't open. Both the log message and the toast appear when the data is changed in the collection, but the activity doesn't open. The code from the onStartCommand is below.
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
String input = intent.getStringExtra("inputExtra");
context = getApplicationContext();
Intent notificationIntent = new Intent(context, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Example Service")
.setContentText(input)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
reference.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot value, #Nullable FirebaseFirestoreException error) {
for (DocumentChange documentChange : value.getDocumentChanges()) {
if (documentChange.getType() == DocumentChange.Type.MODIFIED) {
Log.d(TAG, "onComplete: reference modified");
Toast.makeText(context, "message received", Toast.LENGTH_SHORT).show();
Intent intent1 = new Intent(context, Open.class);
intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent1);
}
}
}
});
return START_STICKY;
}
Any help would be greatly appreciated.
Android 10 (API level 29) and higher place restrictions on when apps can start activities when the app is running in the background. These restrictions help minimize interruptions for the user and keep the user more in control of what's shown on their screen.
For the purposes of starting activities, an app running a foreground service is still considered to be "in the background"
Alternatives to display activity
Apps that are in the background should display time-sensitive notifications to provide urgent information to the user instead of directly starting an activity.
Exceptions to the restrictions:
There are some exceptions in which app can display activity directly, some of those are:
The app has a visible window, such as an activity in the foreground.
The app has an activity in the back stack of the foreground task.
The app has an activity in the back stack of an existing task on the
Recents screen.
For more detailed articles, read this

Have to start Android child services also in foreground?

In my Android app, I record a lot of things like GPS, barometric pressure, Bluetooth sensors. There is a "super" service which is started in the foreground:
Intent intent = new Intent(context, DataCollectorService.class);
ContextCompat.startForegroundService(context, intent);
The service itself has to start numerous child services like GPS-recording, Bluetooth sensor recording and the like. And the service creates a notification:
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// . . .
Notification notification = new NotificationCompat.Builder(this, channelId)
// . . .
.build();
startForeground(NOTIFICATION_ID, notification);
mHandler.postDelayed(this::startChildServices, 100);
return START_STICKY;
}
private void startChildServices() {
// . . .
if (gpsEnabled) {
Intent intent = new Intent(this, GpsTrackingService.class);
bindService(intent, mGpsServiceConnection, Context.BIND_AUTO_CREATE);
startService(intent);
}
if (isPressureEnabled) {
Intent intent = new Intent(this, PressureService.class);
bindService(intent, pressureConnection, Context.BIND_AUTO_CREATE);
startService(intent);
}
// . . .
}
All works well, but a very small percentage of the users encounter crashes like this:
android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
Actually, I start the "super" service in the foreground, as you can see.
Do I have to start the child services in the foreground as well?
Obviously, I don't want to have 3-7 notification icons in the users status bar.

Dispatching notification in correct activity

I have a problem with notification on Android (Xamarin).
My scenario is this:
I have a class handling (inheriting GcmServiceBase) the message and creating the Notification object.
In this class I override OnMessage method in this way:
protected override void OnMessage(Context context, Intent intent)
{
if (intent != null || intent.Extras != null)
{
string messageText = intent.Extras.GetString("message");
string messageTitle = intent.Extras.GetString("title");
Intent app_launch_intent = new Intent(context, typeof(Project.WaitForm));
if (App.Instance == null)
{
Console.WriteLine("GCM: Notification received while application not running...");
app_launch_intent.AddFlags(ActivityFlags.ClearTop);
app_launch_intent.AddFlags(ActivityFlags.SingleTop);
}
else if ((App.Instance != null) && (App.Instance.mainActivity.IsInBackground))
{
App.Instance.Logger.Write("GCM: Notification received while application in background...", LogType.Default, LogLevel.Info);
app_launch_intent = new Intent(context, App.Instance.mainActivity.GetType());
}
else
{
App.Instance.Logger.Write("GCM: Notification received while application in foreground...", LogType.Default, LogLevel.Info);
app_launch_intent = new Intent(context, App.Instance.mainActivity.GetType());
app_launch_intent.AddFlags(ActivityFlags.SingleTop);
}
app_launch_intent.PutExtras(intent.Extras);
app_launch_intent.PutExtra("isNotify", true);
PendingIntent pendingIntent = PendingIntent.GetActivity(this, PushService.notificationId, app_launch_intent, PendingIntentFlags.OneShot);
createNotification(context, app_launch_intent, pendingIntent, messageTitle, messageText);
if (App.Instance == null)
{
Console.WriteLine("GCM: Notification object correctly created.");
}
else
{
App.Instance.Logger.Write("GCM: Notification object correctly created.", LogType.Default, LogLevel.Info);
}
}
}
public void createNotification(Context context, Intent result_intent, PendingIntent pendingIntent, string title, string desc)
{
NotificationManager notificationManager = GetSystemService(Context.NotificationService) as NotificationManager;
Notification.Builder builder = new Notification.Builder(this)
.SetContentIntent(pendingIntent)
.SetAutoCancel(true)
.SetContentTitle(title)
.SetContentText(desc)
.SetSmallIcon(Resource.Drawable.notify_icon_transparent)
.SetLargeIcon(PushService.IconAgenda)
.SetTicker(title);
Notification notification = builder.Build();
PushService.notificationId += 1;
notificationManager.Notify(PushService.notificationId, notification);
}
as you can see, when message is received I test if application object exists then I create Intent and notification object (App.Instance is a singleton holding informations about the app itself. mainActivity is the current activity shown on screen).
In this way when notification is clicked, last activity on the top of the stack (current on-screen activity if app is in foreground or last on-screen activity before app went in background) will come
up and something in it will take care about notification itself, called by OnNewIntent.
Everything is working fine except for a thing:
Let's say I have 3 Activity called A, B, C.
I start the app and then I receive 2 different notifications when current activity on screen is A.
Both will be displayed in the top bar without problems.
I click on the first of them and it asks me to open another activity (catched in OnNewEvent of current activity and due to notification type), so I say "yes" and I will have a transition to activity C.
Once reached activity C and after done some work, I click on the other notification.
Clicking on it I will be carried to activity A and then notification will be handled.
I know this "problem" is because at notify creation I use the App.Instance.mainActivity.GetType() to create intent that will be use to create PendingIntent and that at that time was activity A for both.
So my question is:
How can I handle second notify click in last activity on screen (so in activity C) instead of activity A?
(I add Xamarin tag just because code above is in C# and not in Java so it may sound strange to a native Android developer)

Resume singleTask activity

I am trying to "resume" a single task activity so it appears in the foreground when a user clicks my notification. (Same behavior as if the user tapped on the app icon from the applications menu.)
My notification creates a PendingIntent which broadcasts an action that is received by my broadcast receiver. If the app is in not in the foreground, I try to resume the app. Additionally, I'm trying to pass a message to my onResume function through the intent. However, I'm hitting an error:
Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
Despite this error, my app is being resumed...don't understand why. However, my extras are not being passed to my onResume function.
So first I create a notification.
public static class MyNotificationCreator {
private static final int MY_NOTIFICATION_ID = 987;
public static void createNotification(Context context) {
Intent openAppIntent = new Intent(context, MyReceiver.class);
openAppIntent.setAction("PleaseOpenApp");
PendingIntent pi = PendingIntent.getBroadcast(context, /*requestCode*/0, openAppIntent, /*flags*/0);
Notification notification = ne Notification.Builder(context)
.setContentTitle("")
.setContentText("Open app")
.setSmallIcon(context.getApplicationInfo().icon)
.setContentIntent(pi)
.build();
NotificationManager notificationManager = (NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(MY_NOTIFICATION_ID, notification); }
}
Which broadcasts "PleaseOpenApp" for MyReceiver.
public class MyReceiver extends BroadcastReceiver {
#Override
public void onRecieve(Context context, Intent intent) {
if (intent.action() == "PleaseOpenApp" && !MyPlugin.isForeground) {
PackageManager pm = context.getPackageManager();
//Perhaps I'm not supposed to use a "launch" intent?
Intent launchIntent = pm.getLaunchIntentForPackage(context.getPackageName());
//I'm adding the FLAG_ACTIVITY_NEW_TASK, but I'm still hitting an error saying my intent does not have the FLAG_ACTIVITY_NEW_TASK...
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
launchIntent.putExtra("foo", "bar");
context.startActivity(launchActivity);
} else {
//do other stuff
}
}
}
My plugin keeps track of whether or not we're in the foreground. Also, it tries to get "food" after my receiver attempts to start the app.
public class MyPlugin extends CordovaPlugin {
public static boolean isForeground = false;
#Override
public void initialize(CordovaInterface cordova, CordovaWebView webview) {
super.initialize(cordova, webview);
isForeground = true;
}
#Override
public void onResume(boolean multitasking) {
isForeground = true;
String foo = activity.getIntent().getStringExtra("foo");
Log.d("MyPlugin", foo); //foo is null after clicking the notification!
}
#Override
public void onPause(boolean multitasking) {
isForeground = false;
}
#Override
public void onDestroy() {
isForeground = false;
}
}
Note: because I'm using cordova my activity has a singleTask launchMode.
Also, I'm new to Android development so any help about resuming activities not in the foreground vs resuming activities that have been destroyed and info about general concepts / best practices that I'm not understanding would be appreciated!
I don't think your Broadcast/Broadcast Receiver pattern is necessary.
Intents can be used to directly launch an activity, and when you build the Intent, you can add the extras. Then, your activity onResume() can extract them directly.
Here is a sample Intent and PendingIntent construction that can be sent in a notification:
Intent startActivity = new Intent(context, MyActivity.class);
// You can experiment with the FLAGs passed here to see what they change
startActivity.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra("Extra1", myExtra1)
.putExtra("Extra2", myExtra2)
// ADDING THIS MAKES SURE THE EXTRAS ATTACH
.setAction("SomeString");
// Then, create the PendingIntent
// You can experiment with the FLAG passed here to see what it changes
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, startActivity, PendingIntent.FLAG_UPDATE_CURRENT);
// Then, create and show the notification
Notification notif = new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.my_small_icon)
.setContentTitle(myTitle)
.setContentText(myContent)
.setOngoing(isOngoingNotif)
.setAutoCancel(shouldAutoCancel)
.setOnlyAlertOnce(shouldAlertOnce)
.setContentIntent(pendingIntent)
.build();
NotificationManagerCompat manager = NotificationManagerCompat.from(context);
manager.notify(MY_NOTIFICATION_ID, notif);
In your code you are using a "launch Intent" to resume your application. You've added "extras" to the Intent but they will never be seen.
If your app is running, but in the background, and you call startActivity() with a "launch Intent", all this does it bring your task from the background to the foreground. It does not deliver the Intent to the Activity!.
A "launch Intent" does exactly the same thing as when you press the app icon of an app on the HOME screen (if it is already running, but in the background). This just brings the existing task in its current state, from the background to the foreground.
If you want to delivery "extras" to your app, you cannot use a "launch Intent". You must use a regular 'Intent. Depending on your architecture, you could either start a newActivity(which would get the "extras" inonCreate(), or you could start an existingActivity(which would get the "extras" inonNewIntent()`.

How to execute a method by clicking a notification

I have an application with two buttons. One button that "closes" the application and one that begins the algorithm. When I click "begin" it "hides" the application and displays a notification in the notification bar. I need to be able to execute/call a method when the notification is clicked/pressed. There are a few answers for this sort of question, but they are incredibly vague and one only points to a link to the doc on BroadcastReceiver.
If you are going to leave a url to the BroadcastReceiver doc and say "read this page," please don't reply to this question. If you are going to explain how I could use BroadcastReceiver to execute a method (from within the same class that displayed the notification), please show me some code for how this could be done.
My algorithm: press a button, display notification, click notification, call a method (don't display activity). That's it.
If it's not possible, just let me know. If it is, please show me what you would do to make it possible. Something this simple shouldn't have been overlooked by the developers of the android sdk.
After several iterations of trial and error, I finally found a fairly straightforward and clean way to run an arbitrary method when a notification's action is clicked. In my solution, there is one class (I'll call it NotificationUtils) that creates the notification and also contains an IntentService static inner class that will run when actions on the notification are clicked. Here is my NotificationUtils class, followed by the necessary changes to AndroidManifest.xml:
public class NotificationUtils {
public static final int NOTIFICATION_ID = 1;
public static final String ACTION_1 = "action_1";
public static void displayNotification(Context context) {
Intent action1Intent = new Intent(context, NotificationActionService.class)
.setAction(ACTION_1);
PendingIntent action1PendingIntent = PendingIntent.getService(context, 0,
action1Intent, PendingIntent.FLAG_ONE_SHOT);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle("Sample Notification")
.setContentText("Notification text goes here")
.addAction(new NotificationCompat.Action(R.drawable.ic_launcher,
"Action 1", action1PendingIntent));
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
}
public static class NotificationActionService extends IntentService {
public NotificationActionService() {
super(NotificationActionService.class.getSimpleName());
}
#Override
protected void onHandleIntent(Intent intent) {
String action = intent.getAction();
DebugUtils.log("Received notification action: " + action);
if (ACTION_1.equals(action)) {
// TODO: handle action 1.
// If you want to cancel the notification: NotificationManagerCompat.from(this).cancel(NOTIFICATION_ID);
}
}
}
Now just implement your actions in onHandleIntent and add the NotificationActionService to your manifest within the <application> tags:
<service android:name=".NotificationUtils$NotificationActionService" />
Summary:
Create a class that will create the notification.
Inside that class, add a IntentService inner classes (make sure it is static or you will get a cryptic error!) that can run any method based on the action that was clicked.
Declare the IntentService class in your manifest.
On Notification click we can't get any fire event or any click listener. When we add notification in notification bar, we can set a pending intent, which fires an intent (activity/service/broadcast) upon notification click.
I have a workound solution for you, if you really don't want to display your activity then the activity which is going to start with pending intent send a broad cast from there to your parent activity and just finish the pending activity and then once broadcast receiver receives in parent activity call whatever method you want inside the receiver. For your reference..
// This is what you are going to set a pending intent which will start once
// notification is clicked. Hopes you know how to add notification bar.
Intent notificationIntent = new Intent(this, dummy_activity.class);
notificationIntent.setAction("android.intent.action.MAIN");
notificationIntent.addCategory("android.intent.category.LAUNCHER");
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT |
Notification.FLAG_AUTO_CANCEL);
// Now, once this dummy activity starts send a broad cast to your parent activity and finish the pending activity
//(remember you need to register your broadcast action here to receive).
BroadcastReceiver call_method = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action_name = intent.getAction();
if (action_name.equals("call_method")) {
// call your method here and do what ever you want.
}
};
};
registerReceiver(call_method, new IntentFilter("call_method"));
}
}

Categories

Resources