NotificationListenerServiceを導入する

2018-03-30 14:38:33 · 1055 words · 3 minute read Android

設定して権限を与えると、表示されている通知の内容を覗き放題になるNotificationListenerService。
あまり使いたくはなかったのですが、使わざるを得ない状況となったので嵌ったポイントなどを残しておきます。

NotificationListenerService

NotificationListenerServiceを継承したクラスを作り、AndroidManifest.xmlに適切な設定を書いて、システム設定で権限を与えると現在表示されている通知をパッケージに関係なく取得可能になります。
権限付与時に影響範囲を絞る事ができないため、ユーザにとってはできる限り避けたい機能を提供するAPIだと言えるかもしれません。

既知のバグ (仕様?)

NotificationListenerServiceを継承したクラスに設定から権限を付与して、色々やっているうちに通知内容が取得できなくなることがあります。
その際、既に色々なところで言われていますが、権限の振り直しやあやしいところのデバッグなど、何をしても治らなくなることがあり、そうなると継承しているクラスの名前を変えるしかなくなってしまいます。

アプリのアップデート時にもこのバグが確認されているそうで、注意が必要です。

ワークフロー

NotificationListenerServiceを継承したクラスの定義

Google Play Musicアプリの通知を監視するシンプルなサンプルを載せておきます。

GPMNotificationListenerService.kt :

import android.app.Notification
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import timber.log.Timber

class GPMNotificationListenerService : NotificationListenerService() {

    companion object {
        private const val PACKAGE_NAME_GPM: String = "com.google.android.music"
    }

    override fun onNotificationPosted(sbn: StatusBarNotification?) {
        super.onNotificationPosted(sbn)
        if (sbn == null) return

        if (sbn.packageName == PACKAGE_NAME_GPM)
            onUpdate(sbn.notification)
    }

    private fun onUpdate(notification: Notification) {
        notification.extras.apply {
            val track: String? = if (containsKey(Notification.EXTRA_TITLE)) getString(Notification.EXTRA_TITLE) else null
            val artist: String? = if (containsKey(Notification.EXTRA_TEXT)) getString(Notification.EXTRA_TEXT) else null
            val album: String? = if (containsKey(Notification.EXTRA_INFO_TEXT)) getString(Notification.EXTRA_SUB_TEXT) else null
            val artwork: Bitmap = (notification.getLargeIcon().loadDrawable(this@GPMNotificationListenerService) as BitmapDrawable).bitmap
            Timber.d("track: $track, artist: $artist, album: $album, artwork: $artwork")
        }
    }
}

定義したクラスをAndroidManifest.xmlに登録

必ず指定しなければいけないパラメータがあります。ミニマムな定義を以下に示します。

AndroidManifest.xml :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.geckour.notificationlistenerserviceexsample">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        ...

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

        ...

    </application>
</manifest>

定義したクラスへ権限を付与

定義したクラスに権限を付与しなくても即アプリがクラッシュするということはありません。
しかし、権限がないと、定義したクラスが自動的に実行されることはなく、通知状況が変化しても何も起こりません。
権限の確認と設定画面の表示に関するサンプルコードを以下に示します。

SampleActivity.kt :

class SampleActivity: Activity() {

    enum class RequestCode {
        GRANT_NOTIFICATION_LISTENER
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        requestNotificationListenerPermission()
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        when (requestCode) {
            RequestCode.GRANT_NOTIFICATION_LISTENER.ordinal -> {
                requestNotificationListenerPermission()
            }
        }
    }

    private fun requestNotificationListenerPermission(onGranted: () -> Unit = {}) {
        if (NotificationManagerCompat.getEnabledListenerPackages(this).contains(packageName).not()) {
            AlertDialog.Builder(this)
                    .setTitle(R.string.dialog_title_alert_grant_notification_listener)
                    .setMessage(R.string.dialog_message_alert_grant_notification_listener)
                    .setCancelable(false)
                    .setPositiveButton(R.string.dialog_button_ok) { dialog, _ ->
                        startActivityForResult(
                                Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"),
                                RequestCode.GRANT_NOTIFICATION_LISTENER.ordinal)
                        dialog.dismiss()
                    }.show()
        } else onGranted()
    }
}

おしまい

これで好きなだけ通知と戯れることができます。
用法・用量を正しく守って素敵なAndroidライフを。

tweet Share