引言
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.java
frameworks/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
@Override
public 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
*/
@UnsupportedAppUsage
public 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
*/
@UnsupportedAppUsage
public void setZenMode(int mode, Uri conditionId, String reason) {
INotificationManager service = getService();
try {
service.setZenMode(mode, conditionId, reason);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
NotificationManagerServices的实现如下:
@Override
public 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 users
mConfigs.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 config
mConditions.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 broadcasts
final 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)));
}
@Override
public 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的实现如下:
@VisibleForTesting
protected 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去进行设置。
@VisibleForTesting
protected 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/