AR 眼镜之-系统通知定制(通知弹窗)-实现方案

devtools/2024/11/18 18:49:11/

目录

📂 前言

AR 眼镜系统版本

系统通知定制

1. 🔱 技术方案

1.1 技术方案概述

1.2 实现方案

1)实现系统通知的监听

2)系统通知显示:通知弹窗

2. 💠 实现系统通知的监听

NotificationListenerService-toc" style="margin-left:80px;">2.1 继承 NotificationListenerService

2.2 在 manifest 中声明这个可接收通知的服务

2.3 让通知应用拥有获取系统通知的权限

1)通知应用申明可获取系统通知使用权限

2)判断通知应用是否拥有可获取系统通知的权限

3)打开通知权限设置页面

3. ⚛️ 系统通知显示:通知弹窗

3.1 统一处理通知

Notification%20%E5%88%86%E5%8F%91%E8%B7%AF%E7%94%B1-toc" style="margin-left:120px;">1)每条通知到来时由 handleNotification 分发路由

NotificationManagerBean%20%E5%8C%BA%E5%88%86%20AR%20%E7%9C%BC%E9%95%9C%E9%80%9A%E7%9F%A5%E4%BB%A5%E5%8F%8A%E4%B8%8E%20AR%20%E7%9C%BC%E9%95%9C%E8%BF%9E%E6%8E%A5%E7%9A%84%E6%89%8B%E6%9C%BA%E9%80%9A%E7%9F%A5-toc" style="margin-left:120px;">2)NotificationManagerBean 区分 AR 眼镜通知以及与 AR 眼镜连接的手机通知

3)飞行模式时不显示系统通知

3.2 播放通知音效

3.3 showDialog 显示通知弹窗

NotificationLayoutDialogBinding%20%E5%8A%A0%E8%BD%BD%E9%80%9A%E7%9F%A5%E5%BC%B9%E7%AA%97%20View-toc" style="margin-left:120px;">1)NotificationLayoutDialogBinding 加载通知弹窗 View

2)getAppName 获取 app 名

Notification%20%E6%98%BE%E7%A4%BA%E9%80%9A%E7%9F%A5%E5%BC%B9%E7%AA%97%20View-toc" style="margin-left:120px;">3)showNotification 显示通知弹窗 View

4. ✅ 小结

SystemUI%20%E6%B5%81%E7%A8%8B-toc" style="margin-left:40px;">附录1:SystemUI 流程

NotificationListenerService%20%E7%9B%91%E5%90%AC%E9%80%9A%E7%9F%A5-toc" style="margin-left:40px;">附录2:使用 NotificationListenerService 监听通知


📂 前言

AR 眼镜系统版本

        W517 Android9。

系统通知定制

        系统通知的底层 实现主要依赖 Android 原生通知模块 NotificationManagerService系统通知的上层 UI 主要依赖于继承 NotificationListenerService 去实现,实现过程如下图所示,主要分为三步:1)应用 A 通过 sendNotification 发送通知;2)Android 通知模块 NotificationManagerService 接收到通知;3、应用 B 通过继承 NotificationListenerService 监听到系统通知。对于底层实现感兴趣的同学可自行去深入了解,本文所讨论的系统通知实现方案主要针对于上层 UI。

        那么,Android 原生系统通知是怎样实现的呢?答案很简单:通过 SystemUI 应用实现,SystemUI 通过继承 NotificationListenerService 监听系统通知,然后显示在通知栏。

        但是,AR 眼镜系统与传统 Android 2D 存在较大显示与交互差异,且根据产品需求综合来看,本文采用类似 SystemUI 的方案,通知应用 通过继承 NotificationListenerService 实现系统通知的监听与显示。

1. 🔱 技术方案

1.1 技术方案概述

        通知应用 通过继承 NotificationListenerService 实现系统通知的监听与显示,上层 UI 主要包括:通知弹窗、通知中心,系统通知定制的实现方案将分为两个篇章展开,分别是 通知弹窗通知中心篇

1.2 实现方案

1)实现系统通知的监听
  1. 继承 NotificationListenerService,实现 onNotificationPosted 方法;

  2. 在 manifest 中声明这个可接收通知的服务;

  3. 让通知应用拥有获取系统通知的权限。

2)系统通知显示:通知弹窗
  1. 统一处理通知;

  2. 播放通知音效;

  3. 显示与隐藏通知弹窗

2. 💠 实现系统通知的监听

NotificationListenerService">2.1 继承 NotificationListenerService

        主要实现其中的 onNotificationPosted(sbn: StatusBarNotification) 方法,其他方法可按需实现,如: onNotificationRemoved(sbn: StatusBarNotification)、onListenerConnected()、onListenerDisconnected()。

class AGGNotificationListenerService : NotificationListenerService() {override fun onNotificationPosted(sbn: StatusBarNotification) {super.onNotificationPosted(sbn)Log.i(TAG, "onNotificationPosted: packageName = ${sbn.packageName}")// 普通通知:未设置Style// 设置点击 setContentIntent// 设置按钮 addAction(最多可添加三个)// 设置进度条 setProgress// 设置自定义通知 setCustomContentView(RemoteViews)// 设置自定义通知展开视图 setCustomBigContentView(RemoteViews)// 设置自定义顶部提醒视图 setCustomHeadsUpContentView(RemoteViews(context.getPackageName(),R.layout.custom_heads_up_layout))// 带图标样式 setLargeIcon// 1. 过滤黑名单包名的通知。if (BLACK_LISTING_PACKAGE_NAME.contains(sbn.packageName)) return// 2. 过滤空内容消息通知val title = sbn.notification.extras.getString(Notification.EXTRA_TITLE, "")val content = sbn.notification.extras.getCharSequence(Notification.EXTRA_TEXT, "")if (title.isEmpty() && content.isEmpty()) returnif (content == getString(R.string.app_running_notification_text)) return // 去掉通知: “记录”正在运行,点按即可了解详情或停止应用AGGNotificationManager.handleNotification(this, NotificationManagerBean(NotificationManagerBean.FROM_GLASS, sbn))}override fun onNotificationRemoved(sbn: StatusBarNotification) {super.onNotificationRemoved(sbn)Log.i(TAG, "onNotificationRemoved: packageName = ${sbn.packageName}")}override fun onListenerConnected() {super.onListenerConnected()Log.i(TAG, "onListenerConnected: ")}override fun onListenerDisconnected() {super.onListenerDisconnected()Log.i(TAG, "onListenerDisconnected: ")}companion object {private val TAG = AGGNotificationListenerService::class.java.simpleNameprivate val BLACK_LISTING_PACKAGE_NAME =// Android系统通知、Android电话通知mutableSetOf("android", "com.android.dialer", "com.android.server.telecom")}}

2.2 在 manifest 中声明这个可接收通知的服务

<serviceandroid:name=".AGGNotificationListenerService"android:exported="true"android:label="AGG Notification"android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"><intent-filter><action android:name="android.service.notification.NotificationListenerService" /></intent-filter>
</service>

2.3 让通知应用拥有获取系统通知的权限

1)通知应用申明可获取系统通知使用权限
<uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS" />
2)判断通知应用是否拥有可获取系统通知的权限
fun isNotificationListenersEnabled(context: Context, packageName: String): Boolean = NotificationManagerCompat.getEnabledListenerPackages(context).contains(packageName)
3)打开通知权限设置页面
fun gotoNotificationAccessSetting(context: Context): Boolean {return try {val intent = Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)context.startActivity(intent)true} catch (e: ActivityNotFoundException) {// 普通情况下找不到的时候需要再特殊处理找一次try {val intent = Intent()intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)val cn = ComponentName("com.android.settings","com.android.settings.Settings\$NotificationAccessSettingsActivity")intent.component = cnintent.putExtra(":settings:show_fragment", "NotificationAccessSettings")context.startActivity(intent)return true} catch (e1: java.lang.Exception) {e1.printStackTrace()}Toast.makeText(this, "对不起,您的手机暂不支持", Toast.LENGTH_SHORT).show()e.printStackTrace()false}
}

注:如若获取不到系统通知,可参考本文末尾的附录2:使用 NotificationListenerService 监听通知。

3. ⚛️ 系统通知显示:通知弹窗

3.1 统一处理通知

Notification%20%E5%88%86%E5%8F%91%E8%B7%AF%E7%94%B1">1)每条通知到来时由 handleNotification 分发路由
object AGGNotificationManager {private val TAG = AGGNotificationManager::class.java.simpleName/*** 处理通知,每条通知到来时先经过此处路由。*/fun handleNotification(context: Context, notificationManagerBean: NotificationManagerBean) {// ...}}
NotificationManagerBean%20%E5%8C%BA%E5%88%86%20AR%20%E7%9C%BC%E9%95%9C%E9%80%9A%E7%9F%A5%E4%BB%A5%E5%8F%8A%E4%B8%8E%20AR%20%E7%9C%BC%E9%95%9C%E8%BF%9E%E6%8E%A5%E7%9A%84%E6%89%8B%E6%9C%BA%E9%80%9A%E7%9F%A5">2)NotificationManagerBean 区分 AR 眼镜通知以及与 AR 眼镜连接的手机通知
data class NotificationManagerBean(@FromType var from: Int = FROM_NONE, // 通知来源:1:眼镜;2:手机var glassNotification: StatusBarNotification? = null, //眼镜通知var phoneNotification: MessageReqMsgNoti? = null, // 手机通知
) {@IntDef(FROM_NONE, FROM_GLASS, FROM_PHONE)@Retention(AnnotationRetention.SOURCE)annotation class FromTypecompanion object {const val FROM_NONE = -1const val FROM_GLASS = 1const val FROM_PHONE = 2}
}
3)飞行模式时不显示系统通知
object AGGNotificationManager {private val TAG = AGGNotificationManager::class.java.simpleName/*** 处理通知,每条通知到来时先经过此处路由。*/fun handleNotification(context: Context, notificationManagerBean: NotificationManagerBean) {if (isAirPlaneMode(context)) {Log.i(TAG, "handleNotification: isAirPlaneMode = true.")return}// ...}/*** 是否在飞行模式*/fun isAirPlaneMode(context: Context): Boolean = Settings.Global.getInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0) == 1    }

3.2 播放通知音效

SoundPoolTools.playNotifyCome(context.applicationContext)

        参考系统应用音效播放即可:AR 眼镜之-系统应用音效-实现方案-CSDN博客

3.3 showDialog 显示通知弹窗

NotificationLayoutDialogBinding%20%E5%8A%A0%E8%BD%BD%E9%80%9A%E7%9F%A5%E5%BC%B9%E7%AA%97%20View">1)NotificationLayoutDialogBinding 加载通知弹窗 View
showDialog(context: Context,packageName: String,smallIcon: Drawable?,title: String,content: CharSequence
){val binding = NotificationLayoutDialogBinding.inflate(LayoutInflater.from(context)).apply {itemInfoLeftIcon.setImageDrawable(smallIcon)itemInfoMsg.text = getAppName(context, packageName)itemTitle.text = titleitemContent.text = content}AGGSuspensionNotification.showNotification(context, binding.root)
}
2)getAppName 获取 app 名
fun getAppName(context: Context, packageName: String): String {return try {val pm = context.packageManagerval pi = pm.getPackageInfo(packageName, 0)pi?.applicationInfo?.loadLabel(pm)?.toString() ?: packageName} catch (e: Exception) {packageName}
}
Notification%20%E6%98%BE%E7%A4%BA%E9%80%9A%E7%9F%A5%E5%BC%B9%E7%AA%97%20View">3)showNotification 显示通知弹窗 View
object AGGSuspensionNotification {private val TAG = AGGSuspensionNotification::class.java.simpleNameprivate var mWindowManager: WindowManager? = nullprivate var mLayoutParams: WindowManager.LayoutParams? = nullprivate var mCustomView: View? = nullfun showNotification(context: Context, customView: View) {mCustomView = customViewinitLayoutParams(context)if (!customView.isAttachedToWindow) {kotlin.runCatching {Log.i(TAG, "showNotification: addView")mWindowManager?.addView(customView, mLayoutParams)}}}fun removeNotification() {Log.i(TAG, "removeNotification: ")if (mCustomView?.isAttachedToWindow == true) {kotlin.runCatching {Log.i(TAG, "removeNotification: removeViewImmediate")mWindowManager?.removeViewImmediate(mCustomView)}mCustomView = null}}private fun initLayoutParams(context: Context) {Log.i(TAG, "initLayoutParams: ")mWindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManagermLayoutParams = WindowManager.LayoutParams().apply {type = WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAYflags =(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)format = PixelFormat.TRANSLUCENTwidth = (840 * context.resources.displayMetrics.density + 0.5f).toInt()height = (840 * context.resources.displayMetrics.density + 0.5f).toInt()gravity = Gravity.CENTER_HORIZONTAL or Gravity.TOPx = -100y = -100title = TAG + "_MASK"// dofIndex = 0// setTranslationZ(TRANSLATION_Z_150CM)}}}

注:对于通知弹窗的消失,以及通知中心显示与交互,由于篇幅问题,将放在下一篇章。AR 眼镜之-系统通知定制(通知中心)-实现方案-CSDN博客

4. ✅ 小结

        对于系统通知定制(通知弹窗),本文只是一个基础实现方案,更多业务细节请参考产品逻辑去实现。

        另外,由于本人能力有限,如有错误,敬请批评指正,谢谢。


SystemUI%20%E6%B5%81%E7%A8%8B">附录1:SystemUI 流程

SystemUI流程_systemui启动流程-CSDN博客文章浏览阅读1k次。SystemUI 是系统应用,由 SystemServer 进程进行启动,入口 Application 为SystemUIApplication。常用UI组件有如下几个:状态栏 StatusBar通知栏 NotificationPanel导航栏 NavigationBar最近任务 Recent键盘锁 Keyguard以上从 SystemUI 大概类图,以及自身启动流程开始,到 StatusBar 创建流程,再到系统 Notification 实现流程,一步步去理解 SystemUI 的相关流程。_systemui启动流程https://blog.csdn.net/Agg_bin/article/details/130252705

NotificationListenerService%20%E7%9B%91%E5%90%AC%E9%80%9A%E7%9F%A5">附录2:使用 NotificationListenerService 监听通知

Android9-W517-使用NotificationListenerService监听通知_android notificationlistenerservice-CSDN博客文章浏览阅读1.2k次,点赞18次,收藏15次。方案一通过Action跳转《系统设置》应用,手动打开通知监听权限:android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS——结果如图:显示在此设备上不能获得此特性——暂不可行;方案二在源码frameworks/base/core/res/res/values/config.xml路径下,修改config_defaultListenerAccessPackages属性的值为应用包名com.***.launcher——_android notificationlistenerservicehttps://blog.csdn.net/Agg_bin/article/details/136483571


http://www.ppmy.cn/devtools/104860.html

相关文章

Python GraphQL 库之graphene使用详解

概要 随着 Web 技术的发展,GraphQL 已成为 REST 的一种强有力替代方案,为客户端提供了更灵活的数据查询方式。Graphene 是一个用于构建 GraphQL API 的 Python 库,它使得开发者可以轻松地将复杂的数据模型暴露为 GraphQL API。通过 Graphene,开发者可以利用 Python 的面向…

Python爬虫案例五:将获取到的文本生成词云图

基础知识&#xff1a; # 词云图 wordcloud # 1、导包 jieba wordcloud import jieba from wordcloud import WordCloud data 全年经济社会发展主要目标任务圆满完成 data_list list(jieba.cut(data)) # print(data_list) # generator数据类型# 2、构造词云图样式 》虚拟的…

kafka3.7.1 单节点 KRaft部署测试发送和接收消息

一、环境准备 kafka3.7.1 包下载地址&#xff1a; https://mirrors.nju.edu.cn/apache/kafka/3.7.1/kafka_2.13-3.7.1.tgz openjdk11.0.2 下载地址&#xff1a; https://mirrors.nju.edu.cn/openjdk/11.0.2/openjdk-11.0.2_linux-x64_bin.tar.gz 二、openjdk 安装 【如已安装…

基于QT与STM32的电力参数采集系统(华为云IOT)(211)

文章目录 一、前言1.1 项目介绍【1】开发背景【2】项目实现的功能【3】项目硬件模块组成1.2 设计思路【1】整体设计思路【2】整体构架【3】上位机开发思路【4】供电方式1.3 项目开发背景【1】选题的意义【2】可行性分析【3】参考文献【4】摘要1.4 开发工具的选择【1】设备端开发…

使用智能码二维码zhinengma.cn实现政务公开

政务公开是提升政府透明度、增强公众信任和参与度的重要手段。随着信息技术的发展&#xff0c;智能码二维码作为一种便捷、高效的信息传递工具&#xff0c;在政务公开领域发挥着越来越重要的作用。 一、智能码二维码简介 智能码二维码&#xff08;Smart QR Code&#xff09;是一…

使用 ASP.NET Core 与 Entity Framework Core 进行数据库操作

使用 ASP.NET Core 与 Entity Framework Core 进行数据库操作 Entity Framework Core&#xff08;EF Core&#xff09;是ASP.NET Core中的一个轻量级ORM框架&#xff0c;提供了以面向对象的方式与数据库进行交互的能力。本文将通过Visual Studio 2022详细介绍如何使用EF Core进…

Android使用addr2line分析Native Crash

NDK提供的工具将函数地址解析为具体的函数名和行数才能进一步分析问题。 常用的地址转换工具有addr2line、ndk-stack等&#xff0c;个人比较喜欢addr2line&#xff0c;所以接下来介绍下该工具的基本使用方式 日常使用过程中&#xff0c;只需要关注-C -f -e三个参数即可 // -…

Java:随机字符生成器

Java程序生成随机字符和数字组合代码 前言程序概述代码解析生成包含大小写A-Z和数字0-9&#xff0c;总长度为5&#xff0c;且不包含字母O的随机字符串生成包含大小写A-Z和数字0-9&#xff0c;总长度为4或5&#xff0c;且数字不能连续的随机字符串 总结 前言 在现代的软件工程实…