6

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:

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.

Community
  • 1
  • 1
Sean Barbeau
  • 11,496
  • 8
  • 58
  • 111
  • 1
    Have you tried an explicit `Intent`? Unless those actions are part of some public SDK that third parties are supposed to invoke, you should not have the `` on `AlarmReceiver`. I cannot reproduce your problem with an explicit `Intent` -- `setDeleteIntent()` works fine on my Nexus 4 running 4.4.4. – CommonsWare Jul 15 '14 at 23:20
  • `Intent deleteIntent = new Intent(mContext, AlarmReceiver.class);` that is currently being used is an explicit Intent, right? Some of this design pre-dates my involvement in the project, but I believe the idea was to (eventually) allow external apps to schedule/cancel transit alarms as well as triggering the same action from internal code. I tried removing the ``s on AlarmReceiver, but that didn't change anything. – Sean Barbeau Jul 16 '14 at 02:11
  • @CommonsWare At the bottom of my answer I've added a small sample project on Github that reproduces the issue - https://github.com/barbeau/DeleteIntentDemo. Any feedback is appreciated. If you could provide the sample that works correctly, that would also help. – Sean Barbeau Jul 16 '14 at 03:42
  • Is this the autocancel bug or something else? Is the intent fired when manually deleting the notification? – Mister Smith Jul 16 '14 at 07:17
  • @SeanBarbeau: "that is currently being used is an explicit Intent, right?" -- yes, sorry, I was focused on the ``. – CommonsWare Jul 16 '14 at 10:40
  • @MisterSmith If you're referring to https://code.google.com/p/android/issues/detail?id=41253, this is a different issue. The autocancel bug is that the DeleteIntent isn't fired when the user taps on the notification. This issue is for the basic use case of listening for an Intent when the user dismisses the notification by swiping it away, or tapping on the "clear" button in the notification pane (i.e., does not tap on it) – Sean Barbeau Jul 16 '14 at 13:29
  • Filed AOSP issue here - https://code.google.com/p/android/issues/detail?id=73720 – Sean Barbeau Jul 17 '14 at 14:28
  • I don't use `setLatestEventInfo()` and I still don't get any action from deleteIntent on 4.4.4 android – user25 Feb 04 '17 at 18:18

2 Answers2

4

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.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • 1
    You're right! If I remove that line in the demo project it works on my Galaxy S3 with 4.4.4 as well. Looking at the Android source for `Notification.setLatestEventInfo()` (http://goo.gl/lqrdwy), it looks like `Notification.Builder()` is used internally to overwrite the current Notification with the new values, but the current `deleteIntent` is never copied - so this is essentially a bug there. Strange, since eliminating `Notification.setLatestEventInfo()` doesn't have any effect in the original project (just tested again). Must be something else going on there in addition to this... – Sean Barbeau Jul 16 '14 at 14:05
  • @SeanBarbeau: Note that your sample is using `NotificationCompat.Builder`, not `Notification.Builder` as mentioned in your comment. If you come up with another sample that can reproduce your problem on 4.4.4, let me know. – CommonsWare Jul 16 '14 at 16:45
  • Right - in the demo project I'm originally creating the `notification` using `NotificationCompat.Builder`. But, when `notification.setLatestEventInfo()` is called later, `Notification.setLatestEventInfo()` uses `Notification.Builder` internally. So even though I'm originally creating the `Notification` with the compatibility library, the platform ends up using the normal `Notification.Builder` in the process of `Notification.setLatestEventInfo()`. We're missing `builder.setDeleteIntent(this.deleteIntent)` in `Notification.setLatestEventInfo()`. – Sean Barbeau Jul 16 '14 at 17:08
  • https://github.com/barbeau/DeleteIntentDemo/tree/NoCompatLibrary illustrates the same issue without the compatibility library involved. I'm still working through the issue in the original app, though. Re-applying the deleteIntent after calling `Notification.setLatestEventInfo()` does fix the problem, but (unlike the demo project) simply eliminating the call to `Notification.setLatestEventInfo()` does not. I'd really like to know why its still not working without the call to `Notification.setLatestEventInfo()`. – Sean Barbeau Jul 16 '14 at 17:28
  • Ah - the original Service-based impl does not work when you eliminate `Notification.setLatestEventInfo()`, but with my tweaks to convert it to BroadcastReceiver, eliminating call to `Notification.setLatestEventInfo()` does fix the issue (just like demo project). So there must be some differing treatment in the platform for handling the deleteIntent for Services vs. BroadcastReceivers. – Sean Barbeau Jul 16 '14 at 17:40
  • I updated the demo project master branch with a 2nd button to send the deleteIntent to a Service. During development I thought I reproduced the issue I'm seeing with the Service not working despite commenting out `Notification.setLatestEventInfo()`, but the demo project seems to work fine with Service now too. I'm marking this as the answer since re-applying the `deleteIntent` seems to be the only viable workaround for both BroadcastReceivers and Services in the original project. I plan to file a bug with AOSP issue tracker for the `Notification.setLatestEventInfo()` deleteIntent problem. – Sean Barbeau Jul 16 '14 at 21:56
  • 1
    Ah - Service issue seems to be my error during testing. So, to summarize, one problem is causing this - `Notification.setLatestEventInfo()` erases any previously registered deleteIntent. Workarounds are to never call this method, or re-register the deleteIntent after calling it. – Sean Barbeau Jul 16 '14 at 22:11
1

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.

Mister Smith
  • 27,417
  • 21
  • 110
  • 193
  • Problem seems to be (at least partially) with use of `Notification.setLatestEventInfo()`. See @CommonsWare answer above. – Sean Barbeau Jul 16 '14 at 14:09