1. 程式人生 > >Android7.1勿擾功能簡析

Android7.1勿擾功能簡析

Android系統在5.1系統開始增加勿擾模式,漸漸的有著取代靜音模式的趨勢,最新的系統已經更新到7.1.1,我們來看一下最新的原生勿擾有哪些功能。 首先在螢幕下滑出來的快捷開關介面中,我們可以看到勿擾模式。在這裡如果我們點選開啟勿擾模式,會出現三種勿擾模式供選擇。 (1)完全靜音:這會阻止所有聲音和振動(包括鬧鐘、音樂、視訊和遊戲)打擾您。您仍然可以撥打電話。這裡面也同事可以選擇定時或者直到自己將其關閉。 (2)僅限鬧鐘:這裡面除了鬧鐘之外,別的都會阻止通知。 (3)僅限優先打擾:您不會受聲音和振動的打擾,但鬧鐘、提醒、活動和您指定的來電者除外。 勿擾模式在設定中的位置,在聲音設定下。進入勿擾的設定中,我們可以看到有如下三項設定: (1)僅允許優先打擾內容:設定在勿擾處於僅限優先打擾的時候,這種情況下,哪些聯絡人或者提醒等可以通知您。 (2)自動規則:可以自定義多個自動規則,這些規則指哪些活動或者時間內自動開啟勿擾模式。同時可以開啟鬧鐘響鈴時結束勿擾模式。 (3)遮蔽視覺打擾:處於勿擾模式時的兩種更優化方案的開關
  • 螢幕開啟時遮蔽:禁止在勿擾模式下被靜音的通知在螢幕上短暫顯示或彈出
  • 螢幕關閉時遮蔽:禁止在勿擾模式下被靜音的通知開啟螢幕
以上都是從手機上直接可以看出的原生系統的勿擾模式的功能。接下來從程式碼層面簡單分析一下:首先勿擾模式的功能入口在SoundSettings.java中,佈局檔案區域性如下:Settings\res\xml\sound_settings.xml
        <!-- Interruptions -->
        <com.android.settingslib.RestrictedPreference
                android:key="zen_mode"
                android:title="@string/zen_mode_settings_title"
                settings:useAdminDisabledSummary="true"
                settings:keywords="@string/keywords_sounds_and_notifications_interruptions"
                android:fragment="com.android.settings.notification.ZenModeSettings" />
點選勿擾選項進入ZenModeSettings中。ZenModeSettings中的佈局檔案為zen_mode_settings,如下: Settings\res\xml\zen_mode_settings.xml
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
    android:key="zen_mode_settings"
    android:title="@string/zen_mode_settings_title" >

    <!-- Priority only allows -->
    <PreferenceScreen
            android:key="priority_settings"
            android:title="@string/zen_mode_priority_settings_title"
            android:fragment="com.android.settings.notification.ZenModePrioritySettings" />

    <!-- Automated rules -->
    <PreferenceScreen
            android:key="automation_settings"
            android:title="@string/zen_mode_automation_settings_title"
            android:fragment="com.android.settings.notification.ZenModeAutomationSettings" />

    <!-- Visual interruptions -->
    <PreferenceScreen
            android:key="visual_interruptions_settings"
            android:title="@string/zen_mode_visual_interruptions_settings_title"
            android:fragment="com.android.settings.notification.ZenModeVisualInterruptionSettings" />
</PreferenceScreen>

上面的佈局檔案和我們從手機上看到的勿擾設定介面一樣。我們可以看出ZenModePrioritySettings 、ZenModeAutomationSettings 、ZenModeVisualInterruptionSettings分別對應著僅允許優先打擾內容、自動規則、遮蔽視覺打擾三種頁面。

ZenModeSettings、ZenModePrioritySettings、ZenModeAutomationSettings 、ZenModeVisualInterruptionSettings都繼承於ZenModeSettingsBase。我們來分別看一下這些類。

ZenModeSettings這個類,我們從程式碼中可以看出它只是三項設定介面的入口,以及對勿擾的更新做一些介面的顯示工作。如下程式碼:

Settings/src/com/android/settings/notification/ZenModeSettings.java

    @Override
    protected void onZenModeChanged() {
        updateControls();
    }

    @Override
    protected void onZenModeConfigChanged() {
        mPolicy = NotificationManager.from(mContext).getNotificationPolicy();
        updateControls();
    }

    private void updateControls() {
        updatePrioritySettingsSummary();
        updateVisualSettingsSummary();
    }

    private void updatePrioritySettingsSummary() {
        String s = getResources().getString(R.string.zen_mode_alarms);
        s = appendLowercase(s, isCategoryEnabled(mPolicy, Policy.PRIORITY_CATEGORY_REMINDERS),
                R.string.zen_mode_reminders);
        s = appendLowercase(s, isCategoryEnabled(mPolicy, Policy.PRIORITY_CATEGORY_EVENTS),
                R.string.zen_mode_events);
        if (isCategoryEnabled(mPolicy, Policy.PRIORITY_CATEGORY_MESSAGES)) {
            if (mPolicy.priorityMessageSenders == Policy.PRIORITY_SENDERS_ANY) {
                s = appendLowercase(s, true, R.string.zen_mode_all_messages);
            } else {
                s = appendLowercase(s, true, R.string.zen_mode_selected_messages);
            }
        }
        if (isCategoryEnabled(mPolicy, Policy.PRIORITY_CATEGORY_CALLS)) {
            if (mPolicy.priorityCallSenders == Policy.PRIORITY_SENDERS_ANY) {
                s = appendLowercase(s, true, R.string.zen_mode_all_callers);
            } else {
                s = appendLowercase(s, true, R.string.zen_mode_selected_callers);
            }
        } else if (isCategoryEnabled(mPolicy, Policy.PRIORITY_CATEGORY_REPEAT_CALLERS)) {
            s = appendLowercase(s, true, R.string.zen_mode_repeat_callers);
        }
        mPrioritySettings.setSummary(s);
    }

    private void updateVisualSettingsSummary() {
        String s = getString(R.string.zen_mode_all_visual_interruptions);
        if (isEffectSuppressed(Policy.SUPPRESSED_EFFECT_SCREEN_ON)
                && isEffectSuppressed(Policy.SUPPRESSED_EFFECT_SCREEN_OFF)) {
            s = getString(R.string.zen_mode_no_visual_interruptions);
        } else if (isEffectSuppressed(Policy.SUPPRESSED_EFFECT_SCREEN_ON)) {
            s = getString(R.string.zen_mode_screen_on_visual_interruptions);
        } else if (isEffectSuppressed(Policy.SUPPRESSED_EFFECT_SCREEN_OFF)) {
            s = getString(R.string.zen_mode_screen_off_visual_interruptions);
        }
        mVisualSettings.setSummary(s);
    }
我們再看ZenModePrioritySettings類,首先它負責自定義優先打擾的內容,其中包括鬧鐘、提醒、活動、訊息、通話(僅限來自聯絡人)、重複來電者(如果同一個人在15分鐘內第二次來電,則允許顯示通知)。這面也僅是一些開關,將最後的更改用NotificationManager.from(mContext).setNotificationPolicy(mPolicy);進行儲存,其中也只有訊息和通話需要選擇,我們來看一下通話的設定以及儲存,其餘的類似:

Settings/src/com/android/settings/notification/ZenModePrioritySettings.java

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.zen_mode_priority_settings);
        final PreferenceScreen root = getPreferenceScreen();

        mPolicy = NotificationManager.from(mContext).getNotificationPolicy();

        ...

        mCalls = (DropDownPreference) root.findPreference(KEY_CALLS);
        addSources(mCalls);
        mCalls.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
            @Override
            public boolean onPreferenceChange(Preference preference, Object newValue) {
                if (mDisableListeners) return false;
                final int val = Integer.parseInt((String) newValue);
                final boolean allowCalls = val != SOURCE_NONE;
                final int allowCallsFrom = val == SOURCE_NONE ? mPolicy.priorityCallSenders : val;
                MetricsLogger.action(mContext, MetricsEvent.ACTION_ZEN_ALLOW_CALLS, val);
                if (DEBUG) Log.d(TAG, "onPrefChange allowCalls=" + allowCalls
                        + " allowCallsFrom=" + ZenModeConfig.sourceToString(allowCallsFrom));
                savePolicy(getNewPriorityCategories(allowCalls, Policy.PRIORITY_CATEGORY_CALLS),
                        allowCallsFrom, mPolicy.priorityMessageSenders,
                        mPolicy.suppressedVisualEffects);
                return true;
            }
        });

        ...

        updateControls();
    }
    
    private void updateControls() {
        mDisableListeners = true;
        if (mCalls != null) {
            mCalls.setValue(Integer.toString(
                    isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CALLS)
                            ? mPolicy.priorityCallSenders : SOURCE_NONE));
        }
        mMessages.setValue(Integer.toString(
                isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_MESSAGES)
                        ? mPolicy.priorityMessageSenders : SOURCE_NONE));
        mReminders.setChecked(isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_REMINDERS));
        mEvents.setChecked(isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_EVENTS));
        mRepeatCallers.setChecked(
                isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_REPEAT_CALLERS));
        mRepeatCallers.setVisible(!isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CALLS)
                || mPolicy.priorityCallSenders != Policy.PRIORITY_SENDERS_ANY);
        mDisableListeners = false;
    }
    
    private void savePolicy(int priorityCategories, int priorityCallSenders,
            int priorityMessageSenders, int suppressedVisualEffects) {
        mPolicy = new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders,
                suppressedVisualEffects);
        NotificationManager.from(mContext).setNotificationPolicy(mPolicy);
    }

ZenModeVisualInterruptionSettings和ZenModePrioritySettings類似,只是換成了螢幕開啟時遮蔽和螢幕關閉時遮蔽兩個開關,所以就不分析了。我們看ZenModeAutomationSettings這個稍微不同的類。

ZenModeSettingsBase中有一個mRules的集合,儲存著使用者自己定義的AutomaticZenRule,而mRules的值是NotificationManager中獲取的的。也就是這裡規則的維護儲存也還是在NotificationManager中,如下程式碼。

Settings/src/com/android/settings/notification/ZenModeSettingsBase.java

abstract public class ZenModeSettingsBase extends RestrictedSettingsFragment {
    ...
    
    protected Context mContext;
    protected Set<Map.Entry<String, AutomaticZenRule>> mRules;
    
    ...

    private void updateZenMode(boolean fireChanged) {
        final int zenMode = Settings.Global.getInt(getContentResolver(), Global.ZEN_MODE, mZenMode);
        if (zenMode == mZenMode) return;
        mZenMode = zenMode;
        if (DEBUG) Log.d(TAG, "updateZenMode mZenMode=" + mZenMode);
        if (fireChanged) {
            onZenModeChanged();
        }
    }

    protected String addZenRule(AutomaticZenRule rule) {
        try {
            String id = NotificationManager.from(mContext).addAutomaticZenRule(rule);
            final AutomaticZenRule savedRule =
                    NotificationManager.from(mContext).getAutomaticZenRule(id);
            maybeRefreshRules(savedRule != null, true);
            return id;
        } catch (Exception e) {
            return null;
        }
    }

    protected boolean setZenRule(String id, AutomaticZenRule rule) {
        final boolean success =
                NotificationManager.from(mContext).updateAutomaticZenRule(id, rule);
        maybeRefreshRules(success, true);
        return success;
    }

    protected boolean removeZenRule(String id) {
        final boolean success =
                NotificationManager.from(mContext).removeAutomaticZenRule(id);
        maybeRefreshRules(success, true);
        return success;
    }

    protected void maybeRefreshRules(boolean success, boolean fireChanged) {
        if (success) {
            mRules = getZenModeRules();
            if (DEBUG) Log.d(TAG, "Refreshed mRules=" + mRules);
            if (fireChanged) {
                onZenModeConfigChanged();
            }
        }
    }

    protected void setZenMode(int zenMode, Uri conditionId) {
        NotificationManager.from(mContext).setZenMode(zenMode, conditionId, TAG);
    }

    private Set<Map.Entry<String, AutomaticZenRule>> getZenModeRules() {
        Map<String, AutomaticZenRule> ruleMap
                = NotificationManager.from(mContext).getAutomaticZenRules();
        return ruleMap.entrySet();
    }

    ...
}

知道上面的東西,ZenModeAutomationSettings就簡單多了,就是mRules列表的展示,以及新增刪除等操作。只是這裡面新增可以選擇兩種模式,分別是活動規則和時間規則。分別對應的類是ZenModeEventRuleSettings和ZenModeScheduleRuleSettings,他們都繼承自ZenModeRuleSettingsBase。這裡ZenModeAutomationSettings雖然程式碼很多,但都是各種彈出框的操作,就不分析了。主要看看活動規則和時間規則這兩個裡面分別又有哪些操作吧。

時間規則的列表,分別有規則名稱、星期幾、開始時間、結束時間、勿擾、鬧鐘響鈴時間可覆蓋結束時間(在所設結束時間或下一次鬧鐘響鈴時(兩者選其先)停止)。由private ScheduleInfo mSchedule;中來儲存著時間規則的一些資料。

活動規則的頁面的列表,分別是名字、在以下日曆活動期間、回覆內容如下的活動、勿擾。private EventInfo mEvent;中來儲存著時間規則的一些資料。以上兩個規則裡面其餘都只是進行一些修改操作。這些操作最後的修改的資料都會通過呼叫ZenModeRuleSettingsBase裡面的updateRule()方法儲存到NotificationManager裡。程式碼如下:

Settings/src/com/android/settings/notification/ZenModeScheduleRuleSettings.java

        mStart = new TimePickerPreference(getPrefContext(), mgr);
        mStart.setKey(KEY_START_TIME);
        mStart.setTitle(R.string.zen_mode_start_time);
        mStart.setCallback(new TimePickerPreference.Callback() {
            @Override
            public boolean onSetTime(final int hour, final int minute) {
                if (mDisableListeners) return true;
                if (!ZenModeConfig.isValidHour(hour)) return false;
                if (!ZenModeConfig.isValidMinute(minute)) return false;
                if (hour == mSchedule.startHour && minute == mSchedule.startMinute) {
                    return true;
                }
                if (DEBUG) Log.d(TAG, "onPrefChange start h=" + hour + " m=" + minute);
                mSchedule.startHour = hour;
                mSchedule.startMinute = minute;
                updateRule(ZenModeConfig.toScheduleConditionId(mSchedule));
                return true;
            }
        });

Settings/src/com/android/settings/notification/ZenModeRuleSettingsBase.java

    protected void updateRule(Uri newConditionId) {
        mRule.setConditionId(newConditionId);
        setZenRule(mId, mRule);
    }
    
    protected boolean setZenRule(String id, AutomaticZenRule rule) {
        final boolean success =
                NotificationManager.from(mContext).updateAutomaticZenRule(id, rule);
        maybeRefreshRules(success, true);
        return success;
    }

自此所有在Setting裡面的勿擾模式的程式碼分析結束了,綜上可知,Settings模組中勿擾只是一個介面的展示,其資料都放在NotificationManager進行管理操作。