Notification DeleteIntent broken on later versions of Android - java

In our app OneBusAway Android (open-source on Github), we need to be notified when the user dismisses a particular reminder notification, so we don't post another reminder notification for the same event (how long until their bus arrives).
We're doing this by listening for an Intent in our app, registered as the DeleteIntent with the Notification. When the user dismisses the notification (either by swiping it away, or tapping the clear button in the notification window), our app should receive that Intent.
From testing, it seems that with the current version on Google Play (and the current master branch on Github), the DeleteIntent is never received in our application in the following versions of Android:
Android 4.4.3
Android 4.4.4
However, the exact same code DOES work (i.e., the Intent registered as the DeleteIntent is received by the app) on:
Android 2.3.3
Android 2.3.6
Android 4.1.1
Android 4.1.2
I've looked at the following SO posts that deal with DeleteIntent, and none of the solutions listed work on Android 4.4.3 and 4.4.4:
Notification Auto-Cancel does not call DeleteIntent
Android - DeleteIntent, how to use?
Notification deleteIntent does not work
https://stackoverflow.com/questions/24218626/how-to-detect-notification-cancel-event-in-android-not-deleteintent
https://stackoverflow.com/questions/22769523/why-my-deleteintent-is-not-working-on-my-notification
Android deleteIntent not working? What's wrong with my code?
Custom actions using implicit intents between applications
The current working master branch uses a Service to listen for the Intent. However, based on some of the above posts, I did tweak some of the code to be more in line with working examples that use a BroadcastReceiver to listen for the Intent.
The code using the BroadcastReceiver is in the following Github branch:
https://github.com/CUTR-at-USF/onebusaway-android/tree/issue104-RepeatingReminders
Below are excerpts for what my current version looks like (that still works on Android 4.1.2 and lower, but not 4.4.3 or 4.4.4), along with links to Github source:
Creating the notification
https://github.com/CUTR-at-USF/onebusaway-android/blob/issue104-RepeatingReminders/onebusaway-android/src/main/java/com/joulespersecond/seattlebusbot/tripservice/NotifierTask.java#L131
private Notification createNotification(Uri alertUri) {
//Log.d(TAG, "Creating notification for alert: " + alertUri);
Intent deleteIntent = new Intent(mContext, AlarmReceiver.class);
deleteIntent.setAction(TripService.ACTION_CANCEL);
deleteIntent.setData(alertUri);
return new NotificationCompat.Builder(mContext)
.setSmallIcon(R.drawable.ic_stat_notification)
.setDefaults(Notification.DEFAULT_ALL)
.setOnlyAlertOnce(true)
.setDeleteIntent(PendingIntent.getBroadcast(mContext, 0,
deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT))
.setAutoCancel(true)
.build();
}
Title and other dynamic notification info are set a few lines later (and reset later, if the notification remains undismissed):
#SuppressWarnings("deprecation")
private void setLatestInfo(Notification notification,
String stopId,
String routeId,
long timeDiff) {
final String title = mContext.getString(R.string.app_name);
final PendingIntent intent = PendingIntent.getActivity(mContext, 0,
new ArrivalsListActivity.Builder(mContext, stopId).getIntent(),
PendingIntent.FLAG_UPDATE_CURRENT);
notification.setLatestEventInfo(mContext,
title,
getNotifyText(routeId, timeDiff),
intent);
}
TripService contains the constants for the action:
public static final String ACTION_CANCEL =
"com.joulespersecond.seattlebusbot.action.CANCEL";
AlarmReceiver
https://github.com/CUTR-at-USF/onebusaway-android/blob/issue104-RepeatingReminders/onebusaway-android/src/main/java/com/joulespersecond/seattlebusbot/AlarmReceiver.java
public class AlarmReceiver extends BroadcastReceiver {
private static final String TAG = "AlarmReceiver";
#Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "In onReceive with intent action " + intent.getAction());
...
}
}
AndroidManifest
https://github.com/CUTR-at-USF/onebusaway-android/blob/issue104-RepeatingReminders/onebusaway-android/src/main/AndroidManifest.xml
<receiver android:name=".AlarmReceiver">
<!-- These action names must match the constants in TripService -->
<intent-filter>
<action android:name="com.joulespersecond.seattlebusbot.action.SCHEDULE" />
<action android:name="com.joulespersecond.seattlebusbot.action.POLL" />
<action android:name="com.joulespersecond.seattlebusbot.action.CANCEL" />
</intent-filter>
</receiver>
With the above, on Android 4.4.3/4.4.4, the AlarmReceiver never sees the Intent when the user dismisses the notification.
I also tried adding a MIME type, as specified in Custom actions using implicit intents between applications, but that didn't work on Android 4.4.3/4.4.4 either:
Intent deleteIntent = new Intent(mContext, AlarmReceiver.class);
deleteIntent.setAction(TripService.ACTION_CANCEL);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
deleteIntent.setDataAndTypeAndNormalize(alertUri, TripService.REMINDER_MIME_TYPE);
} else {
deleteIntent.setDataAndType(alertUri, TripService.REMINDER_MIME_TYPE);
}
return new NotificationCompat.Builder(mContext)
.setSmallIcon(R.drawable.ic_stat_notification)
.setDefaults(Notification.DEFAULT_ALL)
.setOnlyAlertOnce(true)
.setDeleteIntent(PendingIntent.getBroadcast(mContext, 0,
deleteIntent, 0))
//.setLights(0xFF00FF00, 1000, 1000)
//.setVibrate(VIBRATE_PATTERN)
.build();
REMINDER_MIME_TYPE is application/vnd.com.joulespersecond.seattlebusbot.reminder
Manifest for using the MIME type:
<receiver android:name=".AlarmReceiver">
<!-- These action names must match the constants in TripService -->
<intent-filter>
<action android:name="com.joulespersecond.seattlebusbot.action.SCHEDULE" />
<action android:name="com.joulespersecond.seattlebusbot.action.POLL" />
<action android:name="com.joulespersecond.seattlebusbot.action.CANCEL" />
<data android:mimeType="application/vnd.com.joulespersecond.seattlebusbot.reminder" />
</intent-filter>
</receiver>
I also tried not using the support library (i.e., using Notification.Builder instead of NotificationCompat.Builder), but that didn't change anything either.
Any ideas why this isn't working on Android 4.4.3/4.4.4?
More info is shown in the Github issue for this problem.
EDIT
I've also replicated this issue in a small Github project "DeleteIntentDemo":
https://github.com/barbeau/DeleteIntentDemo
Instructions to reproduce are in the README for this project.
EDIT 2
This appears to be due to a bug in Android in Notification.setLatestEventInfo() - I've reported it here:
https://code.google.com/p/android/issues/detail?id=73720
Please see #CommonsWare's answer for the workaround.
EDIT 3
My AOSP patch to fix this issue has now been merged so this problem won't appear for legacy apps in future releases of Android:
https://code.google.com/p/android/issues/detail?id=73720#c4
However, in the above AOSP thread is it emphasized that one should no longer be using Notification.setLatestEventInfo() - instead, use Notification.Builder to create a new Notification.

In your sample project, if you remove the following line, the deleteIntent works on a Nexus 4 running 4.4.4:
setLatestInfo(getActivity(), notification, routeId);
I suspect that this call is wiping out your deleteIntent. It may work to re-apply your deleteIntent to the Notification as part of your setLatestInfo() processing.

You must have a different problem because I'm able to receive the deleteintent in several 4.3 and 4.4 emulators.
I wanted to test your "simple" project but it uses Android Studio, so I made my own simpler test.
Steps to reproduce:
-Create an Activity and set the launch mode to singleInstance in the manifest.
-In the handler of a button or menu item, launch a notification:
Intent deleteIntent = new Intent(this, MainActivity.class);
Notification notification = new NotificationCompat.Builder(this)
.setSmallIcon(android.R.drawable.ic_dialog_alert)
.setOnlyAlertOnce(true)
.setContentTitle("Notification delete intent test")
.setContentText("Please dismiss this notification by swipping or deleting it. A Toast will be shown if the deletion intent works.")
.setDeleteIntent(PendingIntent.getActivity(this, 0, deleteIntent, 0))
.setAutoCancel(true)
.build();
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.notify((int)System.currentTimeMillis(), notification);
-Override onNewIntent to show a toast or log a message when the notification is cancelled:
#Override
public void onNewIntent(Intent intent){
Toast.makeText(this, "Notification deleted!", Toast.LENGTH_LONG).show();
}
To dismiss the notification either swipe or press the clear button. It wont work pressing over it because autocancel is not considered an explicit user action and hence the delete intent wont be delivered.

Related

Google denied update due Remediation for Implicit PendingIntent Vulnerability

When i'm trying to update my app - i got error during review process. Remediation for Implicit PendingIntent Vulnerability - https://support.google.com/faqs/answer/10437428. In my app there is on place, where i'm creating PendingIntent - for Firebase push notifications:
Inside class FCMService extends FirebaseMessagingService
#Override
public void onMessageReceived(#NotNull RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
Intent intent = new Intent(this, ApplicationActivity.class);
intent.setAction("com.google.firebase.MESSAGING_EVENT");
intent.setPackage(getApplicationContext().getPackageName());
Map<String, String> data = remoteMessage.getData();
for (Map.Entry<String, String> entry : data.entrySet()) {
String value = entry.getValue();
String key = entry.getKey();
if (key.equals(ApplicationActivity.LINK_URL) ||
key.equals(ApplicationActivity.FLOCKTORY_LINK_URL)) {
intent.putExtra(ApplicationActivity.FLOCKTORY_LINK_URL, value);
if (remoteMessage.getNotification() != null && remoteMessage.getNotification().getTitle() != null) {
intent.putExtra(ApplicationActivity.HMS_PUSH_TITLE, remoteMessage.getNotification().getTitle());
}
}
}
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
RemoteMessage.Notification notification = remoteMessage.getNotification();
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, getString(R.string.channel_id))
.setSmallIcon(R.drawable.ic_launcher_notification)
.setColor(getResources().getColor(R.color.colorNotification))
.setContentTitle(notification == null ? "" : notification.getTitle())
.setContentText(notification == null ? "" : notification.getBody())
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setAutoCancel(true);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(new Random(UUID.randomUUID().getLeastSignificantBits()).nextInt(), builder.build());
In Manifest:
<service
android:name="ru.svyaznoy.shop.domain.FCMService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
implementation "com.google.firebase:firebase-messaging:22.0.0"
minSdkVersion 24
targetSdkVersion 30
I just cant figure out what's wrong with this code - i pass explicit Intent with all required fields set. My head is blowing - this update is very important. Does anyone had similar issue?
Thanks to #kkazakov problem solved. Library com.huawei.hms:push contains unsafe usage of implicit PendingIntents. Google approved update for build without this lib.
For me it's time to create gms and hms build flavors to avoid problems with Huawei in the future.
The Intent in your example is an explicit intent with a given action. So this shouldn't be the cause for your update problem.
I am facing the same security problem and I think the cause for this is in a dependency. Because there are only explicit pending intents in my app.
I don't think that google prevents an update because of a vulnerability in their own libraries so I currently looking into the dependencies of the Huawei SDKs. It's just a guess but without any more information from play store guessing is the only thing we can do.
Thank you for your feedback. This issue has been resolved in the release of Push SDK 5.3.0.304.
It has been tested and verified by developers and can be approved by Google for release.
For details, you can check the Push kit Version Change History description.
Just want to update that our app used Push SDK 5.3.0.304, but still got the warning in the play console's Pre-launch report details.
It indicates that PushNotification.java's method with this signature: void a(android.content.Context,android.content.Intent,long,int) produces the issue.
Maybe #shirley could help to check if this method still has the issue. Thanks.

Android notification returns to the wrong activity

Hi all,
I am implementing a notification in a service which when clicking on the notification once it appears - It opens an activity.
I have this code in my service class inside a runnable that runs nonstop every few seconds:
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setSmallIcon(android.R.drawable.ic_dialog_email)
.setContentTitle(notificationTitle)
.setContentText(notificationMessage);
NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle();
builder.setStyle(bigText);
Intent resultIntent = new Intent(this, openemailactivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pendingIntent);
Notification notification = builder.build();
notification.flags = Notification.FLAG_AUTO_CANCEL;
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(0, notification);
In my notification activity named "openemailactivity" I have a button which has a code in it to return the user to the home activity called "MainActivity":
btnReturnToMainScreen.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
}
});
Everything works fine until I restart my Android phone, the service loads on boot (as intended) and then generates a notification and then I click on it.
Then, when I click on it, the "openemailactivity" activity opens well also as intended, and then I click the button that opens the "MainActivity" (The above code) and it does that O.k.
And now the problem starts !
After this other activity was opened ("MainActivity"), and then the notification pops up again by the service (Notification timer\checking runs in a timed Handler\Runnable that runs nonstop) - and then I click on the notification itself that was created -
the activity that it should call ("openemailactivity") is not called anymore ! instead - the last called activity pops up ("MainActivity"), which is wrong !
The problem starts only when I restart my phone, the service loads on boot, notification is generated, I click on it, It opens the right activity, and then I call the code from the button (OnClickListener).
I don't have a clue on how to solve this problem.
What do you think can be the problem ?
What am I doing wrong and how can I solve it ?
Thanks for any reply.
Strange but it looks like I found an answer (Hopefully - It needs to get more comprehensive testing since Surprises always exist in this environment).
I tried to set:
resultIntent.addFlags(FLAG_ACTIVITY_CLEAR_TOP);
in the notification code that exists in the service, right after declaring this variable (resultIntent).
As for now it looks like it works !
By looking at the Android documentation for this flag, it looks like it may explain the problem that I had:
https://developer.android.com/reference/android/content/Intent.html
(Search for the explanation of: "FLAG_ACTIVITY_CLEAR_TOP").

Android foreground service notification not persistent

I have created a class which extends Service and runs as a foreground service. I would like my service notification to be persistent (i.e. not removed by swiping). However, my notification can be dismissed by swiping.
The Service documentation states: ...A foreground service must provide a notification for the status bar, which is placed under the Ongoing heading. This means that the notification cannot be dismissed unless the service is either stopped or removed from the foreground...
I did put break points to check whether onDestroy() or stopSelf() is hit, but this is not the case. The service is running in foreground mode, but I can dismiss the notification by swiping.
I have found quite a few questions regarding the opposite case, where it was not possible to dismiss the notification after the service was stopped, but did not find any question similar to my problem.
The service is started via an Intent and initialized as follows:
#Override
public void onCreate()
{
super.onCreate();
initialize();
}
private void initialize()
{
Notification n = get_service_notification();
startForeground(10, n);
LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver, new IntentFilter(BroadcastCodes.service_broadcast_intent_name));
}
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
startServing();
return START_STICKY;
}
where startServing() subscribes to location updates
The notification is built like this
private Notification get_service_notification()
{
Intent intent = new Intent(this, LoginActivity.class);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder b = new NotificationCompat.Builder(this);
b.setAutoCancel(false)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.app_icon)
.setTicker("some text")
.setContentTitle("some text")
.setContentText("some text")
.setContentIntent(contentIntent)
.setContentInfo("")
.setOngoing(true);
Notification res = b.build();
res.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR;
return res;
}
I have tried playing around with the flags and setOngoing/setAutoCancel, but the result stayed the same.
SDK versions are configured as minSdkVersion 18, targetSdkVersion 25
I would really appreciate any input regarding this issue - it might be just some stupid mistake, but I already did spend more than a few hours trying to research on my own...unfortunately to no avail.
EDIT 1
I still have not figured out the problem. I decided to try a dirty hack, but still no luck.
I made a PendingIntent and registered it via setDeleteIntent() the idea being to display a new notification once this one was dismissed by swipe. Unfortunately, I was not able to get this to work (the intent never fires when swiping).
I would also like to clarify, that the notification CAN BE swiped away, but IS NOT AFFECTED (i.e. is not deleted) by the clear-all button in the notification pane (the trash icon).
EDIT 2
As mentioned above I was not able to use setDeleteIntent() to recreate my notification when it is dismissed by swipe.
For now I settled with a workaround - my service does some periodical tasks. I am now calling
notificationManager.notify(10, service_notification);
when my task runs so that even if swiped away, my notification will be recreated after a while.
I still have a strong feeling that I just misread the documentation, since there are several notifications on my test device (Honor API23) that can not be swiped away.
EDIT 3
I have tried the same code on a different device (Lenovo API23) and the notification works as expected (can not be dismissed and can not be swiped). My problem seems to be device specific.
I made a few further tests (besides the ones mentioned in updates). I tried running various examples/tutorials from around the internet like this page here
On the Honor I was initially running my tests the notification could always be swiped, on the Lenovo and others the "ongoing" flag works as stated in the documentation, i.e. it can not be dismissed via swipe action.
To sum it up, my problem really seems to be device specific.

Launching an Activity with no UI which starts a service and finishes, but don't want to interrupt the currently running app

this is more of a style question. Right now, due to Android limitations, in order to get the functionality I am going after, I have to receive an intent with an activity which launches with no UI and doesn't render, starts a service with the intent, and then immediately finishes. However, apps vary in how they react to this launching of another activity. For example, YouTube videos pause. Is there any way I can launch this wrapper activity with as little impact on the app that is currently running as possible?
You must use BroadcastReciever. Here you can find a good tutorial about it.
Put it in you Manifest
<receiver android:name=".AutoStart ">
<intent-filter>
<action android:name="com.tutorialspoint.CUSTOM_INTENT">
</action>
</intent-filter>
</receiver>
Extend BroadcastReceiver
public class AutoStart extends BroadcastReceiver
{
#Override
public void onReceive(Context arg0, Intent arg1)
{
Log.d("AutoStart","Broadcast received");
Intent intent = new Intent(arg0,YourService.class);
arg0.startService(intent);
}
}
and Finally you can send a broadcast like this:
Intent intent = new Intent();
intent.setAction("com.tutorialspoint.CUSTOM_INTENT");
sendBroadcast(intent);

Service stops when activity is closed

I've read a bunch of answers pertaining to this question and they all seem to be the same:
"Run your service with START_STICKY"
"Run your service in the foreground"
"Run your service with startService and don't bind it"
I'm doing ALL of these things, and my service STILL closes and restarts every time my activity is closed.
This is NOT an IntentService.
I'm also not calling stopSelf or stopService anywhere except in onClick handlers.
Please scroll down to my update - This behavior has been confirmed to be a bug in the android OS and I have reported it to google. Click here to view the report.
Starting my service from MainActivity:
svcIntent = new Intent(getBaseContext(), MyService.class);
startService(svcIntent);
In my onStartCommand:
// Enter foreground state
String title = "Service has been started...";
String subject = "Service is running...";
String body = "Monitoring your battery usage.";
Notification notification = new Notification(R.drawable.theicon, title,
System.currentTimeMillis());
if (prefs.getBoolean("notificationSounds", true))
notification.defaults |= Notification.DEFAULT_SOUND;
else
notification.sound = null;
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, subject, body, pendIntent);
startForeground(1500, notification);
At the end of my onStartCommand:
...
// Release WakeLock
wl.release();
return START_STICKY;
UPDATE
I FIGURED OUT WHATS CAUSING IT! But I have no idea how to fix it. In my service i also use an AlarmManager inside my service to set up function calls to the service a specified time away.
// Alarm manager setup for MyService
AlarmManager AM = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
svcIntent1 = new Intent(this, AlarmReceiver.class);
prefs.edit().putInt("initialBatt", initialBatt).apply();
svcIntent1.setAction("com.myApp.servicealarm");
pendingIntent = PendingIntent.getBroadcast(this, 93, svcIntent1, PendingIntent.FLAG_UPDATE_CURRENT);
// Set the alarm
AM.set(AlarmManager.RTC_WAKEUP, timeNow + waitTime, pendingIntent);
I noticed that if I DO NOT comment out the AM.set call to set an alarm, EVEN WITH AN EMPTY onReceive, my service is killed when the alarm goes off, after I swipe my app away in recent apps. If I comment out the set alarm call, then the service is never killed and keeps running after I close my app. What the heck?! I need this alarm for the functionality of my algorithms!!
It's very odd. As soon as the alarm goes off, my debug message does not print, and my service restarts. But the second time around, after the service restarts, the debug message does print and the program executes successfully.
I've tried this and it still happens with a normal broadcast receiver as well. I've also stripped my code down to ONLY the set alarm call from my service and the broadcast receiver, and the same thing occurs so it's not my algorithm. Apparantly if you have a foreground service that sets an alarm, when the alarm goes off your service restarts.
CLOSING
This behavior seems to be caused by a bug in the android OS so I do not expect an answer. If you'd like to see this bug for yourself, click here. I've provided a project that you can compile and reproduce the problem with.
Android kills the process when the broadcast Intent is sent (before it is received/processed in your app).
This is a nasty Android bug, as of 4.4 (API 19).
See https://code.google.com/p/android/issues/detail?id=63618&can=1&q=service%20restart%20broadcast&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars
especially comments #22 and #23
Unfortunately, almost all "open" issues were marked as "obsolete" recently, with the assumption that they were all fixed in Android 5.0. There's no way for a developer to reopen an "obsolete" issue.
EDIT: Add details about foreground broadcast
Based on the information in the linked issue, it looks like adding Intent.FLAG_RECEIVER_FOREGROUND to your broadcast Intent will ensure that the the process does not get killed on the next receipt of a broadcast Intent.
To do this, add:
svcIntent1.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
to the code where you set your alarm in the AlarmManager.
Please read the comments in the linked issue for more details.
Try to run your service in separate process. Define it in your manifest like this:
<service
android:name=".path.to.service.class"
android:process=":my_service"/>

Categories