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

目录

📂 前言

AR 眼镜系统版本

系统通知定制

1. 🔱 技术方案

1.1 技术方案概述

1.2 实现方案

1)实现系统通知的监听

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

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

2.1 继承 NotificationListenerService

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

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

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

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

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

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

3.1 统一处理通知

1)每条通知到来时由 handleNotification 分发路由

2)NotificationManagerBean 区分 AR 眼镜通知以及与 AR 眼镜连接的手机通知

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

3.2 播放通知音效

3.3 showDialog 显示通知弹窗

1)NotificationLayoutDialogBinding 加载通知弹窗 View

2)getAppName 获取 app 名

3)showNotification 显示通知弹窗 View

4. ✅ 小结

附录1:SystemUI 流程

附录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. 💠 实现系统通知的监听

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 统一处理通知

1)每条通知到来时由 handleNotification 分发路由
object AGGNotificationManager {private val TAG = AGGNotificationManager::class.java.simpleName/*** 处理通知,每条通知到来时先经过此处路由。*/fun handleNotification(context: Context, notificationManagerBean: NotificationManagerBean) {// ...}}
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 显示通知弹窗

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}
}
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. ✅ 小结

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

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


附录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

附录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.rhkb.cn/news/414237.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【原型设计工具评测】Axure、Figma、Sketch三强争霸

在当今的数字化设计领域&#xff0c;选择合适的原型设计工具对于项目的成功至关重要。Axure、Figma 和 Sketch 是目前市场上最受欢迎的三款原型设计工具&#xff0c;它们各具特色&#xff0c;满足了不同用户的需求。本文将对这三款工具进行详细的对比评测&#xff0c;帮助设计师…

联蔚盘云亮相CDIE消费品行业峰会

8月28日&#xff0c;由华昂集团主办&#xff0c;专注于消费品行业的2024CDIE行业峰会在广州盛大开幕。联蔚数科携子品牌联蔚盘云亮相本次大会。本次峰会汇聚了众多企业高管&#xff0c;行业领域专家&#xff0c;围绕AI技术前沿、数智营销新策略、会员运营以及品牌增量路径等话题…

后台框架-统一异常管理

搭建后台框架全局异常管理是一个很重要的部分&#xff0c;好在SpringBoot提供了很好的处理方法 使用ControllerAdvice ControllerAdvice是Spring MVC中的一个全局异常处理注解&#xff0c;它允许在一个地方集中处理所有控制器抛出的异常。通过使用ControllerAdvice&#xff0…

Leetcode199二叉树的右视图(java实现)

今天我们分享的题目是199题&#xff0c;题目描述如下&#xff1a; 那么本道题的解题思路呢就是使用层序遍历&#xff0c;每次将每层中的最后一个元素加入到我们的集合中。 本道题目和之前的层序遍历二叉树的题目很像&#xff0c;但是需要注意的细节。那么我会在代码中指出。 代…

Flink CDC读取Mysql时,Decimal类型数据异常,变成了字符串(源码解析及解决方案)

1. 问题说明 使用Flink CDC 读取mysql数据时,当表字段为decimal时,读取的数据变成了字符串。 如下示例: 环境: Flink 1.18.0 Flink CDC 3.1.1 mysql 8 mysql的数据如下: 使用Flink CDC读取后的数据如下: 为了方便看,复制出来就是: {“id”:1,“price”:“AZA=”,…

ClickHousez中如何定时清理过期数据库?

一、脚本清理 要在ClickHouse中自动删除过期的数据库&#xff0c;你可以使用ClickHouse的SQL命令结合外部脚本&#xff08;如Shell脚本&#xff09;和计划任务&#xff08;如cron&#xff09;来实现。下面是一个示例&#xff0c;展示如何创建一个Shell脚本来检查数据库的创建时…

[引人深思]博彩用户真的赢了吗?——多维度揭示赌博危害

1.项目背景 博彩业&#xff0c;作为全球经济中一个庞大而复杂的行业&#xff0c;吸引了无数用户参与其中&#xff0c;然而&#xff0c;在巨大的利益诱惑背后&#xff0c;博彩业对个人和社会造成的潜在危害却不容忽视&#xff0c;尽管博彩活动常被包装为“娱乐”或“休闲活动”…

VCTP论文精读

机器视觉推理自从引入神经符号机制以来取得了巨大进步&#xff0c;这使得机器能够发展出多步骤的推理链。然而&#xff0c;正如早期认知科学家所预示的那样&#xff0c;这种逻辑和符号系统基本上不适合于现实世界、常识知识的表示和推理&#xff0c;因为它们仅依赖于封闭世界的…

详解树状数组(C/C++)

树状数组&#xff08;Binary Indexed Tree&#xff0c;简称BIT或Fenwick Tree&#xff09;是一种用于高效处理数据序列的算法数据结构。它能够支持两个主要操作&#xff1a;单点更新和区间求和&#xff0c;这两个操作的时间复杂度都能达到O(log n)&#xff0c;其中 n 是数据序列…

搭建基于QT的TCP服务器与客户端

1、实现功能 1、服务器和客户端能够建立连接 2、服务器可以给客户端发送信息 3、客户端可以给服务器发送信息 2、server 2-1、widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTcpServer> #include <QTcpSocket> QT_BEGIN_NA…

【LangChain】使用LangChain的提示词模板:技巧与总结

&#x1f601; 作者简介&#xff1a;前端开发爱好者&#xff0c;致力学习前端开发技术 ⭐️个人主页&#xff1a;夜宵饽饽的主页 ❔ 系列专栏&#xff1a;JavaScript小贴士 &#x1f450;学习格言&#xff1a;成功不是终点&#xff0c;失败也并非末日&#xff0c;最重要的是继续…

电感的分类

电感作为电子电路中的重要元件&#xff0c;具有多种分类方式&#xff0c;每种类型的电感都有其独特的优缺点。以下是对电感分类及其优缺点的详细分析&#xff1a; 一、按工作频率分类 高频电感&#xff1a;适用于高频电路&#xff0c;具有较高的自谐振频率和较低的损耗。 优点…

9-8 束搜索

贪心搜索 穷举搜索 束搜索 小结 序列搜索策略包括贪心搜索、穷举搜索和束搜索。 贪心搜索所选取序列的计算量最小&#xff0c;但精度相对较低。 穷举搜索所选取序列的精度最高&#xff0c;但计算量最大。 束搜索通过灵活选择束宽&#xff0c;在正确率和计算代价之间进行权衡…

Hive 案例分析(B站用户行为大数据分析)

Hive 案例分析&#xff08;B站用户行为大数据分析&#xff09; 一、案例需求二、设计数据表结构2.1 user 表结构2.2 video 表结构 三、创建数据表3.1 创建 video 数据库3.2 创建外表3.1.2 创建 external_user3.1.3 创建 external_video 3.2 创建内表3.2.1 创建 orc_user3.2.2 创…

【Qt笔记】QTreeView控件详解

目录 引言 一、QTreeView的基本用法 1. 创建QTreeView 2. 设置数据模型 3. 展开和折叠节点 4. 处理用户交互 二、自定义数据模型 1. 继承QAbstractItemModel 2. 实现必要的方法 3. 使用自定义模型 三、自定义视图和委托 1. 自定义视图 2. 自定义委托 四、过滤与…

C++ | Leetcode C++题解之第378题有序矩阵中第K小的元素

题目&#xff1a; 题解&#xff1a; class Solution { public:bool check(vector<vector<int>>& matrix, int mid, int k, int n) {int i n - 1;int j 0;int num 0;while (i > 0 && j < n) {if (matrix[i][j] < mid) {num i 1;j;} else…

YOLOv9改进策略【模型轻量化】| MoblieNetV3:基于搜索技术和新颖架构设计的轻量型网络模型

一、本文介绍 本文记录的是基于MobileNet V3的YOLOv9目标检测轻量化改进方法研究。MobileNet V3的模型结构是通过网络搜索得来的&#xff0c;其中的基础模块结合了MobileNet V1的深度可分离卷积、MobileNet V2的线性瓶颈和倒置残差结构以及MnasNet中基于挤压和激励的轻量级注意…

python-Flask搭建简易登录界面

使用Flask框架搭建一个简易的登录界面&#xff0c;登录成功获取token数据 1 搭建简易登录界面 代码如下 from flask import Flask, jsonify from flask import request import time, hashlibapp Flask(__name__)login_html <html> <head> <title>Log…

day7 测试知识积累

1.有一个班级表,里面有学号,姓名,学科,分数。找到语文学科分数最高的前10位的姓名(SQL) select 姓名 from 班级表 where 学科=语文 order by 分数 DESC limit 10; 2.有一张年级表,有班级,年级,学生姓名,找到这10名同学所在的班级(SQL) select class from 年级表 wher…

图片转为PDF怎么弄?看这里,三款软件助你一键转换!

嘿&#xff0c;朋友们&#xff01;现在信息这么多&#xff0c;图片在我们学习、工作、生活中帮了大忙。但有时候&#xff0c;我们想把图片整理好、分享给别人或者打印出来&#xff0c;PDF格式就特别合适。PDF文件不管在哪儿打开&#xff0c;内容都不会变样&#xff0c;还能加密…