I am trying to make a React Native Expo Module to read the notifications on my device. I have tried other StackOverflow answers (this and this) and I haven't been able to get it to work. I setup the NotificationListenerService and am trying to use the getActiveNotifications()
method but it is returning an empty array. Here is the relevant source code:
ReadNotificationsModule.kt:
package expo.modules.readnotifications
import android.util.Log
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import android.content.Intent
import android.provider.Settings
import androidx.core.app.NotificationManagerCompat
class ReadNotificationsModule : Module() {
private lateinit var notificationListenerService: ReadNotificationsService
// Each module class must implement the definition function. The definition consists of components
// that describes the module's functionality and behavior.
// See https://docs.expo.dev/modules/module-api for more details about available components.
override fun definition() = ModuleDefinition {
// Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
// Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
// The module will be accessible from `requireNativeModule('ReadNotifications')` in JavaScript.
Name("ReadNotifications")
OnCreate {
notificationListenerService = ReadNotificationsService()
}
// Sets constant properties on the module. Can take a dictionary or a closure that returns a dictionary.
Constants(
"PI" to Math.PI
)
// Defines event names that the module can send to JavaScript.
Events("onChange")
// Defines a JavaScript synchronous function that runs the native code on the JavaScript thread.
Function("hello") {
"Hello world! "
}
Function("getNotifications") {
notificationListenerService.getActiveNotifications()
}
Function("getPermission") {
// Log.wtf(TAG, "getPermission")
// val i = Intent();
// i.setAction(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
// i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
// i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
// startActivity(i);
notificationListenerService.getPermission(appContext)
}
Function("checkPermissions") {
notificationListenerService.checkPermissions(appContext)
}
// Defines a JavaScript function that always returns a Promise and whose native code
// is by default dispatched on the different thread than the JavaScript runtime runs on.
AsyncFunction("setValueAsync") { value: String ->
// Send an event to JavaScript.
sendEvent("onChange", mapOf(
"value" to value
))
}
// Enables the module to be used as a native view. Definition components that are accepted as part of
// the view definition: Prop, Events.
View(ReadNotificationsView::class) {
// Defines a setter for the `name` prop.
Prop("name") { view: ReadNotificationsView, prop: String ->
Log.d("TAG", prop)
}
}
}
}
ReadNotificationsService.kt:
package expo.modules.readnotifications
import android.util.Log
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import android.content.Intent
import android.os.IBinder
import android.provider.Settings
import androidx.core.app.NotificationManagerCompat
import expo.modules.kotlin.AppContext
class ReadNotificationsService : NotificationListenerService() {
private val TAG = "ReadNotificationsService"
private var ready = false
override fun onListenerConnected() {
Log.wtf(TAG, "onListenerConnected")
ready = true
}
override fun onListenerDisconnected() {
Log.wtf(TAG, "onListenerDisconnected")
ready = false
}
//check notification access setting is enabled or not
fun checkPermissions(context: AppContext): String {
val packageName = context.reactContext?.getPackageName()
println("packageName: " + packageName)
val enabledPackages = NotificationManagerCompat.getEnabledListenerPackages(this)
if (enabledPackages.contains(packageName)) {
return "authorized"
} else {
return "denied"
}
}
fun getPermission(context: AppContext) {
Log.wtf(TAG, "getPermission")
// val intent = Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")
// startActivity(intent)
// startActivity(Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS));
// startActivity(Intent(Settings.ACTION_SETTINGS))
val i = Intent();
i.setAction(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
context.reactContext?.startActivity(i);
}
override fun onNotificationPosted(sbn: StatusBarNotification) {
Log.wtf(TAG, "onNotificationPosted")
Log.wtf(TAG, sbn.toString())
}
override fun onNotificationRemoved(sbn: StatusBarNotification) {
Log.wtf(TAG, "onNotificationRemoved")
Log.wtf(TAG, sbn.toString())
}
override fun onBind(intent: Intent): IBinder? {
return super.onBind(intent)
}
}
AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="expo.modules.readnotifications">
<uses-permission android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" />
<application>
<service android:name=".ReadNotificationsService"
android:enabled="true"
android:exported="false"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
<meta-data
android:name="android.service.notification.default_filter_types"
android:value="conversations|alerting|ongoing|silent">
</meta-data>
</service>
</application>
</manifest>