勿擾模式代碼結構簡析

勿擾模式是Android 7.0開始加入的功能。它的核心思想是屏蔽了通知的鈴聲、振動和展示。

代碼分散在幾部分。

1.設置代碼在Settings中,ZenMode開頭的一系列文件

/packages/apps/Settings/src/com/android/settings/notification/ZenModeSettings.java
/packages/apps/Settings/src/com/android/settings/notification/ZenModeSettingsBase.java
/packages/apps/Settings/src/com/android/settings/notification/ZenModeVoiceActivity.java
/packages/apps/Settings/src/com/android/settings/notification/ZenModePrioritySettings.java
/packages/apps/Settings/src/com/android/settings/notification/ZenModeRuleSettingsBase.java
/packages/apps/Settings/src/com/android/settings/notification/ZenModeEventRuleSettings.java
/packages/apps/Settings/src/com/android/settings/notification/ZenModeAutomationSettings.java
/packages/apps/Settings/src/com/android/settings/notification/ZenModeScheduleRuleSettings.java
/packages/apps/Settings/src/com/android/settings/notification/ZenModeScheduleDaysSelection.java
/packages/apps/Settings/src/com/android/settings/notification/ZenModeVisualInterruptionSettings.java

2.framework中關於勿擾的文件

2.1 勿擾模式設置,可以在進程間傳遞

/frameworks/base/core/java/android/service/notification/ZenModeConfig.aidl
/frameworks/base/core/java/android/service/notification/ZenModeConfig.java

2.2 勿擾模式使用相關文件

/frameworks/base/services/core/java/com/android/server/notification/ZenModeHelper.java
/frameworks/base/services/core/java/com/android/server/notification/ZenModeFiltering.java
/frameworks/base/services/core/java/com/android/server/notification/ZenModeConditions.java

2.3 systemUI中,有勿擾模式UI和功能兩個部分構成。因爲最終的目的是處理通知,所以實現是在systemUI中。

/frameworks/base/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
/frameworks/base/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java
/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java

3.各個apk對勿擾模式的處理,以來電鈴聲爲例

/packages/services/Telecomm/src/com/android/server/telecom/Ringer.java

    private boolean shouldRingForContact(Uri contactUri) {
        final NotificationManager manager =
                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        final Bundle extras = new Bundle();
        if (contactUri != null) {
            extras.putStringArray(Notification.EXTRA_PEOPLE, new String[] {contactUri.toString()});
        }
        return manager.matchesCallFilter(extras);
    }

使用matchesCallFilter來決定是否響鈴。


4.matchesCallFilter流程分析

frameworks/base/core/java/android/app/NotificationManager.java

    public boolean matchesCallFilter(Bundle extras) {
        INotificationManager service = getService();
        try {
            return service.matchesCallFilter(extras);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java

        public boolean matchesCallFilter(Bundle extras) {
            enforceSystemOrSystemUI("INotificationManager.matchesCallFilter");
            return mZenModeHelper.matchesCallFilter(
                    Binder.getCallingUserHandle(),
                    extras,
                    mRankingHelper.findExtractor(ValidateNotificationPeople.class),
                    MATCHES_CALL_FILTER_CONTACTS_TIMEOUT_MS,
                    MATCHES_CALL_FILTER_TIMEOUT_AFFINITY);
        }

android/frameworks/base/services/core/java/com/android/server/notification/ZenModeHelper.java

    public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
            ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
        synchronized (mConfig) {
            return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConfig, userHandle,
                    extras, validator, contactsTimeoutMs, timeoutAffinity);
        }
    }

android/frameworks/base/services/core/java/com/android/server/notification/ZenModeFiltering.java

    public static boolean matchesCallFilter(Context context, int zen, ZenModeConfig config,
            UserHandle userHandle, Bundle extras, ValidateNotificationPeople validator,
            int contactsTimeoutMs, float timeoutAffinity) {
        if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through //勿擾模式判斷
        if (zen == Global.ZEN_MODE_ALARMS) return false; // not an alarm
        if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
            if (config.allowRepeatCallers && REPEAT_CALLERS.isRepeat(context, extras)) {  //短時間內第二次來電放行
                return true;
            }
            if (!config.allowCalls) return false; // no other calls get through //是否禁止全部來電
            if (validator != null) {
                final float contactAffinity = validator.getContactAffinity(userHandle, extras,
                        contactsTimeoutMs, timeoutAffinity);
                return audienceMatches(config.allowCallsFrom, contactAffinity); //依據聯繫人判斷
            }
        }
        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方法
frameworks/base/services/core/java/com/android/server/notification/ValidateNotificationPeople.java

    public float getContactAffinity(UserHandle userHandle, Bundle extras, int timeoutMs,
            float timeoutAffinity) {
        ...
        final PeopleRankingReconsideration prr = validatePeople(context, key, extras, 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;
    }
線程工作方法work如下:

        public void work() {
            long start = SystemClock.elapsedRealtime();
            if (VERBOSE) Slog.i(TAG, "Executing: validation for: " + mKey);
            long timeStartMs = System.currentTimeMillis();
            for (final String handle: mPendingLookups) {
                LookupResult lookupResult = null;
                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);
                    lookupResult = searchContacts(mContext, uri);
                } else { //非法的uri,生成默認的結果
                    lookupResult = new LookupResult();  // invalid person for the cache
                    Slog.w(TAG, "unsupported URI " + handle);
                }
                if (lookupResult != null) {
                    synchronized (mPeopleCache) {
                        final String cacheKey = getCacheKey(mContext.getUserId(), handle);
                        mPeopleCache.put(cacheKey, lookupResult); //查詢存儲到緩存
                    }
                    if (DEBUG) Slog.d(TAG, "lookup contactAffinity is " + lookupResult.getAffinity());
                    mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity());  //保存結果值
                } else {
                    if (DEBUG) Slog.d(TAG, "lookupResult is null");
                }
            }
            ...
        }
以電話號碼分支爲例:

    private LookupResult resolvePhoneContact(Context context, final String number) {
        Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
                Uri.encode(number));
        return searchContacts(context, phoneUri);
    }
    private LookupResult searchContacts(Context context, Uri lookupUri) {
        LookupResult lookupResult = new LookupResult();
        Cursor c = null;
        try {
            c = context.getContentResolver().query(lookupUri, LOOKUP_PROJECTION, null, null, null);
            if (c == null) {
                Slog.w(TAG, "Null cursor from contacts query.");
                return lookupResult;
            }
            while (c.moveToNext()) {
                lookupResult.mergeContact(c);
            }
        } catch (Throwable t) {
            Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t);
        } finally {
            if (c != null) {
                c.close();
            }
        }
        return lookupResult;
    }
上述兩個方法就是查詢聯繫人數據庫,其中生成結果的方法mergeContact如下:

        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");
            }

            // 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");
            }
        }
實際上就是給mAffinity賦值,標記爲普通聯繫人或者星標聯繫人。

如果要加入指定聯繫人在勿擾模式中優先,從流程分析看只要修改searchContacts方法就可以了。


發佈了101 篇原創文章 · 獲贊 25 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章