如何取消另一个应用程序的持续通知?

How to cancel an ongoing notification of another app?

背景

我找到了一个应用程序,它以某种方式隐藏了提醒通知,甚至包括正在进行的通知(由前台服务使用),名为 NCleaner

我想知道这样的东西是如何工作的。

问题

关于如何控制其他应用的通知的信息不多。仅限当前应用程序,出于某种原因,我无法取消正在进行的通知,但我成功取消了普通通知。

我发现了什么

我找到了this sample showing how to monitor notifications. After searching on the docs about NotificationListenerService,我还找到了如何取消其他应用的特定通知。

但是,它不适用于正在进行的通知。

为了测试正常的通知,我只是给自己发了一封电子邮件,或者通过我的电脑在 PushBullet 应用程序上写了一些东西。

为了测试持续通知,我安装了 运行 我的业余时间应用程序 (here),它会在应用程序的开头显示持续通知,从 Android O. 事实上,它甚至可以隐藏 OS、USB 连接和调试的通知(只需通过 USB 将设备连接到 PC)...

这是我根据示例编写的代码,用于取消它收到的所有通知:

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) {}
}

清单:

<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"))
        }
    }

}

问题

  1. 为什么我的代码无法删除正在进行的通知?
  2. 为什么我找到的应用程序可以做到这一点?它有什么作用?
  3. 这个 API 在通知方面提供了多少?查看文档,它似乎可以做很多事情:读取通知、关闭它们、获取有关它们的各种信息……但我找不到其他一些功能:是否可以修改通知?改变它的优先级?为了避免成为单挑通知而只是在通知抽屉里?是否可以触发通知的动作?

我使用此代码构建通知。它有标题、文本、图标、contentAction 作为 Broadcast Message。希望能回答你的问题)

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。可以触发通知动作吗?

是的,你可以。你可以从StatusBarNotification得到Notification,然后得到PendingIntent的动作并使用它们。
比如我想触发一个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()
    }
}

此代码将触发发送广播消息。但是它会忽略 .setAutoCancel(true) 的通知,所以你需要像这样手动处理它

    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。 NCleaner 如何取消正在进行的通知?

我想到的第一种方式是像这样使用 NotificationListenerService.snoozeNotification

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

我 99% 确定 NCLeaner 使用此代码。因为它不支持 pre-O 设备的此功能。

注。您不能只使用 Long.MAX_VALUE,通知会暂停几秒钟,然后再次出现。在 Pixel 2 8.1 上测试。

3。您可以修改现有通知吗?

不,你不能。 只有通知所有者应用程序可以修改它。

P.S.

我还尝试通过 NotificationManagerNotificationListenerService 的隐藏方法和字段来克服反射的限制。所有尝试的结果是:

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

所以你的应用程序需要是系统第一,或者 root 可以帮助成功。但是我没有这样的设备。