break;
case Settings.Secure.ZEN_DURATION_FOREVER:
mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
break;
default:
Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
mHost.getUserId(), true).id;
mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
conditionId, TAG);
}
}
}
}
@SysUISingleton
public class ZenModeControllerImpl extends CurrentUserTracker
implements ZenModeController, Dumpable {
@Override
public void setZen(int zen, Uri conditionId, String reason) {
mNoMan.setZenMode(zen, conditionId, reason);
}
}
public class NotificationManagerService extends SystemService {
@VisibleForTesting
final IBinder mService = new INotificationManager.Stub() {
@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);
}
}
}
}
public class ZenModeHelper {
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);
}
}
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)) {
mConfig = config;
dispatchOnConfigChanged();
updateConsolidatedPolicy(reason);
}
if (policyChanged) {
dispatchOnPolicyChanged();
}
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);
}
}
private final class H extends Handler {
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);
}
}
}
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*/);
}
@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);//根据勿扰模式的状态设置Settings数据库值
updateConsolidatedPolicy(reason);//更新勿扰模式统一的策略
updateRingerModeAffectedStreams();//更新铃声模式受影响的铃声流
if (setRingerMode && (zen != zenBefore || (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
&& policyHashBefore != mConsolidatedPolicy.hashCode()))) {
applyZenToRingerMode();//更新铃声模式
}
applyRestrictions();//引用勿扰模式的各项限制
if (zen != zenBefore) {
mHandler.postDispatchOnZenModeChanged();//回调方法通知勿扰模式发生改变
}
}
}
总结:
通过快捷方式设置开启“勿扰模式”,首先进入到DndTile类,handleClick方法是对该Tile的点击事件做处理的,无论
是开启或关闭“勿扰模式”最终都会调用ZenModeControllerImpl#setZen进行设置,只是传入的参数一样
通过AIDL的方式调用NotificationManagerService#setZenMode方法设置勿扰模式
调用ZenModeHelper.setManualZenMode#setManualZenMode方法进行设置勿扰模式和更新规则
setConfigLocked(newConfig, "setNotificationPolicy")方法中先对配置文件进行空判断和有效判断。然后判断配置
文件相对于原始是否有变得,如果有,调用dispatchOnConfigChanged(),该方法内部是一个回调,会调用到
NotificationManagerService#onConfigChanged()
找到onConfigChanged(),调用handleSavePolicyFile()发送消息执行SavePolicyFileRunnable的run方法,该方法内
部调用writePolicyXml(),该方法通过ZenModeHelper对象再继续调用writeXml(...),就这样
系统将之前解析出来
的数据信息,也就是用户设定的各种勿扰的配置信息写在xml里面
各个apk对勿扰模式的处理,以来电铃声为例
public class Ringer {
private boolean shouldRingForContact(Uri contactUri) {
final NotificationManager manager =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
final Bundle peopleExtras = new Bundle();
if (contactUri != null) {
ArrayList personList = new ArrayList<>();
personList.add(new Person.Builder().setUri(contactUri.toString()).build());
peopleExtras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, personList);
}
return manager.matchesCallFilter(peopleExtras);
}
}
调用matchesCallFilter方法来决定是否响铃。
public class NotificationManager {
public boolean matchesCallFilter(Bundle extras) {
INotificationManager service = getService();
try {
return service.matchesCallFilter(extras);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
public class NotificationManagerService extends SystemService {
final IBinder mService = new INotificationManager.Stub() {
@Override
public boolean matchesCallFilter(Bundle extras) {
// Because matchesCallFilter may use contact data to filter calls, the callers of this
// method need to either have notification listener access or permission to read
// contacts.
boolean systemAccess = false;
try {
enforceSystemOrSystemUI("INotificationManager.matchesCallFilter");
systemAccess = true;
} catch (SecurityException e) {
}
boolean listenerAccess = false;
try {
String[] pkgNames = mPackageManager.getPackagesForUid(Binder.getCallingUid());
for (int i = 0; i < pkgNames.length; i++) {
// in most cases there should only be one package here
listenerAccess |= mListeners.hasAllowedListener(pkgNames[i],
Binder.getCallingUserHandle().getIdentifier());
}
} catch (RemoteException e) {
} finally {
if (!systemAccess && !listenerAccess) {
getContext().enforceCallingPermission(Manifest.permission.READ_CONTACTS,
"matchesCallFilter requires listener permission, contacts read access,"
+ " or system level access");
}
}
return mZenModeHelper.matchesCallFilter(
Binder.getCallingUserHandle(),
extras,
mRankingHelper.findExtractor(ValidateNotificationPeople.class),
MATCHES_CALL_FILTER_CONTACTS_TIMEOUT_MS,
MATCHES_CALL_FILTER_TIMEOUT_AFFINITY);
}
}
}
public class ZenModeHelper {
public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
synchronized (mConfig) {
return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConsolidatedPolicy,
userHandle, extras, validator, contactsTimeoutMs, timeoutAffinity);
}
}
}
public class ZenModeFiltering {
public static boolean matchesCallFilter(Context context, int zen, NotificationManager.Policy
consolidatedPolicy, UserHandle userHandle, Bundle extras,
ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
ZenLog.traceMatchesCallFilter(false, "no interruptions");
return false; // nothing gets through
} // 勿扰模式判断
if (zen == Global.ZEN_MODE_ALARMS) {
ZenLog.traceMatchesCallFilter(false, "alarms only");
return false; // not an alarm
}
if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
if (consolidatedPolicy.allowRepeatCallers()
&& REPEAT_CALLERS.isRepeat(context, extras, null)) {
ZenLog.traceMatchesCallFilter(true, "repeat caller");
return true;
} // 短时间内第二次来电放行
if (!consolidatedPolicy.allowCalls()) {
ZenLog.traceMatchesCallFilter(false, "calls not allowed");
return false; // no other calls get through
} // 是否禁止全部来电
if (validator != null) {
final float contactAffinity = validator.getContactAffinity(userHandle, extras,
contactsTimeoutMs, timeoutAffinity);
boolean match =
audienceMatches(consolidatedPolicy.allowCallsFrom(), contactAffinity);
ZenLog.traceMatchesCallFilter(match, "contact affinity " + contactAffinity);
return match;
} // 根据联系人判断
}
ZenLog.traceMatchesCallFilter(true, "no restrictions");
return true;
}
private static boolean audienceMatches(int source, float contactAffinity) {
switch (source) {
case ZenModeConfig.SOURCE_ANYONE:
return true; // 所有人
case ZenModeConfig.SOURCE_CONTACT:
return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT; // 联系人
case ZenModeConfig.SOURCE_STAR:
return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT; // 星标联系人
default:
Slog.w(TAG, "Encountered unknown source: " + source);
return true;
}
}
}
就是依据配置走不同的分支。接下来继续看核心的getContactAffinity方法、线程工作方法work如
下:
public class ValidateNotificationPeople implements NotificationSignalExtractor {
public float getContactAffinity(UserHandle userHandle, Bundle extras, int timeoutMs,
float timeoutAffinity) {
if (DEBUG) Slog.d(TAG, "checking affinity for " + userHandle);
if (extras == null) return NONE;
final String key = Long.toString(System.nanoTime());
final float[] affinityOut = new float[1];
Context context = getContextAsUser(userHandle);
if (context == null) {
return NONE;
}
final PeopleRankingReconsideration prr =
validatePeople(context, key, extras, null, affinityOut);
float affinity = affinityOut[0];
if (prr != null) {
// Perform the heavy work on a background thread so we can abort when we hit the
// timeout.
final Semaphore s = new Semaphore(0);
AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
prr.work(); // 开线程查数据库
s.release();
}
});
try {
if (!s.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
Slog.w(TAG, "Timeout while waiting for affinity: " + key + ". "
+ "Returning timeoutAffinity=" + timeoutAffinity);
return timeoutAffinity; // 超时后直接返回
}
} catch (InterruptedException e) {
Slog.w(TAG, "InterruptedException while waiting for affinity: " + key + ". "
+ "Returning affinity=" + affinity, e);
return affinity; // 线程被中断后返回缓存结果
}
affinity = Math.max(prr.getContactAffinity(), affinity); //正常情况下返回的结果
}
return affinity;
}
private class PeopleRankingReconsideration extends RankingReconsideration {
@Override
public void work() {
if (VERBOSE) Slog.i(TAG, "Executing: validation for: " + mKey);
long timeStartMs = System.currentTimeMillis();
for (final String handle: mPendingLookups) {
final String cacheKey = getCacheKey(mContext.getUserId(), handle);
LookupResult lookupResult = null;
boolean cacheHit = false;
synchronized (mPeopleCache) {
lookupResult = mPeopleCache.get(cacheKey);
if (lookupResult != null && !lookupResult.isExpired()) {
// The name wasn't already added to the cache, no need to retry
cacheHit = true;
}
}
if (!cacheHit) {
final Uri uri = Uri.parse(handle);
if ("tel".equals(uri.getScheme())) { // 处理电话号码类型的uri
if (DEBUG) Slog.d(TAG, "checking telephone URI: " + handle);
lookupResult = resolvePhoneContact(mContext, uri.getSchemeSpecificPart());
} else if ("mailto".equals(uri.getScheme())) { // 处理电子邮件类型的uri
if (DEBUG) Slog.d(TAG, "checking mailto URI: " + handle);
lookupResult = resolveEmailContact(mContext, uri.getSchemeSpecificPart());
} else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) { // 处理联系人lookup_uri
if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
// only look up phone number if this is a contact lookup uri and thus isn't
// already directly a phone number.
lookupResult = searchContactsAndLookupNumbers(mContext, uri);
} else { // 非法的uri,生成默认的结果
lookupResult = new LookupResult(); // invalid person for the cache
if (!"name".equals(uri.getScheme())) {
Slog.w(TAG, "unsupported URI " + handle);
}
}
}
if (lookupResult != null) {
if (!cacheHit) {
synchronized (mPeopleCache) {
mPeopleCache.put(cacheKey, lookupResult); // 查询结果存储到缓存中
}
}
if (DEBUG) {
Slog.d(TAG, "lookup contactAffinity is " + lookupResult.getAffinity());
}
mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity()); // 保存结果值
// merge any phone numbers found in this lookup result
if (lookupResult.getPhoneNumbers() != null) {
if (mPhoneNumbers == null) {
mPhoneNumbers = new ArraySet<>();
}
mPhoneNumbers.addAll(lookupResult.getPhoneNumbers());
}
} else {
if (DEBUG) Slog.d(TAG, "lookupResult is null");
}
}
if (DEBUG) {
Slog.d(TAG, "Validation finished in " + (System.currentTimeMillis() - timeStartMs) +
"ms");
}
if (mRecord != null) {
mUsageStats.registerPeopleAffinity(mRecord, mContactAffinity > NONE,
mContactAffinity == STARRED_CONTACT, false /* cached */);
}
}
}
}
public class ValidateNotificationPeople implements NotificationSignalExtractor {
private LookupResult resolvePhoneContact(Context context, final String number) {
Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
Uri.encode(number));
return searchContacts(context, phoneUri);
}
LookupResult searchContacts(Context context, Uri lookupUri) {
LookupResult lookupResult = new LookupResult();
final Uri corpLookupUri =
ContactsContract.Contacts.createCorpLookupUriFromEnterpriseLookupUri(lookupUri);
if (corpLookupUri == null) {
addContacts(lookupResult, context, lookupUri);
} else {
addWorkContacts(lookupResult, context, corpLookupUri);
}
return lookupResult;
}
LookupResult searchContacts(Context context, Uri lookupUri) {
LookupResult lookupResult = new LookupResult();
final Uri corpLookupUri =
ContactsContract.Contacts.createCorpLookupUriFromEnterpriseLookupUri(lookupUri);
if (corpLookupUri == null) {
addContacts(lookupResult, context, lookupUri);
} else {
addWorkContacts(lookupResult, context, corpLookupUri);
}
return lookupResult;
}
private void addContacts(LookupResult lookupResult, Context context, Uri uri) {
try (Cursor c = context.getContentResolver().query(
uri, LOOKUP_PROJECTION, null, null, null)) {
if (c == null) {
Slog.w(TAG, "Null cursor from contacts query.");
return;
}
while (c.moveToNext()) {
lookupResult.mergeContact(c);
}
} catch (Throwable t) {
Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
}
}
}
上述几个方法就是查询联系人数据库,其中生成结果的方法mergeContact如下:
private static class LookupResult {
private float mAffinity = NONE;
public void mergeContact(Cursor cursor) {
mAffinity = Math.max(mAffinity, VALID_CONTACT);
// Contact ID
int id;
final int idIdx = cursor.getColumnIndex(Contacts._ID);
if (idIdx >= 0) {
id = cursor.getInt(idIdx);
if (DEBUG) Slog.d(TAG, "contact _ID is: " + id);
} else {
id = -1;
Slog.i(TAG, "invalid cursor: no _ID");
}
// Lookup key for potentially looking up contact phone number later
final int lookupKeyIdx = cursor.getColumnIndex(Contacts.LOOKUP_KEY);
if (lookupKeyIdx >= 0) {
mPhoneLookupKey = cursor.getString(lookupKeyIdx);
if (DEBUG) Slog.d(TAG, "contact LOOKUP_KEY is: " + mPhoneLookupKey);
} else {
if (DEBUG) Slog.d(TAG, "invalid cursor: no LOOKUP_KEY");
}
// Starred
final int starIdx = cursor.getColumnIndex(Contacts.STARRED);
if (starIdx >= 0) {
boolean isStarred = cursor.getInt(starIdx) != 0;
if (isStarred) {
mAffinity = Math.max(mAffinity, STARRED_CONTACT);
}
if (DEBUG) Slog.d(TAG, "contact STARRED is: " + isStarred);
} else {
if (DEBUG) Slog.d(TAG, "invalid cursor: no STARRED");
}
// whether a phone number is present
final int hasPhoneIdx = cursor.getColumnIndex(Contacts.HAS_PHONE_NUMBER);
if (hasPhoneIdx >= 0) {
mHasPhone = cursor.getInt(hasPhoneIdx) != 0;
if (DEBUG) Slog.d(TAG, "contact HAS_PHONE_NUMBER is: " + mHasPhone);
} else {
if (DEBUG) Slog.d(TAG, "invalid cursor: no HAS_PHONE_NUMBER");
}
}
}
实际上就是给mAffinity赋值,标记为普通联系人或者星标联系人。