multiple call to startforeground? - android

i have created a service (EmailService) that sends email ... each time i need to send an email with my app, it starts the service and pass the id of the email via an intent...
i am using startforeground(id_of_email, mynotifcation); to prevent it from being killed and to show a notification to the user of the status of the email sending.
i need to allow the user to send multiple emails at the time, so when the user needs to send another email, it again calls startservice with a new intent(different id of email)...so it calls startforeground(new_id_of_email, mynotifcation); again.
the problem is that the new call to startforeground overwrites the previous notification... (so the user loses the previous notification and doesn't know what is going on with his previous email)

Looking at the Service.startForeground() source shows that multiple calls to startForeground will only replace the currently shown notification. In fact, the call to startForeground is identical to stopForeground(), only with removeNotification set always set to true.
If you wish for you service to display a notification for each email in progress, you will have to manage each notification individually from the service.
public final void startForeground(int id, Notification notification) {
try {
mActivityManager.setServiceForeground(
new ComponentName(this, mClassName), mToken, id,
notification, true);
} catch (RemoteException ex) {
}
}
public final void stopForeground(boolean removeNotification) {
try {
mActivityManager.setServiceForeground(
new ComponentName(this, mClassName), mToken, 0,
null, removeNotification);
} catch (RemoteException ex) {
}
}
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.3_r1/android/app/Service.java#Service.startForeground%28int%2Candroid.app.Notification%29

One can also use STOP_FOREGROUND_DETACH flag.
Quoting from the documentation :
STOP_FOREGROUND_DETACH
added in API level 24 int STOP_FOREGROUND_DETACH Flag for
stopForeground(int): if set, the notification previously provided to
startForeground(int, Notification) will be detached from the service.
Only makes sense when STOP_FOREGROUND_REMOVE is not set -- in this
case, the notification will remain shown, but be completely detached
from the service and so no longer changed except through direct calls
to the notification manager.
Constant Value: 2 (0x00000002)
So, before a repeated call to startForeground() you can call stopForeground(STOP_FOREGROUND_DETACH);. This will detach the notification and repeated calls to startForeground() will not make modifications to it, if you use a different notification-id.
Further, the "detached" notification, now, does not represent the "ongoing service" and hence can be removed by user with a swipe.
BONUS :
For compatibility, one can use ServiceCompat class and its static method ServiceCompat.stopForeground(MyService.this, STOP_FOREGROUND_DETACH) as is documented here.

I created a utility class to manage foreground service notification(s) based on #zeekhuge's answer. You can find the snippet here: https://stackoverflow.com/a/62604739/4522359

Related

I want to show a customize firebase push notification in Android when app is in background or killed state

I want to achieve a custom notification behavior like WhatsApp or any other app where users can interact with the notification in-tray, type a message and send it indirectly from there.
So, far I have been successful to show a custom notification with 2 buttons when the app is in the foreground. I achieved this by simply showing a custom notification when I get control in the firebase service's onMessageReceived() function.
According to my research and work, you do not get control in the service when app is in background or killed state.
So, the question is how do you achieve this? I want to show notification with a title, body, and 2 buttons. Upon clicking the buttons, I want to do 2 different tasks i.e calling APIs by opening the app.
Any help would be appreciated.
There are two type of payloads while sending push notification.
Notification & data payload : if app is in killed/background state then notification payload will be handle by system tray to show notification and on click of notification, data payload will be received inside the intent of launcher activity.(getIntent().getExtras())
Data payload : if contains only "data" payload then onMessageReceived() will be called for each state foreground/background/killed state. so you can create notification and click handling will also be from here by providing pending intent.
Have a look at Firebase SDK implementation.
private void dispatchMessage(Intent intent) {
Bundle data = intent.getExtras();
if (data == null) {
// The intent should always have at least one extra so this shouldn't be null, but
// this is the easiest way to handle the case where it does happen.
data = new Bundle();
}
// First remove any parameters that shouldn't be passed to the app
// * The wakelock ID set by the WakefulBroadcastReceiver
data.remove("androidx.content.wakelockid");
if (NotificationParams.isNotification(data)) {
NotificationParams params = new NotificationParams(data);
ExecutorService executor = FcmExecutors.newNetworkIOExecutor();
DisplayNotification displayNotification = new DisplayNotification(this, params, executor);
try {
if (displayNotification.handleNotification()) {
// Notification was shown or it was a fake notification, finish
return;
}
} finally {
// Ensures any executor threads are cleaned up
executor.shutdown();
}
// App is in the foreground, log and pass through to onMessageReceived below
if (MessagingAnalytics.shouldUploadScionMetrics(intent)) {
MessagingAnalytics.logNotificationForeground(intent);
}
}
onMessageReceived(new RemoteMessage(data));
}
And
/**
* Handle a notification message by displaying it if appropriate, and returning whether the
* message is consumed.
* <li>If this is a no UI notification just used for analytics, doesn't show a notification and
* returns true.
* <li>If the app is in the foreground, doesn't show a notification, and returns false.
* <li>If the app is in the background, shows a notification and returns true.
*/
boolean handleNotification() {
if (params.getBoolean(MessageNotificationKeys.NO_UI)) {
return true; // Fake notification, nothing else to do
}
if (isAppForeground()) {
return false; // Needs to be passed to onMessageReceived
}
ImageDownload imageDownload = startImageDownloadInBackground();
CommonNotificationBuilder.DisplayNotificationInfo notificationInfo =
CommonNotificationBuilder.createNotificationInfo(context, params);
waitForAndApplyImageDownload(notificationInfo.notificationBuilder, imageDownload);
showNotification(notificationInfo);
return true;
}
public static boolean isNotification(Bundle data) {
return "1".equals(data.getString(MessageNotificationKeys.ENABLE_NOTIFICATION))
|| "1"
.equals(data.getString(keyWithOldPrefix(MessageNotificationKeys.ENABLE_NOTIFICATION)));
}
From the implementation, you can see the app only have control if:
The message is not push notification (which is data message)
The app is running in foreground.
Otherwise, Firebase SDK will take over and manipulate the notification.
That's a weird design from Firebase because it could lead to inconsistency notification when the app in background and foreground.
Firebase also mentioned it here but not so obvious.
Then, the solutions:
Ask backend side to change message to data message instead of notification message.
Or, add a hack to by past the condition from firebase sdk which makes NotificationParams.isNotification(data) return false

Android update Activity from a Service

I'm so lost with all that workflow of notifications and services in Android. My sceneario is this:
I have an Android application that communicate to a MySQL database through a web-service using JSON-RPC. The data retrieved from the service will be displayed in the application.
The data will get updated over time, so the application needs to listen for changes of this and, if a change occur, show a notification and update the data displayed in the app.
To listen for changes I will need to run an "infinite"(until the app is destroyed or maybe until the app destroys it) thread that from time to time will call a method on th web-service which will return the changes since the last check.
UPDATE: Ok, I have been trying using Service and IntentService, but non of them fits my needs: a Service execute in the Main Thread, so If I perform an infinite loop there my app will freeze, IntentService has it's own worker thread but there is no comunication with the App, and I need it, or at least I need a way to know if the app is in foreground (in this case the notification will not popup but the data will be passed and updated) or in background (int this case the notification will pop up and on click it will direct the user to the app with the updated data)
#1 You can fire a broadcast message from your Service and define a Broadcast receiver in your Activity to receive this broadcast.
SEND BROADCAST-from Service
Intent i = new Intent("ALERT_CHANGE");
i.putExtra("DATA","News");
sendBroadcast(i);
RECEIVE BROADCAST-in Activity
registerReceiver(uiUpdated, new IntentFilter("ALERT_CHANGE"));
private BroadcastReceiver uiUpdated= new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent)
{
String DATA = i.getStringExtra("Data");
txt.settext(DATA);
}
};
Ok, after a lot of testing and thanks to the info given here I finally found a way to handle with the issue, so I will share it here:
On the IntentService I have a public static AtomicBoolean to control the end of the loop and be able to stop the service.
Then to determine if the Activity is in foreground or not I use the method suggested here https://stackoverflow.com/a/5504711/3107765
With the difference that I use the static modifier there, so I can check it from the service.
if the activity is in foreground I send a broadcast as it was suggested here by Eu. Dr. otherwise I use a notification that once clicked will let the user to the activity.

How is it possible for the user to receive notifications even when the app is not running

So I am trying to make an app so that when a row in a SQL database changes, the user receives a notification even though the app is not currently running. I'm pretty sure this is possible because Facebook can send mobile notifications when the app isn't running, but I'm not sure how to go about this.
You need to use Service which runs in background. From within a service you can start a notification. Its not clear that which 'notification' are you talking about? If you are talking about notification as in Google Cloud Messaging notification, then you need to go a different way. But, then also you would be using
GCMIntentService which extends Service class.
You want to send a notification when when a row in a SQL database changes. Is that row changing on the server side? I am assuming yes because first you were talking about Android database, then you would have mentioned SQLite instead of SQL. Secondly you gave example of Facebook. So, if the answer to my question is yes, then using GCM push services you can send a Push Message to the user. Then when user receives the message, you can show a notification with the proper data. In the onReceive method of GCMIntentService, you will receive the content in an Intent. There you can extract the message and create a notification. See here for more.
You will need to register a content observer to get notified of the changes.
To use the ContentObserver you have to take two steps:
Implement a subclass of ContentObserver
Register your content observer to listen for changes
Notify change from the content provider
--
class DbObserver extends ContentObserver {
public DbObserver(Handler handler) {
super(handler);
}
#Override
public void onChange(boolean selfChange) {
this.onChange(selfChange, null);
// start the notification
}
}
Register your content observer to listen for the changes:
getContentResolver().
registerContentObserver(
SOME_URI,
true,
yourObserver);
Now can call notifyChange after updating your db.
notifyChange(Uri uri, ContentObserver observer)
A quick example of using content observer is https://gist.github.com/JBirdVegas/3874450
You can use Service in android
A service by default runs in the same process in the main thread as the application.
Therefore you need to use asynchronous processing in the service to perform resource intensive tasks in the background. A common used pattern for a service implementation is to create and run a new Thread in the service to perform the processing in the background and then to terminate the service once it has finished the processing.
Each Service has a specific job, and it will run continuously even if you switch between different Activities, or to a different application altogether.
To start a service use the following:
Intent i= new Intent(context, ServiceClass.class);
i.putExtra("KEY1", "Value to be used by the service");
context.startService(i);
A simple example for a service class:
public class ServiceClass extends Service {
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return Service.START_NOT_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
}

Android notification checking from service?

I'm currently writing an app, what has an on-boot service. This service simply pops up a notification after a time, and update it in given periods with new info. Currently there's no need for any further battery saving, I just want the update sequence to work.
I've hit a problem tho. Created the boot BroadcastReceiver what then starts my Service. It it, I do the work via a Timer and a TimerTask. But as I saw, there's no easy way to check if the notification was deleted, and thus not try to update it, but re-create it.
What I want to do:
private Notification n;
NotifTask extends TimerTask {
public void run() {
if(n.exists){
// Update notification
}else{
n = Notification.Builder(context).[customize it].build();
}
}
}
And in the service's onStart, set up a timer what runs this task every 10 seconds.
Now, my question is: is there any way to do the "n.exists" part easily, without intents?
If you post a notification and this notification already exists, it will just get updated with the new information. If it doesn't exist it will be created. I don't think you need to do anything special to get the behaviour you want.
From the documentation for NotificationManager.notifiy():
Post a notification to be shown in the status bar. If a notification
with the same id has already been posted by your application and has
not yet been canceled, it will be replaced by the updated information.

What is the correct way of handling the action NEW_OUTGOING_CALL while looping?

At the moment I am developing an application which catches the action NEW_OUTGOING_CALL with the help of a BroadcastReceiver. I am aborting the call by calling setResultData(null). After that I am showing the user a dialog which allows him to decide if he wants to use my application to rewrite its number. When the users decision has happened I am placing the new call depending on the decision. Now my broadcast receiver gets called up once again.
What is the correct way of getting to know that I have already processed the number? I got a working solution that uses a timestamp to guess if it could be already processed. Another solution would be to add a "+" at the end of the processed number.
These methods are working fine for my application being the only one catching the NEW_OUTGOING_CALL event. But what should I do when other applications (like Sipdroid or Google Voice) are also sitting there catching the NEW_OUTGOING_CALL broadcast aborting it and restarting it again? I don't see a possibility to get to know if we are still in the same "call flow" and if I already processed the number.
I would love to hear your ideas about this problem!
What API level are you working with? If it's >= 11, check out the new BroadcastReceiver.goAsync function that lets you extend the processing of the broadcast outside of the onReceive function of your receiver. This could bypass the need to loop altogether.
If, like me, you're stuck trying to do this before level 11, it is surprisingly tricky to do this elegantly. You may have done this as well, but I tried to include a "processed" flag as an extra in the ACTION_CALL intent that my code generated, hoping that it would somehow get included in the resulting ACTION_NEW_OUTGOING_CALL broadcast, but that sadly does not work.
The best solution I have been able to find is including a fragment in the URI for the ACTION_CALL intent that you generate. This fragment will be included for the resulting ACTION_NEW_OUTGOING_CALL broadcast, so your broadcast receiver can differentiate between the original call and the one that you generate, but it won't interfere with handlers that aren't looking for it.
Here's the basic code.
In your BroadcastReceiver for the ACTION_NEW_OUTGOING_CALL
public class YourBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// extract the fragment from the URI
String uriFragment = Uri.parse(
intent.getStringExtra("android.phone.extra.ORIGINAL_URI")).getFragment();
// if the fragment is missing or does not have your flag, it is new
if (uriFragment == null || !uriFragment.contains("your_flag")) {
// launch your activity, pass the phone number, etc.
// use getResultData to get the number in order to respect
// earlier broadcast receivers
...
// abort the broadcast
this.setResultData(null);
this.abortBroadcast();
}
// otherwise, your code is there, this call was triggered by you
else {
// unless you have a special need, you'll probably just let the broadcast
// go through here
// note that resultData ignores the fragment, so other receivers should
// be blissfully unaware of it
}
}
}
When the user first dials the number, the fragment will either be missing altogether or your flag won't be present, so you'll abort the broadcast and start your activity. In your activity, if you decide to place the call again, do something like the following:
startActivity(new Intent(Intent.ACTION_CALL,
Uri.parse("tel:" + modified_number + "#your_flag")));
The "your_flag" fragment will then be present in the subsequent NEW_OUTGOING_CALL broadcast and thus allow you to handle this case differently in your broadcast receiver.
The nice thing about this is the the fragment is completely ignored unless you look for it in the ORIGINAL_URI, so other broadcast receivers can continue to function. If you want to be really nice, you may want to look for an existing fragment and add your flag to it (perhaps with a comma separator).
I hope that helps. Good luck!
I don't see a possibility to get to
know if we are still in the same "call
flow" and if I already processed the
number.
Technically, you are not in the same "call flow" as placing a new call is asynchronous. You have to use hints (such as a timestamp) as you seem to be doing already.
If you are confident that other applications will not rewrite the number except to change the prefix or to add a suffix, you may want to add another "proximity check" hint to avoid false positives/negatives, but I'm afraid that's about all you can do.
The onReceive() method in Broadcast receiver receives an Intent as an argument.
Extract the Bundle from the Intent using Intent.getExtras().
This Bundle contains 3 key-value pairs as follows :
android.phone.extra.ALREADY_CALLED = null
android.intent.extra.PHONE_NUMBER = 98xxxxxx98
android.phone.extra.ORIGINAL_URI = tel:98xxxxxx98
98xxxxxx98 is the number dialled by the user.
When the onReceive() is called again, this number changes to 98xxxxxx98* or 0*
By checking for the asterisk(*) at the end of the dialled number, it can be inferred if the onReceive() method is called for the first time or the next subsequent times.
One of the answers would be to track the boolean extra in the intent. It is done in similar way by the Google Phone app. You can check this BroadcastReceiver here (look for alreadyCalled usage)
The other way would be just to pass that "rewritten" number from your broadcast to the next broadcast receiver down the road (can be any app, like Sipdroid, Google Voice, or custom VoIP app) without calling ACTION_CALL intent (this is why you get loop and you broadcast receiver called again) The following code is example of how I am handling call in my custom VoIP app. When I intercept NEW_OUTGOING_CALL in my broadcast receiver, I first check if there is internet connection. If phone is connected to internet I use custom defined intent action of my activity to place call through my VoIP app. If there is no internet connection, I just set original phone number to the broadcast receiver result data. This is used by the next broadcast receiver (probably default phone app, but doesn't have to be) in the flow to place a call.
public class BHTTalkerCallReceiver extends BroadcastReceiver {
private static final String TAG = "BHTTalkerCallReceiver";
#Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Broadcast successfull ... ");
// Extract phone number reformatted by previous receivers
String phoneNumber = getResultData();
if (phoneNumber == null) {
// No reformatted number, use the original
phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
}
if (isNetworkAvailable(context)) { // Make sure this app handles call only if there is internet connection
// My app will bring up the call, so cancel the broadcast
setResultData(null);
// Start my app to bring up the call
Intent voipCallIntent = new Intent(context, TalkerActivity.class);
voipCallIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
voipCallIntent.putExtra(TalkerActivity.OUT_CALL_NUMBER, phoneNumber);
voipCallIntent.setAction(TalkerActivity.BHT_TALKER_OUT_CALL);
context.startActivity(voipCallIntent);
} else { //otherwise make a regular call...
// Forward phone data to standard phone call
setResultData(phoneNumber);
}
}
private boolean isNetworkAvailable(final Context context) {
final ConnectivityManager connectivityManager = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE));
return connectivityManager.getActiveNetworkInfo() != null && connectivityManager.getActiveNetworkInfo().isConnected();
}
}

Categories

Resources