IntentService onHandleEvent( ) not starting - android

I want some methods to execute when I click on Notification Action Button.
I have searched on this site, but everything seems to be in order and my IntentService is not being called.
My Action-Button Intent
Intent off = new Intent();
off.setAction("action");
off.putExtra("test", "off");
PendingIntent pOff = PendingIntent.getService(context, 22, off, 0);
Notification Builder
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context)
.setSmallIcon(/**/)
.setContentTitle(/**/)
.setContentText(/**/)
.addAction(/**/, "Off", pOff)
.setContentIntent(pendingIntent)
.setDefaults(Notification.DEFAULT_SOUND)
.setAutoCancel(true);
Intent Service Class
public class NotificationServiceClass extends IntentService {
public NotificationServiceClass(String name) {
super(name);
}
public NotificationServiceClass () {
super("NotificationServiceClass");
}
#Override
protected void onHandleIntent(Intent intent) {
Log.i("test", "onHandle");
if (intent.getAction().equals("action")) {
Log.i("test", "action");
Bundle bundle = intent.getExtras();
if (bundle != null) {
Log.i("test", "onHandleBundleNotNull");
if (bundle.containsKey("test")) {
Log.i("test", bundle.getString("test"));
}
}
}
}
}
XML Declaration for Service class
<service
android:name=".Manager.NotificationServiceClass"
android:exported="false">
</service>

Per the Intents and Intent Filters training, the Intent you've built is an implicit Intent:
Implicit intents do not name a specific component, but instead declare a general action to perform, which allows a component from another app to handle it. For example, if you want to show the user a location on a map, you can use an implicit intent to request that another capable app show a specified location on a map.
What you actually want is an explicit intent: one that specifies the component to start by name as per the note on the same page:
Note: When starting a Service, you should always specify the component name. Otherwise, you cannot be certain what service will respond to the intent, and the user cannot see which service starts.
When constructing your Intent, you should use
// Note how you explicitly name the class to use
Intent off = new Intent(context, NotificationServiceClass.class);
off.setAction("action");
off.putExtra("test", "off");
PendingIntent pOff = PendingIntent.getService(context, 22, off, 0);

In looking at your code, I do not see you telling the PendingIntent what class to use for your service.
You should add:
off.setClass(this, NotificationServiceClass.class);
Otherwise the PendingIntent has nothing to do.

Related

Notification Action without starting new Activity?

I plan to have a heads up notification that has two Actions: one to Approve a login request and one to Decline a login request. By clicking on either of these actions I wish to fire off a HTTP request to my server and most importantly do not want to start a new Activity or have the user redirected to my app at all.
Context context = getBaseContext();
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context)
.setSmallIcon(R.mipmap.notificationicon)
.setContentTitle(notificationTitle)
.setContentText("Access Request for " + appName + " : " + otp)
.setDefaults(Notification.DEFAULT_ALL)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.addAction(R.drawable.ic_tick, "Approve", someApproveIntent? );
Here is my notification builder and after looking around it seems that the addAction method is looking for a new/pendingIntent, which is confusing me as I cannot find any examples online where Intents do not lead to new Activities being fired off.
How would I implement some code (a method maybe) rather then starting a new Activity on each of my Actions?
If you don't want to start an activity you can also wrap a BroadcastReceiver or a Service directly in a PendingIntent.
Wherever you build your notification...
Your notification actions will start a service directly.
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)...
Intent iAction1 = new Intent(context, MyService.class);
iAction1.setAction(MyService.ACTION1);
PendingIntent piAction1 = PendingIntent.getService(context, 0, iAction1, PendingIntent.FLAG_UPDATE_CURRENT);
builder.addAction(iconAction1, titleAction1, piAction1);
// Similar for action 2.
MyService.java
IntentServices run in a row one after another. They do the work on a worker thread.
public class MyService extends IntentService {
public static final String ACTION1 = "ACTION1";
public static final String ACTION2 = "ACTION2";
#Override
public void onHandleIntent(Intent intent) {
final String action = intent.getAction();
if (ACTION1.equals(action)) {
// do stuff...
} else if (ACTION2.equals(action)) {
// do some other stuff...
} else {
throw new IllegalArgumentException("Unsupported action: " + action);
}
}
}
AndroidManifest.xml
Don't forget to register the service in manifest.
<manifest>
<application>
<service
android:name="path.to.MyService"
android:exported="false"/>
</application>
</manifest>

Android - Trouble with service sending multiple local notifications

I've inherited a code base for an Android app and I'm facing a particularly though problem with local notifications.
The idea is to send a notification for each event which is scheduled in the future, considering also the reminder preference on how many minutes before the event the user wants to be notified.
Everything works just fine, except that after the notification is thrown for the first time, if the user opens the app before the event starts, the notification gets thrown another time. This happens every time the app is opened between (event start date - reminder) and event start date.
I've already gave a look at this and also this with no luck.
I've read that using a service may cause exactly this problem and some suggest to remove it but I think this is needed since the notification must be thrown also when the app is closed.
Currently the structure of the code is the following:
Edit - updated description of TabBarActivity
Inside TabBarActivity I have the method scheduleTravelNotification that schedules the AlarmManager.
This method is executed everytime there is a new event to be added on local database, or if an existing event have been updated.
The TabBarActivity runs this method inside the onCreate and onResume methods.
TabBarActivity is also the target of the notification - onclick event.
private static void scheduleTravelNotification(Context context, RouteItem routeItem) {
long currentTime = System.currentTimeMillis();
int alarmTimeBefore = routeItem.getAlarmTimeBefore();
long alarmTime = routeItem.getStartTime() - (alarmTimeBefore * 1000 * 60);
if(alarmTimeBefore < 0){
return;
}
if(alarmTime < currentTime){
return;
}
Intent actionOnClickIntent = new Intent(context, TravelNotificationReceiver.class);
PendingIntent travelServiceIntent = PendingIntent.getBroadcast(context, System.currentTimeMillis(), actionOnClickIntent, PendingIntent.FLAG_ONE_SHOT);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(alarmTime);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), travelServiceIntent);
Log.e("NEXT ALARM", "Time: " + String.valueOf(calendar.getTimeInMillis()));
}
This is TravelNotificationReceiver.java (should I use LocalBroadcastReceiver instead of BroadcastReceiver?)
public class TravelNotificationReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Log.e("RECEIVER", "received TravelNotification request");
Intent notificationIntent = new Intent(context, TravelNotificationService.class);
context.startService(notificationIntent);
}
}
TravelNotificationService.java extends NotificationService.java setting as type = "Travel", flags = 0, title = "something" and text = "something else".
public abstract class NotificationService extends Service {
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
sendNotification();
return super.onStartCommand(intent, flags, startId);
}
public abstract String setNotificationType();
public abstract int setNotificationFlags();
public abstract String setNotificationTitle();
public abstract String setNotificationText();
/**
* Executes all the logic to init the service, prepare and send the notification
*/
private void sendNotification() {
int flags = setNotificationFlags();
String type = setNotificationType();
NotificationHelper.logger(type, "Received request");
// Setup notification manager, intent and pending intent
NotificationManager manager = (NotificationManager) this.getApplicationContext().getSystemService(this.getApplicationContext().NOTIFICATION_SERVICE);
Intent intentAction = new Intent(this.getApplicationContext(), TabBarActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this.getApplicationContext(), 0, intentAction, flags);
// Prepares notification
String title = setNotificationTitle();
String text = setNotificationText();
Notification notification = NotificationHelper.buildNotification(getApplicationContext(), title, text, pendingIntent);
// Effectively send the notification
manager.notify(101, notification);
NotificationHelper.logger(type, "Notified");
}
}
Edit - Here's the code for NotificationHelper.buildNotification
public static Notification buildNotification(Context context, String title, String text, PendingIntent pendingIntent) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setAutoCancel(true);
builder.setContentText(text);
builder.setContentTitle(title);
builder.setContentIntent(pendingIntent);
builder.setSmallIcon(R.mipmap.launcher);
builder.setCategory(Notification.CATEGORY_MESSAGE);
builder.setVisibility(Notification.VISIBILITY_PUBLIC);
return builder.build();
}
Thank you for the answers!
Edit I've seen also this but has no accepted answers, while this post suggest something that I think it's already managed with if(alarmTime < currentTime){ return; } in scheduleTravelNotification.
This may not be your exact problem, but at a glance, you're sending the notification in onStartCommand() which can itself be run many times during the lifetime of the service -- for example, if you issue the service start command "blindly" in an onCreate of an activity, it will happen every time the activity is (re)created.
You have a few options for handling this.
One is to create a boolean flag as a property of the service, default to false, and check it before sending the notification. If it's false, send the notification and set it to true, and if it's already true you do not send a notification.
Another is to check and see if the service is already running, and if it is, don't send the service start command in the first place. This can be tedious to do everywhere, and violates DRY, so if you take this route you may want to create a static method in your service class which checks to see if the service is running and then starts it if not, and call that instead of explicitly starting the service.
Similar to user3137702 answer you could simple have a static boolean of APPISINFORGROUND which is checked everytime the send notification method is hit, and managed from your application/activities code.
As User said it is likely that your onStartCommand method is being called at odd times due to the app / service lifecycle.
Alternatively check your receiver is not being called somewhere else from your code.
It may be your NotificationHelper class which is causing an issue. Please share the code for this class.
One thought may be that your notification is not set to be auto cancelled, check if you include the setAutoCancel() method in your Notification Builder.
Notification notification = new Notification.Builder(this).setAutoCancel(true).build();
I've found a way to make it work, I'm posting this since it seems to be a problem of many people using the approach suggested in this and this articles. After months of testing I can say I'm pretty satisfied with the solution I've found.
The key is to avoid usage of Services and rely on AlarmScheduler and Receivers.
1) Register the receiver in your manifest by adding this line:
<receiver android:name="<your path to>.AlarmReceiver" />
2) In your activity or logic at some point you want to schedule a notification related to an object
private void scheduleNotification(MyObject myObject) {
// Cal object to fix notification time
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(myObject.getTime());
// Build intent and extras: pass id in case you need extra details in notification text
// AlarmReceiver.class will receive the pending intent at specified time and handle in proper way
Intent intent = new Intent(this, AlarmReceiver.class);
intent.putExtra("OBJECT_ID", myObject.getId());
// Schedule alarm
// Get alarmManager system service
AlarmManager alarmManager = (AlarmManager) getApplicationContext().getSystemService(getBaseContext().ALARM_SERVICE);
// Build pending intent (will trigger the alarm) passing the object id (must be int), and use PendingIntent.FLAG_UPDATE_CURRENT to replace existing intents with same id
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), myObject.getId(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
// Finally schedule the alarm
alarmManager.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pendingIntent);
}
3) Define AlarmReceiver
public class AlarmReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// Find object details by using objectId form intent extras (I use Realm but it can be your SQL db)
MyObject myObject = RealmManager.MyObjectDealer.getObjectById(intent.getStringExtra("OBJECT_ID"), context);
// Prepare notification title and text
String title = myObject.getSubject();
String text = myObject.getFullContent();
// Prepare notification intent
// HomeActivity is the class that will be opened when user clicks on notification
Intent intentAction = new Intent(context, HomeActivity.class);
// Same procedure for pendingNotification as in method of step2
PendingIntent pendingNotificationIntent = PendingIntent.getActivity(context, myObject.getId(), intentAction, PendingIntent.FLAG_UPDATE_CURRENT);
// Send notification (I have a static method in NotificationHelper)
NotificationHelper.createAndSendNotification(context, title, text, pendingNotificationIntent);
}
}
4) Define NotificationHelper
public class NotificationHelper {
public static void createAndSendNotification(Context context, String title, String text, PendingIntent pendingNotificationIntent) {
// Get notification system service
NotificationManager notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
// Build notification defining each property like sound, icon and so on
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context);
notificationBuilder.setContentTitle(title);
notificationBuilder.setContentText(text);
notificationBuilder.setSmallIcon(R.drawable.ic_done);
notificationBuilder.setCategory(Notification.CATEGORY_MESSAGE);
notificationBuilder.setVisibility(Notification.VISIBILITY_PUBLIC);
notificationBuilder.setAutoCancel(true);
notificationBuilder.setContentIntent(pendingNotificationIntent);
notificationBuilder.setDefaults(Notification.DEFAULT_SOUND);
notificationManager.notify(1001, notificationBuilder.build());
}
}
At this point it should work and schedule / trigger notification at the right time, and when notification is opened it will appear only once starting the activity declared in notification pending intent.
There is still a problem, AlarmManager have a "volatile" storage on user device, so if user reboots or switch off the phone you will lose all intents that you previously scheduled.
But fortunately there is also a solution for that:
5) Add at top of your manifest this uses permission
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
6) Right below the line added at step 1 register the boot receiver
<receiver android:name="<your path to>.BootReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
7) Define the BootReceiver
public class BootReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// Do something very similar to AlarmReceiver but this time (at least in my case) since you have no source of intents loop through collection of items to understand if you need to schedule an alarm or not
// The code is pretty similar to step 3 but repeated in a loop
}
}
At this point your app should be able to schedule / trigger notification and restores those reminders even if the phone is switched off or rebooted.
Hope this solution will help someone!

Android, Display alertDialog instead of notification when app is open

I followed this developer tutorial, and have Geofencing working within my app, as expected.
A notification is sent when a Geofence Transition occurs, from within an IntentService:
#Override
protected void onHandleIntent(Intent intent) {
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
...
sendNotification(geofenceTransitionDetails);
}
private void sendNotification(String notificationDetails) {
// Create an explicit content Intent that starts the main Activity.
Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class);
// Construct a task stack.
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// Add the main Activity to the task stack as the parent.
stackBuilder.addParentStack(MainActivity.class);
// Push the content Intent onto the stack.
stackBuilder.addNextIntent(notificationIntent);
// Get a PendingIntent containing the entire back stack.
PendingIntent notificationPendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Get a notification builder that's compatible with platform versions >= 4
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
// Define the notification settings.
builder.setSmallIcon(R.mipmap.ic_launcher)
// In a real app, you may want to use a library like Volley
// to decode the Bitmap.
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.mipmap.ic_launcher))
.setColor(Color.RED)
.setContentTitle(notificationDetails)
.setContentText("Return to app")
.setContentIntent(notificationPendingIntent);
// Dismiss notification once the user touches it.
builder.setAutoCancel(true);
// Get an instance of the Notification manager
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Issue the notification
mNotificationManager.notify(0, builder.build());
}
This is cookie-cutter from the tutorial. The intent is set-up in the Main activity:
private PendingIntent getGeofencePendingIntent() {
// Reuse the PendingIntent if we already have it.
if (mGeofencePendingIntent != null) {
return mGeofencePendingIntent;
}
Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
// addGeofences() and removeGeofences().
return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
How can I add functionality that suppresses the notifications if the app is open, and instead displays an AlertDialog to the user? Ideally, I'd like to be able to execute different tasks, depending on which view the user is currently in when the Geofence Transition occurs. Can I monitor/intercept the transition from within each view, or somehow globally?
Thanks in advance.
Some of the answers were incomplete, and so here is the complete solution to what I was looking for.
First off, set up MyApplication class, that implements ActivityLifecycleCallbacks:
public class MyApplication extends Application implements Application.ActivityLifecycleCallbacks {
private static boolean isActive;
#Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(this);
}
public static boolean isActivityVisible(){
return isActive;
}
#Override
public void onActivityResumed(Activity activity) {
isActive = true;
}
#Override
public void onActivityPaused(Activity activity) {
isActive = false;
}
... no other methods need to be used, but there are more that
... must be included for the ActivityLifecycleCallbacks
}
Be sure to name this in your manifest (only name line was added, rest is default):
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme"
android:hardwareAccelerated="true">
What was done above is used to track the lifecycle of your app. You can use this to check if your app is currently in the foreground or not.
Next is to set up a BroadcastReceiver, wherever you would like code to run (in the event that the app is open when the trigger occurs). In this case, it is in my MainActivity:
protected BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
... Do whatever you want here
Toast.makeText(...).show();
}
};
Register the receiver in your onCreate of the same activity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
LocalBroadcastManager.getInstance(this).registerReceiver(mNotificationReceiver, new IntentFilter("some_custom_id"));
}
And don't forget to unregister it:
#Override
protected void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mNotificationReceiver);
super.onDestroy();
}
When a broadcast is received, the code within the receiver is executed.
Now, to check if the app is in the foreground, and send a broadcast if it is. Inside of the IntentService:
#Override
protected void onHandleIntent(Intent intent) {
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError()) {
String errorMessage = getErrorString(this,
geofencingEvent.getErrorCode());
return;
}
int geofenceTransition = geofencingEvent.getGeofenceTransition();
// Test that the reported transition was of interest.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
...
if(MyApplication.isActivityVisible()){
Intent intnt = new Intent("some_custom_id");
intnt.putExtra("message", geofenceTransitionDetails);
LocalBroadcastManager.getInstance(this).sendBroadcast(intnt);
}else{
sendNotification(geofenceTransitionDetails);
}
} else {
// Log the error.
}
}
The important bit is the last nested if-statement:
if(MyApplication.isActivityVisible()){
Intent intnt = new Intent("some_custom_id");
intnt.putExtra("message", geofenceTransitionDetails);
LocalBroadcastManager.getInstance(this).sendBroadcast(intnt);
}else{
sendNotification(geofenceTransitionDetails);
}
Check if the app is in the foreground using MyApplication.isActivityVisible(), as defined above, and then either send the notification, or send a broadcast. Just make sure that your intent code (i.e. "some_custom_id") matches on your sender and receiver.
And that's about it. If the app is in the foreground (specifically the MainActivity), I execute some code. If the app is not in the foreground, I send a notification.
The easiest way would be to use LocalBroadcastManager or some event bus.
So when transition happens you should send local broadcast from IntentService and catch it with some component X in between IntentService and any of your Activity's. Component X must track if any of your Activity's is in foreground and
if yes - pass other local broadcast up (to the foreground Activity),
if not - show notification.
Please note that in Android you cannot track easily if your app is in foreground or not (and if you have more than 1 Activity, you cannot do it properly in my opinion) but you can try.
a) You can notify your service of the activity's lifecycle events.
b) You can keep the current state of your UI in a static field in the activity and check it from the service before showing the notification.

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