14

背景

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

我想知道这样的事情是如何工作的。

问题

没有太多关于如何控制其他应用程序通知的信息。只有当前的应用程序,由于某种原因,我未能取消正在进行的通知,但我已经成功取消了一个正常的通知。

我发现了什么

我发现这个示例展示了如何监控通知。在搜索有关NotificationListenerService的文档后,我还发现了如何取消其他应用程序的特定通知。

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

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

为了测试持续通知,我安装并运行了我的业余时间应用程序(这里),它在应用程序的开头显示一个持续通知,从 Android O 开始。事实上,它甚至可以隐藏操作系统的通知, 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 在通知方面提供了多少?查看文档,它似乎可以做很多事情:阅读通知,关闭它们,获取有关它们的各种信息,......但我找不到其他一些功能:是否可以修改通知?改变它的优先级?为了避免成为提醒通知而只是在通知抽屉中?是否可以触发通知操作?
4

1 回答 1

4

我将此代码用于通知构建。它具有标题、文本、图标、contentAction作为广播消息。回答你的问题就够了,希望)

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

是的你可以。您可以Notification从中获取StatusBarNotification,然后获取PendingIntents 以获取操作并使用它们。
比如我想触发一个contentAction的通知

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. 可以修改现有通知吗?

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

附言

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

引起:java.lang.SecurityException:调用 uid 10210 给了包 com.ns.notificationsender,它由 uid 10211 拥有

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

于 2018-08-06T11:40:13.913 回答