Android Wear custom Notification - android

When the custom notification is peeking on the homescreen, the system displays it with a standard template that it generates from the notification's semantic data. I have to swipe the notification up, to see the custom activity for the notification. I'm showing 'Swipe up to view' text as title for standard template. My question is, Can i replace "Swipe up to view" with a time counter, which increase with timer?
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String ns = getApplicationContext().NOTIFICATION_SERVICE;
mNotificationManager = (NotificationManager) getSystemService(ns);
callNotification(Html.fromHtml(getFormattedTime(CurrentPausedTime)),false,false);
Thread notifyingThread = new Thread(null, updateTimerTaskCustom, "NotifyingServiceNew");
notifyingThread.start();
}
private void callNotification(final Spanned spanned,boolean isPause,boolean pauseAction) {
// TODO Auto-generated method stub
displayIntent = new Intent(getApplicationContext(), CustomNotification.class);
displayIntent.putExtra("exerciseTitle", "Running");
displayIntent.putExtra("duration", CurrentPausedTime);
displayIntent.putExtra("elepsedTime", 0);
displayIntent.putExtra("isPause", isPause);
displayPendingIntent = PendingIntent.getActivity(getApplicationContext(),
0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Intent deleteIntent = new Intent(getApplicationContext(), ClearNotification.class);
deletePendingIntent = PendingIntent.getActivity(getApplicationContext(),
0, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Intent pauseIntent = new Intent(ExerciseActionReciever.ACTION_PAUSE,
null,getApplicationContext(), ExerciseActionReciever.class);
pausePendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
0, pauseIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Intent stopIntent =new Intent(ExerciseActionReciever.ACTION_STOP,
null,getApplicationContext(), ExerciseActionReciever.class);
stopPendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
0, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Intent resumeIntent = new Intent(ExerciseActionReciever.ACTION_RESUME,
null, getApplicationContext(), ExerciseActionReciever.class);
resumePendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, resumeIntent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.WearableExtender wearableExtender =
new NotificationCompat.WearableExtender()
.setHintHideIcon(true)
.setContentIcon(R.drawable.icon)
.setDisplayIntent(displayPendingIntent);
mNotifyBuilder =
new NotificationCompat.Builder(getApplicationContext())
.setSmallIcon(R.drawable.icon)
.setContentTitle(""+spanned)
.setDeleteIntent(deletePendingIntent)
.extend(wearableExtender)
.addAction(R.drawable.icon, "Resume", resumePendingIntent)
.addAction(R.drawable.icon, "Stop", stopPendingIntent);
}
private Runnable updateTimerTaskCustom = new Runnable() {
public void run() {
timerHandlerCustom.removeCallbacks(updateTimerTaskCustom);
timerHandlerCustom.postDelayed(updateTimerTaskCustom, 1000);
if(!CustomNotification.isCustomCardAcrivityvisible )
{
CurrentPausedTime = CurrentPausedTime+1000;
mNotifyBuilder.setContentTitle(""+Html.fromHtml(getFormattedTime(CurrentPausedTime)));
mNotificationManager.notify(NOTIFICTIONTION_ID , mNotifyBuilder.build());
}
}
};

You can replace "Swipe up to view" with any text you want - like "53 sec". To update this value you will need to simply update your notification.
More information about updating notifications: http://developer.android.com/training/notify-user/managing.html
BTW. If you want to optimise your code the note on top might be important to you:
When you need to issue a notification multiple times for the same type
of event, you should avoid making a completely new notification.
Instead, you should consider updating a previous notification, either
by changing some of its values or by adding to it, or both.
EDIT: If you want, in addition, to use this solution with custom layout Activity you need to prevent notification from refreshing when this Activity is visible. Otherwise you will end up with Activity being created over and over again (blinking layout). Custom Activity in card layout is visible between the onResume and onPause events, so you need to detect that and update the whole notification ONLY when Activity is NOT visible to the user. The simpliest way is to use a static flag, but you can also play with other more advanced solutions (like LocalBroadcastManager etc.) to achieve this goal.
public class CustomLayoutActivity extends Activity {
public static boolean isCustomCardAcrivityvisible;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.custom_layout_activity);
}
#Override
protected void onResume() {
super.onResume();
isCustomCardAcrivityvisible = true;
}
#Override
protected void onPause() {
super.onPause();
isCustomCardAcrivityvisible = false;
}
}
and if you're about to refresh your notification just do following check:
if(!CustomLayoutActivity.isCustomCardAcrivityvisible) {
updateNotification();
}
Alternatively you can use setUsesChronometer(boolean b) method, to just display a timer (instead of contextText) that will be refreshed for you, but please notice that the timer will only be displayed (on Android Wear) if you will NOT set a custom layout to your card. So while this is not exactly what you want, you may consider this instead.
Show the when field as a stopwatch. Instead of presenting when as a
timestamp, the notification will show an automatically updating
display of the minutes and seconds since when. Useful when showing an
elapsed time (like an ongoing phone call).

Related

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...

Android TV - Recommendation (Notification) will not auto-cancel

When creating a recommendation (or Notification) in Lollipop on Android TV, I cannot get it to Auto-cancel.
I am using the "NotificationCompat.BigPictureStyle" as recommended in the Android TV developer pages. The notification works as designed, triggering the PendingIntent as expected but does not auto-cancel and dissappear from recommendations bar. A second selection of the recommendation brings up a blank screen, so I guess the PendingIntent is null at that point. (ADB shows android.content.IntentSender$SendIntentException on 2nd invocation.)
Tested on Nexus Player and Android TV Emulator.
private void buildAndroidTVRecommendation(String name, PendingIntent pIntent,
Context context2, Bundle extras) {
NotificationManager mNotificationManager = (NotificationManager)
context2.getSystemService(Context.NOTIFICATION_SERVICE);
Bitmap smallBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.air_share);
Notification notification = new NotificationCompat.BigPictureStyle(
( new NotificationCompat.Builder(context)
.setContentTitle("Air-Share - incoming share")
.setContentText("From: "+name)
.setContentInfo("Air-Share"))
.setGroup("Air-Share")
.setColor(0xFFFF2020)
.setCategory(Notification.CATEGORY_RECOMMENDATION)
.setLargeIcon(smallBitmap)
.setSmallIcon(R.drawable.air_share)
.setContentIntent(pIntent)
.setExtras(extras)
.setAutoCancel(true)
)
.build();
mNotificationManager.notify(pendingCounter, notification);
mNotificationManager = null;
}
For what it's worth I have created a work-around to get by this issue.
I created a new Activity whose sole purpose is to receive a Pending intent from the Recommendation.
I alter the original Pending Intent of the recommendation to invoke this new activity instead of my desired activity. (The desired activity is outside my app.) In the Extras, I bundle everything I need to know for my original desired intent as well as the notification ID.
When the new activity is launched (after user clicks on recommendation), I extract the ID and cancel the recommendation. I then extract the information for the desired intent, create the intent and finally finish the activity.
public class TVRecommendationActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent in = (Intent) getIntent();
String jsonString = in.getStringExtra("jsonString");
String fileUri = in.getStringExtra("fileUri");
int id =in.getIntExtra("notificationID", -1);
NotificationManager nm = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
if (id >= 0 ) nm.cancel(id);
JSONIntent newIntent = new JSONIntent(getApplicationContext());
newIntent.setIncomingLocalFileURI(fileUri);
Intent out = newIntent.buildIntentFromJSON(jsonString, fileUri);
if (out != null) startActivity(out);
finish();
}
}

Click on notification to enter my app in android

Currently I am working on GCM (Google Cloud message), it allow user to push the message to user device. And I would like achieve the following requirement :
if the user has already enter app , ignore it
if the user has not enter the app , click on notification to enter the app
And the work flow of my app is:
WelcomePage (download json and create data set from it) => MainPage (Display base on the data set)
The code to handle notification
private void sendNotification(String msg) {
mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
String notifyMsg = "";
JSONTokener tokener = new JSONTokener(msg);
if (tokener != null) {
try {
notifyMsg = new JSONObject(tokener).getString("msg");
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Intent myintent = new Intent(this, WelcomePageActivity.class);
myintent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, myintent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle(getResources().getString(R.string.notification_title))
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(notifyMsg))
.setContentText(notifyMsg)
.setContentIntent(contentIntent);
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
}
The problem is if I use WelcomePageActivity class , it will create a new activity if I am at the main page, how can I adjust the code to fit my requirement ?
Thanks
For
1. if the user has already enter app , ignore it:
in the onReceive() , check if your app is running, do not notify.
It can be checked with something like:
ActivityManager activityManager =(ActivityManager)gpsService.this.getSystemService(ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> serviceList= activityManager.getRunningServices(Integer.MAX_VALUE);
if((serviceList.size() > 0)) {
boolean found = false;
for(int i = 0; i < serviceList.size(); i++) {
RunningServiceInfo serviceInfo = serviceList.get(i);
ComponentName serviceName = serviceInfo.service;
if(serviceName.getClassName().equals("Packagename.ActivityOrServiceName")) {
//Your service or activity is running
break;
}
}
if the user has not enter the app , click on notification to enter the app
from the code above, you'l know if you would like to resume the app or launch - call Splash Screen or in your case WelcomeActivity.
About the workflow of your app, i'd suggest check whether you need to download the data every time or not. Can save it maybe or update/download only when required, and rest of flow works as it is.
In your AndroidManifest.xml, define your WelcomePageActivity with the flag android:launchMode="singleTop". From the definition of this flag:
A new instance of a "singleTop" activity may also be created to handle
a new intent. However, if the target task already has an existing
instance of the activity at the top of its stack, that instance will
receive the new intent (in an onNewIntent() call); a new instance is
not created.
So with this flag, your activity will not be created again, rather it will receive a call in the onNewIntent() function with the Intent you used to create the PendingIntent for the notification. You could override this function, and use the intent to pass the activity new information.
You will not able to receive any notification click event so,
try this code :
Intent myintent = new Intent(this, TestActivity.class);
myintent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, myintent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle(getResources().getString(R.string.notification_title))
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(notifyMsg))
.setContentText(notifyMsg)
.setContentIntent(contentIntent);
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
}
public class TestActivity extends Activity{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// check for your app state is running or not
if(appRunning == false) {
// start your WelcomePage activity.
}
}
}
1.Create an object in GcmIntentService
public static final Object CURRENTACTIVIYLOCK = new Object();
//for storing current activity
public static Activity currentActivity;
2.Update this object value in onPause and onResume of MainActivity to recognize Activity is running or not.
#Override
public void onResume() {
super.onResume();
System.out.println("onResume Home page");
synchronized (GcmIntentService.CURRENTACTIVIYLOCK) {
GcmIntentService.currentActivity = this;
}
}
#Override
public void onPause() {
super.onPause();
synchronized (GcmIntentService.CURRENTACTIVIYLOCK) {
GcmIntentService.currentActivity = null;
}
}
3.In GcmIntentService class, check for the current activity in onHandleIntent method.
synchronized (CURRENTACTIVIYLOCK) {
if (currentActivity != null) {
if (currentActivity.getClass() == HomePageActivity.class) {
} else {
sendNotification(extras.getString("message"));
}
} else {
sendNotification(extras.getString("message"));
}
I'm sure this will help you.

How can I avoid blinking notification update while changing button

I have a Notification, which supports play,pause forward and back.
private static Notification createNotification(String interpret, String title, boolean paused) {
// if (builder == null)
builder = new NotificationCompat.Builder(context);
builder.setPriority(Notification.PRIORITY_MAX);
builder.setAutoCancel(false);
builder.setContentTitle(title);
builder.setContentText(interpret);
builder.setOngoing(true);
builder.setOnlyAlertOnce(true);
builder.setSmallIcon(R.drawable.ic_launcher);
builder.setContentIntent(PendingIntent.getActivity(context, 9, new Intent(context, ApplicationActivity.class), Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT));
builder.addAction(R.drawable.av_previous, "", PendingIntent.getBroadcast(context.getApplicationContext(), 0, new Intent(NotificationPlayerControlReceiver.MUSIC_PLAYER_INTENT).putExtra("resultcode", NotificationPlayerControlReceiver.PREVIOUS), PendingIntent.FLAG_CANCEL_CURRENT));
if (paused)
builder.addAction(R.drawable.av_play, "", PendingIntent.getBroadcast(context.getApplicationContext(), 2, new Intent(NotificationPlayerControlReceiver.MUSIC_PLAYER_INTENT).putExtra("resultcode", NotificationPlayerControlReceiver.PLAY), PendingIntent.FLAG_CANCEL_CURRENT));
else
builder.addAction(R.drawable.av_pause, "", PendingIntent.getBroadcast(context.getApplicationContext(), 3, new Intent(NotificationPlayerControlReceiver.MUSIC_PLAYER_INTENT).putExtra("resultcode", NotificationPlayerControlReceiver.PAUSE), PendingIntent.FLAG_CANCEL_CURRENT));
builder.addAction(R.drawable.av_next, "", PendingIntent.getBroadcast(context.getApplicationContext(), 1, new Intent(NotificationPlayerControlReceiver.MUSIC_PLAYER_INTENT).putExtra("resultcode", NotificationPlayerControlReceiver.NEXT), PendingIntent.FLAG_CANCEL_CURRENT));
Notification notification = builder.build();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
notification.tickerView = null;
return notification;
}
Updating the notification:
public static void update(String interpret, String title, boolean paused) {
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(0, createNotification(interpret, title, paused));
}
To avoid blinking on update, I´ve set the builder to a global variable and I reuse it on every update, which works great. but reusing it, means that also all buttons I´ve added are reused and there is no possibility to remove Actions I´ve added before.
The button change only works, if I reinitialize the NotificationCompat.Builder on every update, which means I get the blinking again.
How do I avoid blinking, but letting the button change?
EDIT:
Just checked out Rocket Player, they didn´t solve the problem too, but Google Play Music did
Like Boris said, the problem is that a new notification will be build every update.
My solution covers the same logic, but I use the NotificationBuilder...
here is the code:
if (mNotificationBuilder == null) {
mNotificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(iconId)
.setContentTitle(title)
.setContentText(message)
.setLargeIcon(largeIcon)
.setOngoing(true)
.setAutoCancel(false);
} else {
mNotificationBuilder.setContentTitle(title)
.setContentText(message);
}
keep in mind that mNotificationBuilder is a private field in the class.
The issue is that you create new notification every time you want to update. I had the same issue and it fixed when I did the following:
retain the instance of the notification inbetween different calls of createNotification.
set this instance to null every time it is removed from the notification bar.
do the following code:
Code:
private static Notification createNotification(String interpret, String title, boolean paused) {
if (mNotification == null) {
// do the normal stuff you do with the notification builder
} else {
// set the notification fields in the class member directly
... set other fields.
// The below method is deprecated, but is the only way I have found to set the content title and text
mNotification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
}
return mNotification;
}
And now when you call notify no blinking will appear:
manager.notify(0, createNotification(interpret, title, paused));
PS: I also faced a problem that if I executed setLatestEventInfo the large and small icons got scrwed up. That's why I did:
int tmpIconResourceIdStore = mNotification.icon;
// this is needed to make the line below not change the large icon of the notification
mNotification.icon = 0;
// The below method is deprecated, but is the only way I have found to set the content title and text
mNotification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
mNotification.icon = tmpIconResourceIdStore;
Looking into Adnroid ccode this line mNotification.icon = 0; disables the icon screw up.
I know that this is a rather old question, but since I didn't found a solution anywhere else, I thought answering this now might help others with the same problem.
This problem is kind of tricky to begin with. I encountered it today as well, and being my stubborn self, I found a solution after searching and trying for a while.
How to solve this problem:
In order to be compatible with API-Levels lower than 19, my solution is to use the NotificationCompat classes from the support-library.
As suggested by others, I keep the reference to the NotificationCompat.Builder for as long as the notification is required. The actions I use in my Notification are only added upon initial creation of the Builder, and those actions that will change depending on the situation, I also store in a private member of the service. Upon change, I re-use the Builder object and adjust the NotificationCompat.Action object according to my needs. Then I call the Builder.getNotification() or Builder.build() method, depending on API-level (probably not necessary due to the support-libs, but I didn't check that. If I can omit that, please write a comment, so I can improve my code ;)
Here's an example code of what I just described above:
public Notification createForegroundNotification(TaskProgressBean taskProgressBean, boolean indeterminate) {
Context context = RewardCalculatorApplication.getInstance();
long maxTime = TaskUtils.getMaxTime(taskEntry);
long taskElapsedTime = TaskUtils.calculateActualElapsedTime(taskProgressBean);
long pauseElapsedTime = taskProgressBean.getPauseElapsedTime();
int pauseToggleActionIcon;
int pauseToggleActionText;
PendingIntent pauseToggleActionPI;
boolean pauseButton = pauseElapsedTime == 0;
if(pauseButton) {
pauseToggleActionIcon = R.drawable.ic_stat_av_pause;
pauseToggleActionText = R.string.btnTaskPause;
pauseToggleActionPI = getPendingIntentServicePause(context);
} else {
pauseToggleActionIcon = R.drawable.ic_stat_av_play_arrow;
pauseToggleActionText = R.string.btnTaskContinue;
pauseToggleActionPI = getPendingIntentServiceUnpause(context);
}
String contentText = context.getString(R.string.taskForegroundNotificationText,
TaskUtils.formatTimeForDisplay(taskElapsedTime),
TaskUtils.formatTimeForDisplay(pauseElapsedTime),
TaskUtils.formatTimeForDisplay(taskProgressBean.getPauseTotal()));
// check if we have a builder or not...
boolean createNotification = foregroundNotificationBuilder == null;
if(createNotification) { // create one
foregroundNotificationBuilder = new NotificationCompat.Builder(context);
// set the data that never changes...plus the pauseAction, because we don't change the
// pauseAction-object, only it's data...
pauseAction = new NotificationCompat.Action(pauseToggleActionIcon, getString(pauseToggleActionText), pauseToggleActionPI);
foregroundNotificationBuilder
.setContentTitle(taskEntry.getName())
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(getPendingIntentActivity(context))
.setOngoing(true)
.addAction(R.drawable.ic_stat_action_done, getString(R.string.btnTaskFinish), getPendingIntentServiceFinish(context))
.addAction(pauseAction);
}
// this changes with every update
foregroundNotificationBuilder.setContentText(contentText);
if(indeterminate) {
foregroundNotificationBuilder.setProgress(0, 0, true);
} else {
foregroundNotificationBuilder.setProgress((int) maxTime, (int) taskElapsedTime, false);
}
// if this is not the creation but the button has changed, change the pauseAction's data...
if(!createNotification && (pauseButton != foregroundNotificationPauseButton)) {
foregroundNotificationPauseButton = pauseButton;
pauseAction.icon = pauseToggleActionIcon;
pauseAction.title = getString(pauseToggleActionText);
pauseAction.actionIntent = pauseToggleActionPI;
}
return (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
? foregroundNotificationBuilder.getNotification() // before jelly bean...
: foregroundNotificationBuilder.build(); // since jelly bean...
}
The variables foregroundNotificationBuilder, pauseAction and foregroundNotificationPauseButton are private members of the service class.
The getPendingIntent...() methods are convenience methods that simply create the PendingIntent objects.
This method is then called when I need to update the notification using the NotificationManager, as well as handed over to the service's startForeground() method. This solves the flickering and the problems with the not updatable actions in the notification.

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