Android Zenmode/DND(勿扰模式) 实现原理剖析
引言
Android手机越来越多的向着用户体验提升方面靠近,那么Zenmode就会变得越来越重要。
近年来,也有很多的新功能依赖于ZenMode去实现,也有很多专利在这个方面申请成功。
举两个简单的例子:
- 在国内的话,我们是否可以根据日历行程和AI分析,在勿扰模式下进行行程的提醒…
- 在国外的话,CMAS报警会很频繁,那么勿扰模式下的CellBroadCast就成为了不同运营商的需求。
本文将会专注于ZenMode的实现,并分析这些内容。
代码结构
1. 设置相关的界面及代码路径:
packages/apps/Settings/src/com/android/settings/notification/zen/
AbstractZenCustomRulePreferenceController.java ZenModeButtonPreferenceController.java ZenModeSliceBuilder.java
AbstractZenModeAutomaticRulePreferenceController.java ZenModeBypassingAppsPreferenceController.java ZenModeSoundVibrationPreferenceController.java
AbstractZenModePreferenceController.java ZenModeBypassingAppsSettings.java ZenModeSoundVibrationSettings.java
SettingsZenDurationDialog.java ZenModeCallsPreferenceController.java ZenModeSystemPreferenceController.java
ZenAccessSettings.java ZenModeCallsSettings.java ZenModeVisEffectPreferenceController.java
ZenAutomaticRuleHeaderPreferenceController.java ZenModeConversationsImagePreferenceController.java ZenModeVisEffectsAllPreferenceController.java
ZenAutomaticRuleSwitchPreferenceController.java ZenModeConversationsPreferenceController.java ZenModeVisEffectsCustomPreferenceController.java
ZenCustomRadioButtonPreference.java ZenModeConversationsSettings.java ZenModeVisEffectsNonePreferenceController.java
ZenCustomRuleBlockedEffectsSettings.java ZenModeDurationPreferenceController.java ZenModeVoiceActivity.java
ZenCustomRuleCallsSettings.java ZenModeEventRuleSettings.java ZenOnboardingActivity.java
ZenCustomRuleConfigSettings.java ZenModeEventsPreferenceController.java ZenRuleButtonsPreferenceController.java
ZenCustomRuleMessagesSettings.java ZenModeMediaPreferenceController.java ZenRuleCallsPreferenceController.java
ZenCustomRuleNotificationsSettings.java ZenModeMessagesPreferenceController.java ZenRuleCustomPolicyPreferenceController.java
ZenCustomRuleSettingsBase.java ZenModeMessagesSettings.java ZenRuleCustomSwitchPreferenceController.java
ZenCustomRuleSettings.java ZenModePeoplePreferenceController.java ZenRuleDefaultPolicyPreferenceController.java
ZenDeleteRuleDialog.java ZenModePeopleSettings.java ZenRuleInfo.java
ZenDurationDialogPreference.java ZenModePreferenceController.java ZenRuleMessagesPreferenceController.java
ZenFooterPreferenceController.java ZenModePriorityConversationsPreferenceController.java ZenRuleNameDialog.java
ZenModeAddAutomaticRulePreferenceController.java ZenModePrioritySendersPreferenceController.java ZenRuleNotifFooterPreferenceController.java
ZenModeAddBypassingAppsPreferenceController.java ZenModeRemindersPreferenceController.java ZenRulePreference.java
ZenModeAlarmsPreferenceController.java ZenModeRepeatCallersPreferenceController.java ZenRuleRepeatCallersPreferenceController.java
ZenModeAllBypassingAppsPreferenceController.java ZenModeRestrictNotificationsSettings.java ZenRuleSelectionDialog.java
ZenModeAutomaticRulesPreferenceController.java ZenModeRuleSettingsBase.java ZenRuleStarredContactsPreferenceController.java
ZenModeAutomationPreferenceController.java ZenModeScheduleDaysSelection.java ZenRuleVisEffectPreferenceController.java
ZenModeAutomationSettings.java ZenModeScheduleRuleSettings.java ZenRuleVisEffectsAllPreferenceController.java
ZenModeBackend.java ZenModeSendersImagePreferenceController.java ZenRuleVisEffectsCustomPreferenceController.java
ZenModeBehaviorFooterPreferenceController.java ZenModeSettingsBase.java ZenRuleVisEffectsNonePreferenceController.java
ZenModeBlockedEffectsPreferenceController.java ZenModeSettingsFooterPreferenceController.java ZenSuggestionActivity.java
ZenModeBlockedEffectsSettings.java ZenModeSettings.java
2. framework notification相关实现
a. 接口类的实现
frameworks/base/core/java/android/app
INotificationManager.aidl
ITransientNotification.aidl
ITransientNotificationCallback.aidl
Notification.aidl
NotificationChannel.aidl
NotificationChannelGroup.aidl
NotificationChannelGroup.java
NotificationChannel.java
NotificationHistory.aidl
NotificationHistory.java
Notification.java
NotificationManager.aidl
NotificationManager.java
b. 具体的实现
frameworks/base/core/java/android/service/notification
Adjustment.aidl ConversationChannelWrapper.aidl IStatusBarNotificationHolder.aidl NotificationStats.aidl ScheduleCalendar.java ZenModeConfig.aidl
Adjustment.java ConversationChannelWrapper.java NotificationAssistantService.java NotificationStats.java SnoozeCriterion.aidl ZenModeConfig.java
Condition.aidl IConditionListener.aidl NotificationListenerService.java NotifyingApp.aidl SnoozeCriterion.java ZenPolicy.java
Condition.java IConditionProvider.aidl NotificationRankingUpdate.aidl NotifyingApp.java StatusBarNotification.aidl
ConditionProviderService.java INotificationListener.aidl NotificationRankingUpdate.java OWNERS StatusBarNotification.java
c. ZenMode的配置实现
frameworks/base/core/java/android/service/notification/
frameworks/base/core/java/android/service/notification$ ls |grep Zen
ZenModeConfig.aidl
ZenModeConfig.java
ZenPolicy.javaframeworks/base/services/core/java/com/android/server/notification$ ls |grep Zen
ZenLog.java
ZenModeConditions.java
ZenModeExtractor.java
ZenModeFiltering.java
ZenModeHelper.java
实现逻辑
设置ZenMode调用逻辑分析
当点击启用勿扰模式时,会在settings里面首先改变状态。
packages/apps/Settings/src/com/android/settings/notification/zen/ZenModeButtonPreferenceController.java
@Overridepublic void updateState(Preference preference) {super.updateState(preference);if (null == mZenButtonOn) {mZenButtonOn = ((LayoutPreference) preference).findViewById(R.id.zen_mode_settings_turn_on_button);updateZenButtonOnClickListener(preference);}if (null == mZenButtonOff) {mZenButtonOff = ((LayoutPreference) preference).findViewById(R.id.zen_mode_settings_turn_off_button);mZenButtonOff.setOnClickListener(v -> {mRefocusButton = true;writeMetrics(preference, false);mBackend.setZenMode(Settings.Global.ZEN_MODE_OFF);});}updatePreference(preference);}
这里的实现相对来说比较简单,设置开和关的button,然后如果打开的情况下那么就会去通过setZenMode向下设置。
private void updateZenButtonOnClickListener() {int zenDuration = getZenDuration();switch (zenDuration) {case Settings.Secure.ZEN_DURATION_PROMPT:mZenButtonOn.setOnClickListener(v -> {mMetricsFeatureProvider.action(mContext,SettingsEnums.ACTION_ZEN_TOGGLE_DND_BUTTON, false);new SettingsEnableZenModeDialog().show(mFragment, TAG);});break;case Settings.Secure.ZEN_DURATION_FOREVER:mZenButtonOn.setOnClickListener(v -> {mMetricsFeatureProvider.action(mContext,SettingsEnums.ACTION_ZEN_TOGGLE_DND_BUTTON, false);mBackend.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);});break;default:mZenButtonOn.setOnClickListener(v -> {mMetricsFeatureProvider.action(mContext,SettingsEnums.ACTION_ZEN_TOGGLE_DND_BUTTON, false);mBackend.setZenModeForDuration(zenDuration);});}}
当点击了TurnOn的时候,将会进入到下面的逻辑:
case Settings.Secure.ZEN_DURATION_FOREVER:mZenButtonOn.setOnClickListener(v -> {mMetricsFeatureProvider.action(mContext,SettingsEnums.ACTION_ZEN_TOGGLE_DND_BUTTON, false);mBackend.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);});break;
设置setZenMode为ZEN_MODE_IMPORTANT_INTERRUPTIONS。
protected void setZenMode(int zenMode) {NotificationManager.from(mContext).setZenMode(zenMode, null, TAG);mZenMode = getZenMode();}
这里会去调用NotificationManager去设置zenmode.
/*** @hide*/@UnsupportedAppUsagepublic void setZenMode(int mode, Uri conditionId, String reason) {INotificationManager service = getService();try {service.setZenMode(mode, conditionId, reason);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}
INotificationManager是一个AIDL的调用, 将会去调用NotificationManagerService去进行设置。
/*** @hide*/@UnsupportedAppUsagepublic void setZenMode(int mode, Uri conditionId, String reason) {INotificationManager service = getService();try {service.setZenMode(mode, conditionId, reason);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}
NotificationManagerServices的实现如下:
@Overridepublic void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException {enforceSystemOrSystemUI("INotificationManager.setZenMode");final long identity = Binder.clearCallingIdentity();try {mZenModeHelper.setManualZenMode(mode, conditionId, null, reason);} finally {Binder.restoreCallingIdentity(identity);}}
setManualZenMode的实现已经在ZenModeHelper里面了,具体实现如下:
实现的路径为:frameworks/base/services/core/java/com/android/server/notification/ZenModeHelper.java
public void setManualZenMode(int zenMode, Uri conditionId, String caller, String reason) {setManualZenMode(zenMode, conditionId, reason, caller, true /*setRingerMode*/);Settings.Secure.putInt(mContext.getContentResolver(),Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION, 0);}private void setManualZenMode(int zenMode, Uri conditionId, String reason, String caller,boolean setRingerMode) {ZenModeConfig newConfig;synchronized (mConfig) {if (mConfig == null) return;if (!Global.isValidZenMode(zenMode)) return;if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode)+ " conditionId=" + conditionId + " reason=" + reason+ " setRingerMode=" + setRingerMode);newConfig = mConfig.copy();if (zenMode == Global.ZEN_MODE_OFF) {newConfig.manualRule = null;for (ZenRule automaticRule : newConfig.automaticRules.values()) {if (automaticRule.isAutomaticActive()) {automaticRule.snoozing = true;}}} else {final ZenRule newRule = new ZenRule();newRule.enabled = true;newRule.zenMode = zenMode;newRule.conditionId = conditionId;newRule.enabler = caller;newConfig.manualRule = newRule;}setConfigLocked(newConfig, reason, null, setRingerMode);}}
在进行了一系列参数保存和传递后,将会在最后setConfig。
public boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent,String reason) {return setConfigLocked(config, reason, triggeringComponent, true /*setRingerMode*/);}
这个只是个封装,真正的实现是一个private函数:
private boolean setConfigLocked(ZenModeConfig config, String reason,ComponentName triggeringComponent, boolean setRingerMode) {final long identity = Binder.clearCallingIdentity();try {if (config == null || !config.isValid()) {Log.w(TAG, "Invalid config in setConfigLocked; " + config);return false;}if (config.user != mUser) {// simply store away for background usersmConfigs.put(config.user, config);if (DEBUG) Log.d(TAG, "setConfigLocked: store config for user " + config.user);return true;}// handle CPS backed conditions - danger! may modify configmConditions.evaluateConfig(config, null, false /*processSubscriptions*/);mConfigs.put(config.user, config);if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable());ZenLog.traceConfig(reason, mConfig, config);// send some broadcastsfinal boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),getNotificationPolicy(config));if (!config.equals(mConfig)) {dispatchOnConfigChanged();updateConsolidatedPolicy(reason);}if (policyChanged) {dispatchOnPolicyChanged();}mConfig = config;mHandler.postApplyConfig(config, reason, triggeringComponent, setRingerMode);return true;} catch (SecurityException e) {Log.wtf(TAG, "Invalid rule in config", e);return false;} finally {Binder.restoreCallingIdentity(identity);}}
在这边如果当config相比于之前有改变的话,最后会发消息通知。
mHandler.postApplyConfig(config, reason, triggeringComponent, setRingerMode);
private void postApplyConfig(ZenModeConfig config, String reason,ComponentName triggeringComponent, boolean setRingerMode) {sendMessage(obtainMessage(MSG_APPLY_CONFIG,new ConfigMessageData(config, reason, triggeringComponent, setRingerMode)));}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_DISPATCH:dispatchOnZenModeChanged();break;case MSG_METRICS:mMetrics.emit();break;case MSG_APPLY_CONFIG:ConfigMessageData applyConfigData = (ConfigMessageData) msg.obj;applyConfig(applyConfigData.config, applyConfigData.reason,applyConfigData.triggeringComponent, applyConfigData.setRingerMode);}}}
在消息的最后,会发送MSG_APPLY_CONFIG来作为message的主体,在下方的handleMessage中,会applyConfig。
private void applyConfig(ZenModeConfig config, String reason,ComponentName triggeringComponent, boolean setRingerMode) {final String val = Integer.toString(config.hashCode());Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);evaluateZenMode(reason, setRingerMode);mConditions.evaluateConfig(config, triggeringComponent, true /*processSubscriptions*/);}
evaluateZenMode的实现如下:
@VisibleForTestingprotected void evaluateZenMode(String reason, boolean setRingerMode) {if (DEBUG) Log.d(TAG, "evaluateZenMode");if (mConfig == null) return;final int policyHashBefore = mConsolidatedPolicy == null ? 0: mConsolidatedPolicy.hashCode();final int zenBefore = mZenMode;final int zen = computeZenMode();ZenLog.traceSetZenMode(zen, reason);mZenMode = zen;setZenModeSetting(mZenMode);updateConsolidatedPolicy(reason);updateRingerModeAffectedStreams();if (setRingerMode && (zen != zenBefore || (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS&& policyHashBefore != mConsolidatedPolicy.hashCode()))) {applyZenToRingerMode();}applyRestrictions();if (zen != zenBefore) {mHandler.postDispatchOnZenModeChanged();}}
然后会去调用applyRestrictions去进行设置。
@VisibleForTestingprotected void applyRestrictions(boolean zenPriorityOnly, boolean mute, int usage, int code) {final long ident = Binder.clearCallingIdentity();try {mAppOps.setRestriction(code, usage,mute ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,zenPriorityOnly ? mPriorityOnlyDndExemptPackages : null);} finally {Binder.restoreCallingIdentity(ident);}}
如何适配运营商需求
在某些国家,会有运营商的要求为在设置了DND模式后,要求依然会有震动和响铃。
那么如何实现的呢?
首先在broadcast里面,会有一个KEY_OVERRIDE_DND的定义:
// Play alert sound in full volume regardless Do Not Disturb is on.public static final String KEY_OVERRIDE_DND = "override_dnd";
然后初始化:
// retrieve whether to play alert sound in full volume regardless Do Not Disturb is on.mOverrideDnd = intent.getBooleanExtra(ALERT_AUDIO_OVERRIDE_DND_EXTRA, false);
当在静音模式的时候,会进入如下判断:
case AudioManager.RINGER_MODE_SILENT:if (DBG) log("Ringer mode: silent");if (!mOverrideDnd) {mEnableVibrate = false;}// If the phone is in silent mode, we only enable the audio when override dnd// setting is turned on.mEnableAudio = mOverrideDnd;break;
当两个参数为true的时候,变进行响铃的操作。
if (mEnableAudio || mEnableVibrate) {playAlertTone(mAlertType, mVibrationPattern);} else {if (DBG) log("No audio/vibrate playing. Stop CellBroadcastAlertAudio service");stopSelf();return START_NOT_STICKY;}
那么哪些运营商会重写这个perference为true呢?
我们反编译这个apk,去查看不同mcc,mnc的values配置即可
比如: values-mcc310-mnc160/
Android Zenmode/DND(勿扰模式) 实现原理剖析相关推荐
- Android系统设置之勿扰模式
项目场景: 智能车载机 问题描述: 车载机使用4G流量,但客户接入的是带通话功能的sim卡,客户测试过程中遇到有人拨号,来电音量不是静音情况下,居然通了.客户提出禁止来电功能. 原因分析: 尽管系统进 ...
- android 勿扰模式代码,勿扰模式代码结构简析
勿扰模式是Android 7.0开始加入的功能.它的核心思想是屏蔽了通知的铃声.振动和展示. 代码分散在几部分. 1.设置代码在Settings中,ZenMode开头的一系列文件 /packages/ ...
- android 勿扰模式代码,android 勿扰模式代码结构简析
勿扰模式是Android 7.0开始加入的功能.它的核心思想是屏蔽了通知的铃声.振动和展示. 代码分散在几部分. 1.设置代码在Settings中,ZenMode开头的一系列文件 /packages/ ...
- android 勿扰模式代码结构简析
勿扰模式代码结构简析 标签: 勿扰模式 2017-08-08 11:05 60人阅读 评论(0) 收藏 举报 分类: android(59) 版权声明:本文为博主原创文章,未经博主允许不 ...
- android 勿扰模式代码,android Lollipop勿扰模式
android的L新版本中增加了"打扰"的新功能,相信很多同学搞不明白.找了一篇介绍勿扰模式很好的文章,可惜是英文的,现翻译如下,相信读完此问,你会理解android对勿扰模式的设 ...
- android 勿扰模式代码,Android N Zen Mode (勿扰模式)设置流程
Android N去除了情景模式,取而代之的是勿扰模式.勿扰模式的入口有两处,下拉栏和设置声音里面.下面我们就从设置声音入口,看看勿扰模式的设置流程. 首先,勿扰模式的首页有三种选项,分别是仅允许优先 ...
- android Lollipop勿扰模式
android的L新版本中增加了"打扰"的新功能,相信很多同学搞不明白.找了一篇介绍勿扰模式很好的文章,可惜是英文的,现翻译如下,相信读完此问,你会理解android对勿扰模式的设 ...
- 勿扰模式代码结构简析
勿扰模式是Android 7.0开始加入的功能.它的核心思想是屏蔽了通知的铃声.振动和展示. 代码分散在几部分. 1.设置代码在Settings中,ZenMode开头的一系列文件 /packages/ ...
- RK3568平台开发系列讲解(安卓篇)勿扰模式系统流程
文章目录 一.勿扰模式 一.勿扰模式 它的核心思想是屏蔽了通知的铃声.振动和展示. 代码分散在几部分. 1.设置代码在Settings中,ZenMode开头的一系列文件 /packages/apps/ ...
最新文章
- JAVA怎么创建对象组_java – 如何根据特定字段创建一组有序的对象?
- web安全编程——权限的分配和控制
- Redis详解(三)
- Android中scrollview与webview冲突事件
- 【Web API系列教程】3.3 — 实战:处理数据(建立数据库)
- Python常见设计模式
- R7-5 求矩阵各行元素之和 (15 分)
- 数据库-使用DataReader的简单实例(两种办法)
- 【转载】一步步构建大型网站架构
- tensorflow.python.framework.tensor_shape.is_fully_defined()
- 大事化小、小事化了的动态规划
- 快速地将SolidWorks模型导入Adams
- 知识图谱构建技术一览
- 职场篇(一):明哥的职场礼仪七堂课笔记
- 《python初级爬虫》(一)
- [004]Python数据类型二_python_全栈基础
- 阿里视频直播自定义推拉流地址生成
- Vue学习7-MinUI组件与项目托管到码云上
- Win10快捷键,管多
- JavaScript学习二
热门文章
- 深信服 行为感知系统 c.php 远程命令执行漏洞
- 关于Macbook Pro/Air 键盘输入乱码,重置NVRAM无效
- 浙大oj(basic level)1001
- 梦回JDBC —— (Statement对象)
- RecyclerView实现竖向无限循环滚动的列表
- Paperreading之五 Stacked Hourglass Networks(SHN)和源码阅读(PyTorch版本)
- 做了五年Android,我顿悟了...
- 前端炫酷登录页,拿来就能用
- 电视盒子显示服务器未连接,你家的电视盒子直播总是卡,解决方法全都在这里...
- OSChina 周六乱弹 ——泡妞指南