How to cancel an ongoing notification of another app?
Asked Answered
C

1

15

Background

I've found an app that somehow hides heads-up notifications, including even ongoing notifications (used by foreground services), called NCleaner .

I was wondering how such a thing works.

The problem

There isn't much information of how to control notifications of other apps. Only of the current app, and for some reason I've failed to cancel an ongoing notification, but I've succeeded canceling a normal one.

What I've found

I've found this sample showing how to monitor notifications. After searching on the docs about NotificationListenerService, I've also found how to cancel specific notifications of other apps.

However, it doesn't work for ongoing notifications.

For testing of normal notifications I just sent myself an email or wrote something on PushBullet app via my PC.

For testing of ongoing notifications, I've installed and ran my spare time app (here), which shows an ongoing notification right on the beginning of the app, starting from Android O. In fact, it can even hide the notifications of the OS, of USB connected and debugging (just connect the device to the PC via USB) ...

Here's the code I've made, based on the sample, to cancel all notifications it gets:

NotificationListenerExampleService.kt

class NotificationListenerExampleService : NotificationListenerService() {

    override fun onNotificationPosted(sbn: StatusBarNotification) {
        Log.d("AppLog", "sbn:" + sbn.toString())
        cancelNotification(sbn.key)
    }

    override fun onListenerConnected() {
        super.onListenerConnected()
        Log.d("AppLog", "onListenerConnected")
    }

    override fun onNotificationRemoved(sbn: StatusBarNotification) {}
}

manifest:

<application
  android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"
  tools:ignore="AllowBackup,GoogleAppIndexingWarning">
  <activity android:name=".MainActivity">
    <intent-filter>
      <action android:name="android.intent.action.MAIN"/>

      <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
  </activity>

  <service
    android:name=".NotificationListenerExampleService" android:label="@string/service_label" android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
    <intent-filter>
      <action android:name="android.service.notification.NotificationListenerService"/>
    </intent-filter>
  </service>
</application>

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private val isNotificationServiceEnabled: Boolean
        get() {
            val pkgName = packageName
            val flat = Settings.Secure.getString(contentResolver, "enabled_notification_listeners"")
            if (!TextUtils.isEmpty(flat)) {
                val names = flat.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
                for (i in names.indices) {
                    val cn = ComponentName.unflattenFromString(names[i])
                    if (cn != null) {
                        if (TextUtils.equals(pkgName, cn.packageName)) {
                            return true
                        }
                    }
                }
            }
            return false
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (!isNotificationServiceEnabled) {
            startActivity(Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"))
        }
    }

}

The questions

  1. How come my code can't remove ongoing notifications?
  2. How come the app I've found can do it? What does it do?
  3. Just how much does this API provide in terms of notifications? Looking at the docs, it seems it can do a lot: read notifications, dismiss them, get all kinds of information about them, ... But I can't find some other features: Is it possible to modify a notification? To change its priority? To make it avoid being a heads-up-notification and just be in the notification drawer? Is it possible to trigger an action of a notification?
Counterstamp answered 30/7, 2018 at 5:24 Comment(6)
Maybe it snoozes an unclearable or ongoing notifications? developer.android.com/reference/android/service/notification/…Kolinsky
Can it trigger notification action? Probably can, because it has an access to Notification via StatusBarNotification, and you can get PendingIntents from NotificationKolinsky
@Kolinsky Seems to work well. Why not put as an answer so that I could accept it? Also, do you know of alternatives to this, as it requires Android O ?Counterstamp
Also, can you answer the other questions?Counterstamp
I am going to write a full answer tomorrow or on Monday, if nobody will do it before me)Kolinsky
@Kolinsky Thank you. About triggering, I'm not sure how to do it. The snoozing part worked though.Counterstamp
K
6

I used this code for notification building. It has title, text, icon, contentAction as Broadcast Message. It is enough for answering your questions, I hope)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            val notChannel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT)
            notManager.createNotificationChannel(notChannel)
        }

        val builder = NotificationCompat.Builder(this@MainActivity, CHANNEL_ID)

        val contentIntent = Intent(NOT_ACTION)
        contentIntent.putExtra(EXTRA_NOT_ID, notificationId)

        val contentAction = PendingIntent.getBroadcast(this@MainActivity, NOT_REQ_CODE, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT)

        builder.setContentTitle("Notification $notificationId")
                .setContentText("Awesome text for $notificationId notification")
                .setContentIntent(contentAction)
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .setAutoCancel(true)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setCategory(NotificationCompat.CATEGORY_MESSAGE)
                .setDefaults(NotificationCompat.DEFAULT_ALL)

        NotificationManagerCompat
                .from(this@MainActivity)
                .notify(notificationId++, builder.build())

1. Can you trigger an actions of notification?

Yes, you can. You can get Notification from StatusBarNotification, and then get PendingIntents for actions and use them.
For example, I want to trigger a contentAction of notification

override fun onNotificationPosted(sbn: StatusBarNotification) {
    super.onNotificationPosted(sbn)

    Log.d(TAG, "Gotcha notification from ${sbn.packageName}")

    if (sbn.packageName == TARGET_PACKAGE) {
        val contentIntent = sbn.notification.contentIntent
        contentIntent.send()
    }
}

This code will trigger sending of Broadcast message. BUT it will ignore a .setAutoCancel(true) of notification, so you need to handle it manually like this

    if (sbn.packageName == TARGET_PACKAGE) {
        val contentIntent = sbn.notification.contentIntent
        contentIntent.send()
        if (sbn.notification.flags and Notification.FLAG_AUTO_CANCEL != 0) {
            cancelNotification(sbn.key)
        }
    }

2. How NCleaner cancels ongoing notifications?

The first way which comes on my mind is usage of NotificationListenerService.snoozeNotification like this

if (sbn.packageName == TARGET_PACKAGE) {
        snoozeNotification(sbn.key, Long.MAX_VALUE - System.currentTimeMillis())
}

I'm 99% percent sure, that NCLeaner uses this code. Because it doesn't support this feature for pre-O devices.

Note. You can't just use Long.MAX_VALUE, notification will be snoozed for a couple of seconds, and then appear again. Tested on Pixel 2 8.1.

3. Can you modify an existing notification?

No, you cannot. Only notification owner app can modify it.

P.S.

I also tried to overcome limits with reflection via hidden methods and fields of NotificationManager and NotificationListenerService. The result of all attempts is:

Caused by: java.lang.SecurityException: Calling uid 10210 gave package com.ns.notificationsender which is owned by uid 10211

So you app need to be system one, or maybe root can help to succeed. But I don't have such device.

Kolinsky answered 6/8, 2018 at 11:40 Comment(8)
1. The send will work even if it's not your own notification? 2. For some reason, using Long.MAX_VALUE worked for me on Android P, but maybe it's just a duration, so setting 100 years should be enough? 3. You mean you took a notification you got from onNotificationPosted, and you snoozed it, and then you modified it using reflection and sent it as your own? What kind of changes did you try to do? On which step did it crash? Probably on the notify call of the notification, right?Counterstamp
@androiddeveloper 1. Yes, I created to separate android apps. NotificationSender and NotificationListener. Listener can use send() of notification provided by sender. 2. Yes, the second param is duration in ms. So 100 years must be enough. On my Pixel 2 8.1 Long.MAX_VALUE leads to strange behavior.Kolinsky
@androiddeveloper 3. You can change any field you want in sbn.notification, BUT I didn't find a way to update a notification in status bar. When you call notify it acts, like you sending your own app notification, and not updating caught one. When I tried to play with uids and packages, to act like notification owner app - SecurityException.Kolinsky
1. Nice. 2. I see. 3. Oh you mean you didn't snooze it, and you used the same notification and tried to modify it. Right?Counterstamp
@androiddeveloper Yes. In my receiver I changed title of caught notification. But I didn't find a way to deliver changes to status bar. It is possible to modify caught notification, delete original one, and post caught notification as your own, but there will problems in this case. The most obvious are IDs conflicts, notification channel handling, and if owner-app will try to update the original notification it will throw new one, instead of update, because your receiver deleted it, so you must handle it manually.Kolinsky
hi @Kolinsky I want to mute all notification from all other apps.I tried cancelNotification unfortunately i am getting ongoing notification after that only its canceled because i tried to cancel in onNotifcationPosted method.i want to mute ongoing notification alsoSomehow
@Lavanya Velusamy according to my answer, you cannot cancel ongoing notifications, you can only snooze them for long time period to imitate cancelling. I don't know any other wayKolinsky
Thanks @Kolinsky but snoozing notification only possible for devices above oreo and also i found that after snoozing user have to reboot their device for make it effectiveSomehow

© 2022 - 2024 — McMap. All rights reserved.