Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Notification DeleteIntent broken on later versions of Android

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.

like image 579
Sean Barbeau Avatar asked Jul 15 '14 22:07

Sean Barbeau


2 Answers

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.

like image 131
CommonsWare Avatar answered Oct 22 '22 23:10

CommonsWare


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.

like image 35
Mister Smith Avatar answered Oct 22 '22 21:10

Mister Smith