Как отменить текущее уведомление о другом приложении?

Фон

Я нашел приложение, которое каким-то образом скрывает уведомления о хедз-апе, включая даже текущие уведомления (используемые передними службами), называемые NCleaner.

Мне было интересно, как это работает.

Эта проблема

Информация о том, как контролировать уведомления других приложений, не так много. Только из текущего приложения, и по какой-то причине я не смог отменить текущее уведомление, но мне удалось отменить нормальный.

Что я нашел

Я нашел этот пример, показывающий, как отслеживать уведомления. После поиска в документах NotificationListenerService, я также нашел, как отменить определенные уведомления других приложений.

Однако он не работает для текущих уведомлений.

Для тестирования обычных уведомлений я просто отправил себе электронное письмо или написал что-то в приложении PushBullet через свой компьютер.

Для тестирования текущих уведомлений я установил и запустил приложение для свободного времени ( здесь), которое показывает текущее уведомление прямо в начале приложения, начиная с Android O. Фактически, оно может даже скрывать уведомления ОС, USB-соединения и отладки (просто подключите устройство к ПК через USB)...

Здесь код, который я сделал, на основе образца, чтобы отменить все уведомления, которые он получает:

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 предоставляет с точки зрения уведомлений? Глядя на документы, кажется, что он может многое сделать: читать уведомления, увольнять их, получать информацию о них, но я не могу найти другие возможности: возможно ли изменить уведомление? Изменить приоритет? Чтобы избежать его уведомления об уведомлении и просто быть в ящике уведомлений? Возможно ли инициировать действие уведомления?

Ответ 1

Я использовал этот код для создания уведомлений. Он имеет название, текст, значок, 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([email protected], CHANNEL_ID)

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

        val contentAction = PendingIntent.getBroadcast([email protected], 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([email protected])
                .notify(notificationId++, builder.build())

1. Можете ли вы инициировать действия уведомления?

Да, ты можешь. Вы можете получить Notification из StatusBarNotification, а затем получить PendingIntent для действий и использовать их.
Например, я хочу вызвать 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, уведомление будет отложено на пару секунд, а затем снова появится. Проверено на пикселе 2 8.1.

3. Можете ли вы изменить существующее уведомление?

Нет, ты не можешь. Только приложение владельца уведомлений может его изменить.

PS

Я также пытался преодолеть границы с помощью скрытых методов и полей NotificationManager и NotificationListenerService. Результатом всех попыток является:

Вызвано: java.lang.SecurityException: вызов uid 10210 дал пакет com.ns.notificationsender, который принадлежит uid 10211

Таким образом, приложение должно быть системным, или root может помочь добиться успеха. Но у меня нет такого устройства.