访问Android NotificationListenerService设置

15

Android自4.3版本起拥有新的通知监听服务: http://developer.android.com/about/versions/jelly-bean.html http://developer.android.com/reference/android/service/notification/NotificationListenerService.html

根据文档:

默认情况下,通知访问被禁用 - 应用程序可以使用新 Intent,在安装后直接将用户带到设置中启用监听服务。

我没有找到文档中记录要触发的Intent。阅读设置文档似乎没有什么帮助: http://developer.android.com/reference/android/provider/Settings.html

查看Settings类本身: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/provider/Settings.java

我看到ACTION_NOTIFICATION_LISTENER_SETTINGS已经定义,但是在使用Android Studio并指向4.3时,ACTION_NOTIFICATION_LISTENER_SETTINGS不能被解析:

Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);

手动尝试似乎不起作用:

Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName("com.android.settings", "android.settings.NOTIFICATION_LISTENER_SETTINGS");

编辑:如下所示,按照CommonsWare在下面指出的正确方式进行操作:

Intent intent=new Intent("android.settings.NOTIFICATION_LISTENER_SETTINGS");

导致崩溃:

(android.content.ActivityNotFoundException: 找不到可处理意图{ act=android.settings.NOTIFICATION_LISTENER_SETTINGS }的活动)

我漏掉了什么吗?我不确定如何将用户发送到正确的设置屏幕以在我的应用程序中启用此服务。


有趣的问题。使用新的API是否可以获取通知文本、描述等信息? - Cilenco
是的,一旦服务设置好了,您就可以直接获取通知(http://developer.android.com/reference/android/app/Notification.html)。 - powerj1984
是的,但从那里开始,您必须使用“Parcel”进行操作,这并不容易获取通知中的所有不同文本消息。 - Cilenco
你是否已经成功在你的应用中使用NotificationListenerService类?https://dev59.com/FmMm5IYBdhLWcg3wDrpR#17916173 - CeejeeB
@Cilenco 我认为你正在使用 SDK < 18.. 这就是为什么你看不到 StatusBarNotification 的原因。 - AJit
显示剩余2条评论
5个回答

19

我有什么地方理解错了吗?

嗯,在你最后一个问题中,你把一个操作字符串和一个类名混淆了。手动的方法应该是:

Intent intent=new Intent("android.settings.NOTIFICATION_LISTENER_SETTINGS");

关于为什么Android Studio找不到Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS的原因,我无法确定。


更新

根据评论中的讨论,Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS目前不在Android SDK中(标记为@hide)。此外,Settings应用程序的清单具有稍微不同版本的操作字符串:

Intent intent=new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");

原来ACTION_NOTIFICATION_LISTENER_SETTINGS在其文档中有@hide,我猜这里不应该有 - 虽然我认为它只会隐藏文档,而不是使其无法在Android Studio中解决。不幸的是,按照您答案中提到的方式尝试会导致崩溃: Caused by: android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.settings.NOTIFICATION_LISTENER_SETTINGS - powerj1984
1
@powerj1984:"原来ACTION_NOTIFICATION_LISTENER_SETTINGS在其文档中有@hide" - 我没有在您提供的源代码中看到这一点。 "尽管我认为它只是隐藏了文档" - 不,@hide会将该项从SDK完全移除。关于 ActivityNotFoundException,您正在测试什么? - CommonsWare
1
哦,糟糕!我忘记在字符串开头加上“ACTION_”位了。这样做可以让我直接使用该字符串。 - powerj1984
1
@powerj1984:这真的很有趣。通常,操作的字符串表示形式中没有ACTION_,这符合Settings中的@hide条目。但是,在清单条目中,有ACTION_,因此在Intent中的动作中也需要它。看他们以后如何协调这一切会很有趣。与此同时,我会更新答案。 - CommonsWare
1
我收集了这里的信息,并在问题跟踪器上报告了错误:https://code.google.com/p/android/issues/detail?id=58030&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars。 - powerj1984
显示剩余3条评论

7

除了CommonsWare的答案外,这里是如何检查您是否拥有该权限的方法

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {        
    if (!NotificationManagerCompat.getEnabledListenerPackages(this).contains(getPackageName())) {        //ask for permission
       Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
       startActivity(intent);
    }
}

1
你不必检查集合中的每个项目是否等于当前项目。直接使用“contains”更好,而且代码更短。 - android developer
1
这是正确的@androiddeveloper,我也已经更改了示例。 - Galeen
1
你正在请求权限,如果包已启用,但实际上你只需要询问它是否未启用! - Andris
1
@Andris 这是真的,已更改。 - Galeen

7
这就是你今天可以使用的方式,因为它会尝试去做尽可能少的步骤来帮助用户打开它。
@TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)
fun getIntentForNotificationAccess(packageName: String, notificationAccessServiceClass: Class<out NotificationListenerService>): Intent =
        getIntentForNotificationAccess(packageName, notificationAccessServiceClass.name)


@TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)
fun getIntentForNotificationAccess(packageName: String, notificationAccessServiceClassName: String): Intent {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        return Intent(Settings.ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS)
                .putExtra(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME, ComponentName(packageName, notificationAccessServiceClassName).flattenToString())
    }
    val intent = Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS)
    val value = "$packageName/$notificationAccessServiceClassName"
    val key = ":settings:fragment_args_key"
    intent.putExtra(key, value)
    intent.putExtra(":settings:show_fragment_args", Bundle().also { it.putString(key, value) })
    return intent
}

示例用法:

startActivityForResult(getIntentForNotificationAccess(packageName, NotiService::class.java), REQUEST_CODE)

或:
startActivityForResult(getIntentForNotificationAccess(packageName, NotiService::class.java.name), REQUEST_CODE)

请注意,遗憾的是在其他情况下这是不可能的,甚至无法进行高亮和聚焦。对于这些情况,我已经提出了以下请求:

这些神奇的键来自这里 - Vlad
@Vlad 谢谢你。你是怎么找到它的?我无法访问这个文件。你知道是否有一种方法也可以用这些来获取 Android 11 的系统警报窗口权限吗? - android developer
这些参数仅对Android 9有效(我的应用程序将在列表中简单突出显示)。我已经测试过多个版本。 - Vlad
@Vlad 真遗憾。你知道处理这个“魔法”需要哪些特殊权限吗? - android developer
在我的设备S22 Ultra Android 13上,我移除了if{Build.VERSION_CODES.R}的代码块检查,现在我的应用程序可以正常运行并突出显示。 - Mateen Chaudhry

2

感谢@android开发者提供的出色答案,但我有一点澄清。

你必须在try/catch块内调用startActivity,因为某些操作系统(例如安卓11上的荣耀/华为)没有“Settings.ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS”,所以会抛出此异常:

No Activity found to handle Intent { act=android.settings.NOTIFICATION_LISTENER_DETAIL_SETTINGS (has extras) }

使用方法如下:

try {
    startActivity(getIntentForNotificationAccess(packageName, NotiService::class.java))
} catch (e: Exception) {
    if (Build.VERSION.SDK_INT >= 22) {
        try {
            startActivity(Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS))
        } catch (e1: Exception) {
            e1.printStackTrace()
        }
    }
}

0
我测试了@android开发者的代码,但在我的设备上(三星S22 Ultra Android 13)无法正常工作。因此,我修改了他的代码,现在它可以打开NotificationListenerService设置并突出显示筛选我的应用程序。
 @TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)
fun getIntentForNotificationAccess(context: Context,  notificationAccessServiceClass: Class<out NotificationListenerService>): Intent {

    val intent:Intent
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        intent= Intent(Settings.ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS)
        intent.putExtra(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME, ComponentName(context.packageName, notificationAccessServiceClass.name).flattenToString())
    }else{
         intent = Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS)
    }
    val value = "${context.packageName}/${notificationAccessServiceClass.name}"
    val key = ":settings:fragment_args_key"
    intent.putExtra(key, value)
    intent.putExtra(":settings:show_fragment_args", Bundle().also { it.putString(key, value) })
    return intent
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接