Android - Trouble with service sending multiple local notifications - android

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!

Related

AlarmManager getting killed with the app

I created an app to send message using alarm manager but if i put an alarm for a long duration the app is killed by android automatically, so i need to prevent the app from getting killed.Please tell me how can I do it.
Calendar cal = Calendar.getInstance();
int currentApiVersion = android.os.Build.VERSION.SDK_INT;
if (currentApiVersion > android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
cal.set(Calendar.MINUTE, time_picker.getMinute());
cal.set(Calendar.HOUR_OF_DAY, time_picker.getHour());
} else {
//Setting the date and time from the time picker
cal.set(Calendar.MINUTE, time_picker.getCurrentMinute());
cal.set(Calendar.HOUR_OF_DAY, time_picker.getCurrentHour());
}
//System clock time
Calendar c = Calendar.getInstance();
Long a ;//=(long) (Calendar.getInstance().get(Calendar.SECOND) * 1000);
if(cal.get(Calendar.HOUR_OF_DAY) < c.get(Calendar.HOUR_OF_DAY))
h = (cal.get(Calendar.HOUR_OF_DAY) + 24 - c.get(Calendar.HOUR_OF_DAY)) * 60;
else
h = (cal.get(Calendar.HOUR_OF_DAY) - c.get(Calendar.HOUR_OF_DAY * 60;
m = (cal.get(Calendar.MINUTE) - c.get(Calendar.MINUTE));
a = (m + h) * 60;
myIntent = new Intent(this, MyReceiver.class);
myIntent.putExtra("pos", array.select);
//Pending Intent for sending the intent afterwards
pendingIntent[array.select] = PendingIntent.getBroadcast(this.getApplicationContext(), array.select, myIntent, 0);
alarmManager[array.select] = (AlarmManager) (this.getSystemService(Context.ALARM_SERVICE));
alarmManager[array.select].set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + a * 1000, pendingIntent[array.select]);
pendingarray.add(pendingIntent[array.select]);
sms_list.Phone[array.select] = Phone;
Intent back = new Intent(this, sms_list.class);
back.putExtra("PHONE", Phone);
back.putExtra("Flag",2);
back.putExtra("MSG", Message);
back.putExtra("HOUR", (int) cal.get(Calendar.HOUR_OF_DAY));
back.putExtra("MIN", (int) cal.get(Calendar.MINUTE));
back.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(back);
If the answer is wake lock can you please tell me where to use it.
You can use a service to do it, this will also work after the device is rebooted. You also have to make the service foreground to prevent the system from killing it. It can be done by adding an ongoing notification. See the service code below.
In your Manifest add the following
<receiver
android:name=".Autostart"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
<service
android:name=".StarterService"
android:enabled="true"
android:exported="true" />
Then create a new class as follows:
public class Autostart extends BroadcastReceiver {
/**
* Listens for Android's BOOT_COMPLETED broadcast and then executes
* the onReceive() method.
*/
#Override
public void onReceive(Context context, Intent arg1) {
Log.d("Autostart", "BOOT_COMPLETED broadcast received. Executing starter service.");
Intent intent = new Intent(context, StarterService.class);
context.startService(intent);
}
}
And finally your service as follows:
public class StarterService extends Service {
private static final String TAG = "MyService";
/**
* starts the AlarmManager.
*/
#Override
public void onCreate() {
super.onCreate();
//TODO: Start ongoing notification here to make service foreground
}
#Override
public void onStart(Intent intent, int startid) {
//TODO: Put your AlarmManager code here
//TODO: you also need to add some logic to check if some previous work is pending in case of a device reboot
Log.d(TAG, "onStart");
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
//TODO: cancel the notification
Log.d(TAG, "onDestroy");
}
}
Now all you need to do is call the service whenever you need to send the message.
PS: I know an answer is accepted but hope this helps you or someone else.
An alarm should be triggered in a Broadcast Receiver.
If it performs long-lived operations, you should then use threads or Services. Both of them can be launched from a receiver.
EDIT
As a short example, I use this method in a button's onClickListener in the activity :
scheduleAlarm(name);
Method :
public void scheduleAlarm(String client)
{
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
String delay = sharedPref.getString(SettingsActivity.PREF_DELIVERY_DELAY, "48");
// time at which alarm will be scheduled here alarm is scheduled at 1 day from current time,
// we fetch the current time in milliseconds and added 1 day time
// i.e. 24*60*60*1000= 86,400,000 milliseconds in a day
Long time = new GregorianCalendar().getTimeInMillis()+ Integer.parseInt(delay) * 1000; //todo change seconds to hours
// create an Intent and set the class which will execute when Alarm triggers, here we have
// given AlarmReciever in the Intent, the onRecieve() method of this class will execute when
// alarm triggers and
//we will write the code to send SMS inside onRecieve() method pf Alarmreciever class
Intent intentAlarm = new Intent(this, AlarmReceiver.class);
intentAlarm.putExtra("CLIENT", client);
// create the object
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
//set the alarm for particular time
//todo string res
alarmManager.set(AlarmManager.RTC_WAKEUP,time, PendingIntent.getBroadcast(this,1, intentAlarm, PendingIntent.FLAG_UPDATE_CURRENT));
Toast.makeText(this, "Alarm Scheduled in " + delay + " hours", Toast.LENGTH_LONG).show();
}
And finally, the AlarmReceiver.java
package com.patrickmiller.test2;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.widget.Toast;
public class AlarmReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent)
{
Toast.makeText(context, "Alarm received", Toast.LENGTH_SHORT).show();
String client = intent.getStringExtra("CLIENT");
Notify(context, client);
}
public void Notify(Context context, String client) {
//todo expanded layout with options Fiche de contact | Rapport and cover image
//todo send name, address, phone, email and id through Intent to ContactClientActivity
//todo delete notification when generated
try {
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(context)
//todo set notification icon, content title and content text as string resources
.setSmallIcon(R.drawable.warning)
.setContentTitle(client)
.setContentText("N'oubliez pas de générer le rapport du client");
Intent resultIntent = new Intent(context, ContactClientActivity.class);
//todo may need to expend instead of calling activity. Buttons will do.
// Because clicking the notification opens a new ("special") activity, there's
// no need to create an artificial back stack.
PendingIntent resultPendingIntent =
PendingIntent.getActivity(
context,
0,
resultIntent,
PendingIntent.FLAG_UPDATE_CURRENT
);
mBuilder.setContentIntent(resultPendingIntent);
// Sets an ID for the notification
int mNotificationId = 001;
// Gets an instance of the NotificationManager service
NotificationManager mNotifyMgr = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
// Builds the notification and issues it.
mNotifyMgr.notify(mNotificationId, mBuilder.build());
}
catch(Exception e) {
Toast.makeText(context, String.valueOf(e), Toast.LENGTH_LONG).show();
}
}
}
You don't have to care about the client's thing. Just the way I scheduled the alarm..
My operation is a short-lived one, which is sending a notification. If you plan a long-lived operation, you should start a service or a thread from the receiver (onReceive callback method).
ok, your app is finished because is running in the main thread, so you need to make this process in other thread that is not killed when the app is closed. check this documentation from the official page. if you decide start using asyncTask class check this reference

Should I create 5 different receivers/services for my app?

I just started making an application aside from school and work and am not sure what route I should take. Essentially, I am building an app that calculates 5 different prayer times that change every day (the 5 prayers are named Fajr, Zuhr, Asr, Maghrib, and Isha). The calculation is done locally on the device and I found open source code for that and got it to calculate them properly. Once the getTimes() method is called, the prayer times should be calculated for that day and then recalculated once every single day after that. I'm thinking the setRepeating() method of the AlarmManager class would be good for that. How would I go about that? Once the prayer times are calculated, a service(s) should be started to create a notification at that exact time to notify the user that it is time to pray. The dilemma here is that I don't think that I should be using 5 different services/receivers to notify for each of the 5 different prayers. What would be the best way to go about this?
Currently, my app only notifies the user of Maghrib (one of the prayers) prayer time. It does not recalculate the times either.
Sorry if I am not very clear as I am new to this. I can expand more if needed.
My getTimes() method: (for the sake of simplicity I have removed the code that calculates the times)
public void getLocationTime(View v) {
//Maghrib
Calendar calMaghribTime = Calendar.getInstance();
calMaghribTime.set(Calendar.HOUR_OF_DAY, getHourOfDay(strMaghribTime));
calMaghribTime.set(Calendar.MINUTE, Integer.parseInt(strMaghribTime.substring(3,5)));
calMaghribTime.set(Calendar.SECOND, 0);
Intent myIntent = new Intent(MainActivity.this, NotificationCreatorReceiver.class);
pendingIntent = PendingIntent.getBroadcast(MainActivity.this, 0, myIntent, 0);
AlarmManager alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);
alarmManager.set(AlarmManager.RTC, calMaghribTime.getTimeInMillis(), pendingIntent);
Toast.makeText(this, "NotificationCreator onReceive()", Toast.LENGTH_SHORT).show();
} //end of getLocationTime()
Here is my receiver:
public class NotificationCreatorReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Intent service1 = new Intent(context, NotificationCreatorService.class);
context.startService(service1);
}
}
Here is my service:
public class NotificationCreatorService extends Service {
#Override
public IBinder onBind(Intent arg0)
{
// TODO Auto-generated method stub
return null;
}
#Override
public void onCreate()
{
super.onCreate();
// TODO Auto-generated method stub
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "NotificationCreator onStartCommand()", Toast.LENGTH_SHORT).show();
// Use NotificationCompat.Builder to set up our notification.
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
//icon appears in device notification bar and right hand corner of notification
builder.setSmallIcon(R.mipmap.ic_launcher);
// This intent is fired when notification is clicked
Intent intent1 = new Intent(this.getApplicationContext(),MainActivity.class);
PendingIntent pendingNotificationIntent = PendingIntent.getActivity(this.getApplicationContext(),
0, intent1, PendingIntent.FLAG_UPDATE_CURRENT);
// Set the intent that will fire when the user taps the notification.
builder.setContentIntent(pendingNotificationIntent);
// Content title, which appears in large type at the top of the notification
builder.setContentTitle("It's time for Maghrib");
// Content text, which appears in smaller text below the title
builder.setContentText("Maghrib prayer time has started in your area");
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// Will display the notification in the notification bar
notificationManager.notify(0, builder.build());
return START_STICKY;
}
#Override
public void onDestroy()
{
// TODO Auto-generated method stub
super.onDestroy();
}
}
The short answer - probably 1 service, 5 receivers. A Receiver is designed to listen for events and take quick action. A Service should be used to do all of the "heavy lifting" if necessary.
A Receiver should listen for an event and, if you only need to post a notification, then you can probably just do that and be done. But if you want to do much of anything else, it should pass an Intent to the Service with data to tell the Service how to respond.
EDIT:
Receivers have 10 seconds to do their job or else an ANR (Application Not Responding) error will occur. (See the docs: http://developer.android.com/reference/android/content/BroadcastReceiver.html) Creating and sending a notification should not take this long.
However, "good design" means you should acquire a Wake Lock and then pass an intent to a Service to do much of anything. Also, you will probably find that you will want to do "other processing" at some point. However, if I were you and all that is required is to post a notification, I'd just use the Receiver and worry about it later. I've probably processed over a billion notifications this way without error. But a code reviewer may argue that it's "possible" for an ANR to occur... blah... blah... blah...

Not sure of what to choose between Service, BroadcastReceiver and AlarmManager

Hello guys i am building an app in which i would like to add subscription. That means that every user has to pay monthly.
So i want to check if the user has paid he will be able to proceed with the orders if he didn't then i want a dialog to redirect him to pay. What would you suggest me to use Service, BroadcastReceiver or AlarmaManager?
I was thinking of creating a Service and within it create an AsyncTask that will check to the database if the user has paid and then if not inform the user with a dialog. Also i was thinking of creating Notiofications to the user that the subscription ending.
What is your opinion???
I developed a similar function to check many bills. I combined the three methods to ensure stability. But I think you should use Google Play In-app Billing to achieve subscriptions instead of using a local database. If you must use a database to subscribe:
1.After users subscribe, saved info to the database and start a service. the service start a thread,the thread get Data and Analyzing user payments. then use AlarmManager to set Notification and stopSelf.
public class NotificationService extends Service {
...
private AlarmManager am;
private PendingIntent pi;
private NotificationManager mNM;
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Thread thread = new Thread(null, mTask, "AlarmService_Service");
thr.start();
return START_REDELIVER_INTENT;
}
Runnable mTask = new Runnable() {
public void run() {
List<Subscription> mDataList = getData;
if (mDataList.size() > 0) {
for (Subscription mSubscription : mDataList) {
if (mSubscription.isSub == true) {
Intent intent = new Intent(NotificationService.this,
AlamrReceiver.class);
intent.putExtra("data", (Serializable)mSubscription);
intent.setData(Uri.parse("custom://" + uniqueCode));
intent.setAction(String.valueOf(uniqueCode));
am = (AlarmManager) getSystemService(ALARM_SERVICE);
pi = PendingIntent.getBroadcast(
NotificationService.this, uniqueCode, intent,
PendingIntent.FLAG_CANCEL_CURRENT);
am.set(AlarmManager.RTC_WAKEUP, reminderTime, pi);
uniqueCode = uniqueCode + 1;
}
}
}
NotificationService.this.stopSelf();
}
};
}
2.Receive broadcast information and show Notification.
public class AlamrReceiver extends BroadcastReceiver {
private NotificationManager mNM;
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
mNM = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
Subscription mSubscription = intent.getSerializableExtra("data");
if (mSubscription != null) {
showNotification(context, mSubscription);
}
}
private void showNotification(Context context, Subscription mSubscription) {
...
NotificationCompat.Builder builder = new NotificationCompat.Builder(
context);
builder.setContentTitle(text);
builder.setContentText(subTitleString + currencyString);
builder.setSmallIcon(Common.CATEGORY_ICON[cIcon]);
builder.setDefaults(Notification.DEFAULT_VIBRATE);
builder.setAutoCancel(true);
Intent intent = new Intent(context, BillDetailsActivity.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addParentStack(BillDetailsActivity.class);
intent.putExtra("dataMap", (Serializable) tMap);
stackBuilder.addNextIntent(intent);
PendingIntent contentIntent = stackBuilder.getPendingIntent(0,
PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(contentIntent);
int uniqueRequestCode = (int) System.currentTimeMillis();
mNM.notify(uniqueRequestCode, builder.build());
}
}
Do not forget BOOT_COMPLETED , when the phone restarted , start the service and check the database
I am not sure of concept of your app, but if you want to check whether user has subscribed for current month or not, you don't need to run a Service for that.
You should check this in Splash Screen or Main Activity of your app. But anyways,
if you still need to do this, i suggest to go with Service or AlarmManager.
BroadcastReceiver won't work alone, you need to trigger them on particular events according to your need.
Also if you use service, please keep in mind that Android MIGHT kill your service in low memory kind of situation.
So i'll suggest that you should go with AlarmManager, which will check for subscription status after specific time or something.
PS : i know this should be addded as comment, but i don't have enough reputations to comment so posted as answer

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"));
}
}

Techniques to implement a notification service

I have a main activity where the user can enable/disable notifications, set the notification interval, and set the base time the notification interval will use. Notifications will typically trigger about 2 hours from each other. After a certain time, an accumulator will reach a maximum value and notifications will no longer be needed.
What is the standard way of implementing such a notification scheme? I tried using a handler inside of a service using postAtTime, but it seems that there are a lot of conditions that can cause it to never run. I looked at a timer inside of the service, but putting the phone in standby will stop any timers, plus it just seems like a bad idea.
The only other option I came across I have yet to explore, but it involves using an AlarmManager and a BroadcastReceiver. Should I just ditch the service and schedule a repeating alarm instead? I need to be able to disable all remaining alarms once my accumulator has reached max value.
Thanks for any input.
What if you start a service that spawns a thread like this:
thread t = new thread(new Runnable(){
public void Run(){
boolean notified = false;
while( !notified ){
if( notify_time - time > 1000 ){
Thread.sleep(999);
else if( notify_time - time <= 0 ){
// START NOTIFICATION ACTIVITY
notified = true;
}
}
}
}
t.start();
I have not done anything like this personally, so I am not sure what service can do to notify the user or start an activity, but it does have the full panoply of options available to an activity, so yeah.
Oh but it just occured to me you'll need to use a handler for that because of the multithreaded aspect here.
Since I will always have a finite number of notifications and I can calculate the elapsed time in advance, it seems the combination of AlarmManager and a BroadcastReceiver work pretty well. Here is how I implemented this:
I first created a BroadcastReceiver
public class NotificationReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
//Get handle to system notification manager
NotificationManager mNM = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
//Get message from intent
Bundle bundle = intent.getExtras();
CharSequence text = bundle.getString("notification_message");
// Set the icon, scrolling text and timestamp
Notification notification = new Notification(R.drawable.notification_icon, text, System.currentTimeMillis());
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0);
// Set the info for the views that show in the notification panel.
notification.setLatestEventInfo(context, context.getText(R.string.app_name),text, contentIntent);
// Set Flags
notification.flags |= Notification.FLAG_AUTO_CANCEL;
// Send the notification.
mNM.notify(R.string.notification, notification);
}
}
I then created a class that used a AlarmManager to create/cancel alarms that send a message to the BroadcastReceiver
public class NotificationSender {
private AlarmManager mAlarmManager;
private Context mContext;
private Intent mIntent;
public NotificationSender(Context context){
this.mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
this.mIntent = new Intent(context, NotificationReceiver.class);
this.mContext = context;
}
public void setAlarm(Long etaMillis, int accumulator){
//Create intent to send to Receiver
this.mIntent.putExtra("notification_message","Message");
//Use accumulator as requestCode so we can cancel later
PendingIntent sender = PendingIntent.getBroadcast(this.mContext, accumulator, this.mIntent, PendingIntent.FLAG_UPDATE_CURRENT);
//Set Alarm
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, etaMillis, sender);
}
public void cancelAlarms(){
//requestCode (accumulator) will always be a multiple of 10 and less than 100
for (int x = 10; x <= 100; x += 10){
PendingIntent operation = PendingIntent.getBroadcast(this.mContext, x, this.mIntent, PendingIntent.FLAG_UPDATE_CURRENT);
mAlarmManager.cancel(operation);
}
}
public void createAlarms(PreferenceHelper prefs){
//Calculate time notifications are due and set an alarm for each one
//PreferenceHelper is a class to help pull values from shared preferences
Date currentTime = new Date();
for (int i = prefs.getNotificationInterval(); i <= 100; i += prefs.getNotificationInterval()) {
if (i > prefs.getAccumulator()) {
this.setAlarm(SystemClock.elapsedRealtime() + calculateETA(i, prefs).getTime() - currentTime.getTime(), i);
}
}
}
public void refreshAlarms(PreferenceHelper prefs){
this.cancelAlarms();
if (prefs.isNotificationsEnabled()) this.createAlarms(prefs);
}
}
The important part is to use the accumulator as the requestCode so we can cancel all of our alarms later.
Finally I used the NotificationSender class in my activity by calling refreshAlarms() in onCreate() and whenever the user modifies preferences that are relevant to scheduling notifications. Rebooting the phone will clear all alarms so the app must be restarted before notifications will begin. If the system happens to kills the process, the alarms will still trigger at the appropriate time.

Categories

Resources