seong

Flutter + Kotlin - 다른 앱의 알림 받기 ( NotificationListenerService ) 본문

Kotlin

Flutter + Kotlin - 다른 앱의 알림 받기 ( NotificationListenerService )

hyeonseong 2024. 7. 8. 10:39

Flutter로 앱을 개발 중 Ble기기에 AOS로 오는 앱의 알림을 전달 해주는 기능이 필요해졌다.
AOS 기기에서 나의 앱에서 다른 앱들의 알림 읽어오는 방법

 

받는 방식은 간단하게 아래 처럼 진행된다.

NotificationListenerService 객체 및 Listener 생성 -> Listener로 MainActivity에 전달 -> Flutter에 전달

1. 권한 추가 

    <service
        android:name=".CustomNotificationService"
        android:exported="false"
        android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
    <intent-filter>
        <action android:name="android.service.notification.NotificationListenerService" />
    </intent-filter>
    </service>

2. MainActivity

알림을 받기 위해 권한을 확인 및 받는 부분, 데이터를 MainActivity에서 Listen하는 부분이 있다.

class MainActivity : FlutterActivity(),AppNotificationListener{

    lateinit var methodChannel: MethodChannel
    private val customService = CustomNotificationService()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        //OS의 notification을 가져오기 위한 권한 선언
        if(!isNotificationPermissionGranted()) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
                startActivity(Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS))
            }
        }
        //Listener 등록
        customService.addNotificationListener(this)

    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "android")
        methodChannel.setMethodCallHandler { call, result ->
            when (call.method) {
                // .. 메서드 채널 통신
            }
        }
    }

   
    /**
     * Notification 권한 체크
     */
    private fun isNotificationPermissionGranted(): Boolean {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
            val notificationManager =
                getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            return notificationManager.isNotificationListenerAccessGranted(
                ComponentName(
                    application,
                    CustomNotificationService::class.java
                )
            )
        } else {
            return NotificationManagerCompat.getEnabledListenerPackages(applicationContext)
                .contains(applicationContext.packageName)
        }
    }

// Service에서 notification이 되면 함수 실행
    override fun readNotification(notification: NotificationModel) {
    // NotificationModel로 메서드 채널에 전달 할 수 없어서 toMap 해줌.
        val notificationMap = mapOf(
            "packageName" to notification.packageName,
            "title" to notification.title,
            "text" to notification.text
        )
        methodChannel.invokeMethod("notification", notificationMap)
    }

}

3.  Listener

interface AppNotificationListener {
    fun readNotification(notification: NotificationModel)
}

4. CustomNotificationService 작성

싱글톤으로 listener를 생성했고, 알림 데이터가 생성 될 때 마다 listener를 통해서 알려주었다. 

모든 알림에 대해 전달해줄 필요가 없어 내가 원하는 packageName만 필터링도 작성했다.

import android.app.Notification
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import android.util.Log

class CustomNotificationService :NotificationListenerService(){

    companion object {
        val listeners = ArrayList<AppNotificationListener>()
    }
    
    // MainActivity에서 listen할 수 있도록 listener 선언
        fun addNotificationListener(listener: AppNotificationListener){
        listeners.add(listener)
    }
    
    // 최초 Service가 연결 되었을때 호출
    override fun onListenerConnected( ) {
        super.onListenerConnected()
    }
    
		// Service가 닫혔을 경우 호출
    override fun onListenerDisconnected() {
        super.onListenerDisconnected()
    }
    
    // OS에 Notification이 생겼을 경우 호출
    override fun onNotificationPosted(sbn: StatusBarNotification?) {
        super.onNotificationPosted(sbn)
        
        val extras = sbn?.notification?.extras
 
		// 변수
        val extraTitle = extras?.getString(Notification.EXTRA_TITLE)
        val extraText = extras?.getString(Notification.EXTRA_TEXT)
        val extraApp =  sbn?.packageName
        
        
        // 내가 필요한 패키지 명들만 필터링
        if(!filterByPackageName(extraApp.toString())){
            Log.d("Notification","Filtered : $extraApp")
            return
        }
        
        // 필터링 한 데이터를 MainActivity에 전달
        val notificationModel = NotificationModel(extraApp.toString(),extraTitle.toString(),extraText.toString())
        for(listener in listeners){
            listener.readNotification(notificationModel)
        }
    }
    
    // PackageName Filter함수
    private fun filterByPackageName(packageName: String): Boolean {
        return PackageNameEnum.entries.any { packageEnum ->
            packageName.contains(packageEnum.packageName, ignoreCase = true)
        }
    }

}