7

I have a simple NotificationListenerService implementation to test the new 4.3 API. The Service itself used to work. After that, I added the sending of a broadcast when a notification of a particular package is added. Now, as soon as I start the service, it throws a DeadObjectException. This is the stack trace:

    E/NotificationService﹕ unable to notify listener (posted): android.service.notification.INotificationListener$Stub$Proxy@42c047a0
    android.os.DeadObjectException
    at android.os.BinderProxy.transact(Native Method)
    at android.service.notification.INotificationListener$Stub$Proxy.onNotificationPosted(INotificationListener.java:102)
    at com.android.server.NotificationManagerService$NotificationListenerInfo.notifyPostedIfUserMatch(NotificationManagerService.java:241)
    at com.android.server.NotificationManagerService$2.run(NotificationManagerService.java:814)
    at android.os.Handler.handleCallback(Handler.java:730)
    at android.os.Handler.dispatchMessage(Handler.java:92)
    at android.os.Looper.loop(Looper.java:137)
    at com.android.server.ServerThread.run(SystemServer.java:1000)

This is how I start the Service

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.action_start_service:
            startService(new Intent(this, ConnectService.class));
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

I can verify that the Service starts, because I do a Log on it's onCreate() and onDestroy(). And here is how the posting of notifications is handled, if it's needed:

@Override
public void onNotificationPosted(StatusBarNotification sbn) {
    Log.i(TAG, sbn.getNotification().toString());
    if (sbn != null && sbn.getPackageName().equalsIgnoreCase(PKG)) {
        Intent intent = new Intent(ConnectService.NOTIFY);
        intent.putExtra("notification", sbn.getNotification().toString());
        bManager.sendBroadcast(intent);
    }
}

The thing that sucks is that the stack trace is of no use. What's going wrong?

tolgap
  • 9,629
  • 10
  • 51
  • 65

2 Answers2

7

Try not starting the service yourself. If you have enabled the NotificationListenerService in the security settings, the system should bind to it automatically.

Alternatively, check your crash logs to see if your service crashed or its process was killed. I believe there is a bug where if your NotificaitonListerService dies, the system will not rebind until you restart your phone or toggle the notifications permission in security settings.

Gabe
  • 1,239
  • 1
  • 13
  • 20
  • You are absolutely right. I completely forgot about that, and wanted to start the Service myself. – tolgap Jan 28 '14 at 17:50
  • @geecko This isn't a bug. It's the expected behavior. – Gabe Feb 06 '17 at 02:45
  • @Gabe You're the one who used the word bug. My question was: how do you resuscitate the NotificationListenerService if it's been killed (i.e. by a phone that likes to kill background services)? In some cases, toggling the permission in the security settings doesn't work. – geecko May 01 '17 at 16:42
  • @geecko It's likely that some code in your NotificationListenerService crashed. If not then I'm not sure what the issue is. – Gabe May 01 '17 at 23:48
6

I would like to share my answer due the info I collected from different topics in stackoverflow and my own tests. If your NotificationListenerService fails (exception, like IllegalStateException), the system will kill it and not restore it again. You can see that in the logcat:

592-592/? E/NotificationService﹕ unable to notify listener (posted): android.service.notification.INotificationListener$Stub$Proxy@4291d008 
android.os.DeadObjectException
....

If the user goes to Security, Notifications, disable and enable your app, it is still not working. Why? Because it will only by restarted if the user restart the phone or if the user re-enable the option BUT going thru your app using:

startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"));

So we need to check two things, first if the option is enabled:

private boolean checkNotificationSetting() {

    ContentResolver contentResolver = getContentResolver();
    String enabledNotificationListeners = Settings.Secure.getString(contentResolver, "enabled_notification_listeners");
    String packageName = getPackageName();

    return !(enabledNotificationListeners == null || !enabledNotificationListeners.contains(packageName));
}

If it is enabled, we check if the service is Death:

private boolean isNLServiceCrashed() {
    ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    List<ActivityManager.RunningServiceInfo> runningServiceInfos = manager.getRunningServices(Integer.MAX_VALUE);

    if (runningServiceInfos != null) {
        for (ActivityManager.RunningServiceInfo service : runningServiceInfos) {

            //NotificationListener.class is the name of my class (the one that has to extend from NotificationListenerService)
            if (NotificationListener.class.getName().equals(service.service.getClassName())) {

                if (service.crashCount > 0) {
                    // in this situation we know that the notification listener service is not working for the app
                    return true;
                }
                return false;
            }
        }
    }
    return false;
}

What is this service.crashCount ? Documentation says:

Number of times the service's process has crashed while the service is running.

So if its more than 0, it means it's already death. Therefore, in both cases, we have to warning the user and offer the possibility to restart the service using the intent I posted before:

startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"));

Of course, if the service crashes, it will be good to detect why and when to prevent it too.

Ferran Negre
  • 3,712
  • 3
  • 34
  • 56