diff --git a/res/values/strings.xml b/res/values/strings.xml index 11a9bf4c347..833eb48f120 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -9801,6 +9801,8 @@ Conversations that can interrupt All conversations Priority conversations + + priority conversations None @@ -9874,6 +9876,8 @@ Contacts Starred contacts + + Some people or conversations From starred contacts and repeat callers diff --git a/res/xml/zen_mode_calls_settings.xml b/res/xml/zen_mode_calls_settings.xml index acd802334c3..a0b39a9c525 100644 --- a/res/xml/zen_mode_calls_settings.xml +++ b/res/xml/zen_mode_calls_settings.xml @@ -26,12 +26,6 @@ android:key="zen_mode_settings_category_calls" android:title="@string/zen_mode_calls_header" settings:allowDividerBelow="true"> - - - diff --git a/res/xml/zen_mode_custom_rule_messages_settings.xml b/res/xml/zen_mode_custom_rule_messages_settings.xml index 66091ec7e3e..aff903651b3 100644 --- a/res/xml/zen_mode_custom_rule_messages_settings.xml +++ b/res/xml/zen_mode_custom_rule_messages_settings.xml @@ -23,16 +23,7 @@ - - - - + diff --git a/res/xml/zen_mode_messages_settings.xml b/res/xml/zen_mode_messages_settings.xml index 797650f7057..6f537aa74d4 100644 --- a/res/xml/zen_mode_messages_settings.xml +++ b/res/xml/zen_mode_messages_settings.xml @@ -25,12 +25,6 @@ - - - diff --git a/res/xml/zen_mode_people_settings.xml b/res/xml/zen_mode_people_settings.xml index 1db438871be..ed21435018a 100644 --- a/res/xml/zen_mode_people_settings.xml +++ b/res/xml/zen_mode_people_settings.xml @@ -20,37 +20,21 @@ xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/zen_category_people" > - - - - - - - - - + + - diff --git a/src/com/android/settings/notification/zen/ZenCustomRuleMessagesSettings.java b/src/com/android/settings/notification/zen/ZenCustomRuleMessagesSettings.java index d4d37300afb..7b2bd4adcab 100644 --- a/src/com/android/settings/notification/zen/ZenCustomRuleMessagesSettings.java +++ b/src/com/android/settings/notification/zen/ZenCustomRuleMessagesSettings.java @@ -18,12 +18,12 @@ package com.android.settings.notification.zen; import android.app.settings.SettingsEnums; import android.content.Context; -import android.service.notification.ZenPolicy; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.settings.R; +import com.android.settings.notification.NotificationBackend; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.widget.FooterPreference; @@ -32,7 +32,6 @@ import java.util.List; public class ZenCustomRuleMessagesSettings extends ZenCustomRuleSettingsBase { private static final String MESSAGES_KEY = "zen_mode_messages"; - private static final String STARRED_CONTACTS_KEY = "zen_mode_starred_contacts_messages"; private static final String PREFERENCE_CATEGORY_KEY = "zen_mode_settings_category_messages"; @Override @@ -48,11 +47,9 @@ public class ZenCustomRuleMessagesSettings extends ZenCustomRuleSettingsBase { @Override protected List createPreferenceControllers(Context context) { mControllers = new ArrayList<>(); - mControllers.add(new ZenRuleMessagesPreferenceController(context, MESSAGES_KEY, - getSettingsLifecycle())); - mControllers.add(new ZenRuleStarredContactsPreferenceController(context, - getSettingsLifecycle(), ZenPolicy.PRIORITY_CATEGORY_MESSAGES, - STARRED_CONTACTS_KEY)); + mControllers.add(new ZenRulePrioritySendersPreferenceController(context, + PREFERENCE_CATEGORY_KEY, getSettingsLifecycle(), true, + new NotificationBackend())); return mControllers; } @@ -65,6 +62,8 @@ public class ZenCustomRuleMessagesSettings extends ZenCustomRuleSettingsBase { public void updatePreferences() { super.updatePreferences(); PreferenceScreen screen = getPreferenceScreen(); + // TODO(b/200600958): It seems that this string does not currently update to indicate when + // messages aren't in fact blocked by the rule. Preference footerPreference = screen.findPreference(FooterPreference.KEY_FOOTER); footerPreference.setTitle(mContext.getResources().getString( R.string.zen_mode_custom_messages_footer, mRule.getName())); diff --git a/src/com/android/settings/notification/zen/ZenModeBackend.java b/src/com/android/settings/notification/zen/ZenModeBackend.java index e3f5063bf23..85f9aeea5fe 100644 --- a/src/com/android/settings/notification/zen/ZenModeBackend.java +++ b/src/com/android/settings/notification/zen/ZenModeBackend.java @@ -287,6 +287,20 @@ public class ZenModeBackend { } } + protected static int getContactSettingFromZenPolicySetting(int setting) { + switch (setting) { + case ZenPolicy.PEOPLE_TYPE_ANYONE: + return NotificationManager.Policy.PRIORITY_SENDERS_ANY; + case ZenPolicy.PEOPLE_TYPE_CONTACTS: + return NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS; + case ZenPolicy.PEOPLE_TYPE_STARRED: + return NotificationManager.Policy.PRIORITY_SENDERS_STARRED; + case ZenPolicy.PEOPLE_TYPE_NONE: + default: + return SOURCE_NONE; + } + } + protected int getAlarmsTotalSilencePeopleSummary(int category) { if (category == NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) { return R.string.zen_mode_none_messages; diff --git a/src/com/android/settings/notification/zen/ZenModeCallsSettings.java b/src/com/android/settings/notification/zen/ZenModeCallsSettings.java index 82d5cf62765..93e97b1d2cd 100644 --- a/src/com/android/settings/notification/zen/ZenModeCallsSettings.java +++ b/src/com/android/settings/notification/zen/ZenModeCallsSettings.java @@ -21,6 +21,7 @@ import android.content.Context; import android.provider.SearchIndexableResource; import com.android.settings.R; +import com.android.settings.notification.NotificationBackend; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; @@ -44,9 +45,8 @@ public class ZenModeCallsSettings extends ZenModeSettingsBase { Lifecycle lifecycle) { List controllers = new ArrayList<>(); controllers.add(new ZenModePrioritySendersPreferenceController(context, - "zen_mode_settings_category_calls", lifecycle, false)); - controllers.add(new ZenModeSendersImagePreferenceController(context, - "zen_mode_calls_image", lifecycle, false)); + "zen_mode_settings_category_calls", lifecycle, false, + new NotificationBackend())); controllers.add(new ZenModeRepeatCallersPreferenceController(context, lifecycle, context.getResources().getInteger(com.android.internal.R.integer .config_zen_repeat_callers_threshold))); diff --git a/src/com/android/settings/notification/zen/ZenModeMessagesSettings.java b/src/com/android/settings/notification/zen/ZenModeMessagesSettings.java index f8e4548f53f..3405c435ba3 100644 --- a/src/com/android/settings/notification/zen/ZenModeMessagesSettings.java +++ b/src/com/android/settings/notification/zen/ZenModeMessagesSettings.java @@ -21,6 +21,7 @@ import android.content.Context; import android.provider.SearchIndexableResource; import com.android.settings.R; +import com.android.settings.notification.NotificationBackend; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; @@ -44,10 +45,9 @@ public class ZenModeMessagesSettings extends ZenModeSettingsBase { private static List buildPreferenceControllers(Context context, Lifecycle lifecycle) { List controllers = new ArrayList<>(); - controllers.add(new ZenModeSendersImagePreferenceController(context, - "zen_mode_messages_image", lifecycle, true)); controllers.add(new ZenModePrioritySendersPreferenceController(context, - "zen_mode_settings_category_messages", lifecycle, true)); + "zen_mode_settings_category_messages", lifecycle, true, + new NotificationBackend())); controllers.add(new ZenModeBehaviorFooterPreferenceController( context, lifecycle, R.string.zen_mode_messages_footer)); return controllers; diff --git a/src/com/android/settings/notification/zen/ZenModePeopleSettings.java b/src/com/android/settings/notification/zen/ZenModePeopleSettings.java index 962badbd05e..02d2647e2ef 100644 --- a/src/com/android/settings/notification/zen/ZenModePeopleSettings.java +++ b/src/com/android/settings/notification/zen/ZenModePeopleSettings.java @@ -56,10 +56,6 @@ public class ZenModePeopleSettings extends ZenModeSettingsBase implements Indexa Lifecycle lifecycle, Application app, Fragment host, FragmentManager fragmentManager, NotificationBackend notificationBackend) { List controllers = new ArrayList<>(); - controllers.add(new ZenModeConversationsImagePreferenceController(context, - "zen_mode_conversations_image", lifecycle, notificationBackend)); - controllers.add(new ZenModeConversationsPreferenceController(context, - "zen_mode_conversations", lifecycle)); controllers.add(new ZenModeCallsPreferenceController(context, lifecycle, "zen_mode_people_calls")); controllers.add(new ZenModeMessagesPreferenceController(context, lifecycle, diff --git a/src/com/android/settings/notification/zen/ZenModePrioritySendersPreferenceController.java b/src/com/android/settings/notification/zen/ZenModePrioritySendersPreferenceController.java index 8bad60b9968..0d6093c040d 100644 --- a/src/com/android/settings/notification/zen/ZenModePrioritySendersPreferenceController.java +++ b/src/com/android/settings/notification/zen/ZenModePrioritySendersPreferenceController.java @@ -19,83 +19,53 @@ package com.android.settings.notification.zen; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES; -import android.app.NotificationManager; +import static com.android.settings.notification.zen.ZenPrioritySendersHelper.UNKNOWN; + import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.provider.Contacts; -import android.view.View; +import android.os.AsyncTask; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; -import com.android.settings.R; +import com.android.settings.notification.NotificationBackend; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.widget.SelectorWithWidgetPreference; -import java.util.ArrayList; -import java.util.List; - /** - * Common preference controller functionality shared by - * ZenModePriorityMessagesPreferenceController and ZenModePriorityCallsPreferenceController. + * Common preference controller functionality for zen mode priority senders preferences for both + * messages and calls. * - * This includes the options to choose the priority senders that are allowed to bypass DND for - * calls or messages. This can be one of four values: starred contacts, all contacts, anyone, or - * no one. + * These controllers handle the settings regarding which priority senders that are allowed to + * bypass DND for calls or messages, which may be one the following values: starred contacts, all + * contacts, priority conversations (for messages only), anyone, or no one. + * + * Most of the functionality is handled by ZenPrioritySendersHelper, so that it can also be shared + * with settings controllers for custom rules. This class handles the parts of the behavior where + * settings must be written to the relevant backends, as that's where this class diverges from + * custom rules. */ public class ZenModePrioritySendersPreferenceController extends AbstractZenModePreferenceController { - @VisibleForTesting static final String KEY_ANY = "senders_anyone"; - @VisibleForTesting static final String KEY_CONTACTS = "senders_contacts"; - @VisibleForTesting static final String KEY_STARRED = "senders_starred_contacts"; - @VisibleForTesting static final String KEY_NONE = "senders_none"; - - private static final Intent ALL_CONTACTS_INTENT = - new Intent(Contacts.Intents.UI.LIST_DEFAULT) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - private static final Intent STARRED_CONTACTS_INTENT = - new Intent(Contacts.Intents.UI.LIST_STARRED_ACTION) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - private static final Intent FALLBACK_INTENT = new Intent(Intent.ACTION_MAIN) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - - private final PackageManager mPackageManager; private final boolean mIsMessages; // if this is false, then this preference is for calls private PreferenceCategory mPreferenceCategory; - private List mSelectorWithWidgetPreferences = new ArrayList<>(); + private ZenPrioritySendersHelper mHelper; public ZenModePrioritySendersPreferenceController(Context context, String key, - Lifecycle lifecycle, boolean isMessages) { + Lifecycle lifecycle, boolean isMessages, NotificationBackend notificationBackend) { super(context, key, lifecycle); mIsMessages = isMessages; - mPackageManager = mContext.getPackageManager(); - if (!FALLBACK_INTENT.hasCategory(Intent.CATEGORY_APP_CONTACTS)) { - FALLBACK_INTENT.addCategory(Intent.CATEGORY_APP_CONTACTS); - } + mHelper = new ZenPrioritySendersHelper( + context, isMessages, mBackend, notificationBackend, mSelectorClickListener); } @Override public void displayPreference(PreferenceScreen screen) { mPreferenceCategory = screen.findPreference(getPreferenceKey()); - if (mPreferenceCategory.findPreference(KEY_ANY) == null) { - makeRadioPreference(KEY_STARRED, - com.android.settings.R.string.zen_mode_from_starred); - makeRadioPreference(KEY_CONTACTS, - com.android.settings.R.string.zen_mode_from_contacts); - makeRadioPreference(KEY_ANY, - com.android.settings.R.string.zen_mode_from_anyone); - makeRadioPreference(KEY_NONE, - mIsMessages - ? com.android.settings.R.string.zen_mode_none_messages - : com.android.settings.R.string.zen_mode_none_calls); - updateSummaries(); - } - + mHelper.displayPreference(mPreferenceCategory); super.displayPreference(screen); } @@ -111,53 +81,37 @@ public class ZenModePrioritySendersPreferenceController @Override public void updateState(Preference preference) { - final int currSetting = getPrioritySenders(); - - for (SelectorWithWidgetPreference pref : mSelectorWithWidgetPreferences) { - pref.setChecked(keyToSetting(pref.getKey()) == currSetting); - } + final int currContactsSetting = getPrioritySenders(); + final int currConversationsSetting = getPriorityConversationSenders(); + mHelper.updateState(currContactsSetting, currConversationsSetting); } @Override public void onResume() { super.onResume(); - updateSummaries(); - } - - private void updateSummaries() { - for (SelectorWithWidgetPreference pref : mSelectorWithWidgetPreferences) { - pref.setSummary(getSummary(pref.getKey())); + if (mIsMessages) { + updateChannelCounts(); } + mHelper.updateSummaries(); } - private static int keyToSetting(String key) { - switch (key) { - case KEY_STARRED: - return NotificationManager.Policy.PRIORITY_SENDERS_STARRED; - case KEY_CONTACTS: - return NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS; - case KEY_ANY: - return NotificationManager.Policy.PRIORITY_SENDERS_ANY; - case KEY_NONE: - default: - return ZenModeBackend.SOURCE_NONE; - } - } - - private String getSummary(String key) { - switch (key) { - case KEY_STARRED: - return mBackend.getStarredContactsSummary(mContext); - case KEY_CONTACTS: - return mBackend.getContactsNumberSummary(mContext); - case KEY_ANY: - return mContext.getResources().getString(mIsMessages - ? R.string.zen_mode_all_messages_summary - : R.string.zen_mode_all_calls_summary); - case KEY_NONE: - default: + private void updateChannelCounts() { + // Load conversations + new AsyncTask() { + @Override + protected Void doInBackground(Void... unused) { + mHelper.updateChannelCounts(); return null; - } + } + + @Override + protected void onPostExecute(Void unused) { + if (mContext == null) { + return; + } + updateState(mPreferenceCategory); + } + }.execute(); } private int getPrioritySenders() { @@ -168,72 +122,34 @@ public class ZenModePrioritySendersPreferenceController } } - private SelectorWithWidgetPreference makeRadioPreference(String key, int titleId) { - final SelectorWithWidgetPreference pref = - new SelectorWithWidgetPreference(mPreferenceCategory.getContext()); - pref.setKey(key); - pref.setTitle(titleId); - pref.setOnClickListener(mRadioButtonClickListener); - - View.OnClickListener widgetClickListener = getWidgetClickListener(key); - if (widgetClickListener != null) { - pref.setExtraWidgetOnClickListener(widgetClickListener); + private int getPriorityConversationSenders() { + if (mIsMessages) { + return mBackend.getPriorityConversationSenders(); } - - mPreferenceCategory.addPreference(pref); - mSelectorWithWidgetPreferences.add(pref); - return pref; + return UNKNOWN; } - private SelectorWithWidgetPreference.OnClickListener mRadioButtonClickListener = + @VisibleForTesting + SelectorWithWidgetPreference.OnClickListener mSelectorClickListener = new SelectorWithWidgetPreference.OnClickListener() { @Override public void onRadioButtonClicked(SelectorWithWidgetPreference preference) { - int selectedSetting = keyToSetting(preference.getKey()); - if (selectedSetting != getPrioritySenders()) { + // The settingsToSaveOnClick function takes whether or not the preference is a + // checkbox into account to determine whether this selection is checked or unchecked. + final int[] settingsToSave = mHelper.settingsToSaveOnClick(preference, + getPrioritySenders(), getPriorityConversationSenders()); + final int prioritySendersSetting = settingsToSave[0]; + final int priorityConvosSetting = settingsToSave[1]; + + if (prioritySendersSetting != UNKNOWN) { mBackend.saveSenders( mIsMessages ? PRIORITY_CATEGORY_MESSAGES : PRIORITY_CATEGORY_CALLS, - selectedSetting); + prioritySendersSetting); + } + + if (mIsMessages && priorityConvosSetting != UNKNOWN) { + mBackend.saveConversationSenders(priorityConvosSetting); } } }; - - private View.OnClickListener getWidgetClickListener(String key) { - if (!KEY_CONTACTS.equals(key) && !KEY_STARRED.equals(key)) { - return null; - } - - if (KEY_STARRED.equals(key) && !isStarredIntentValid()) { - return null; - } - - if (KEY_CONTACTS.equals(key) && !isContactsIntentValid()) { - return null; - } - - return new View.OnClickListener() { - @Override - public void onClick(View v) { - if (KEY_STARRED.equals(key) - && STARRED_CONTACTS_INTENT.resolveActivity(mPackageManager) != null) { - mContext.startActivity(STARRED_CONTACTS_INTENT); - } else if (KEY_CONTACTS.equals(key) - && ALL_CONTACTS_INTENT.resolveActivity(mPackageManager) != null) { - mContext.startActivity(ALL_CONTACTS_INTENT); - } else { - mContext.startActivity(FALLBACK_INTENT); - } - } - }; - } - - private boolean isStarredIntentValid() { - return STARRED_CONTACTS_INTENT.resolveActivity(mPackageManager) != null - || FALLBACK_INTENT.resolveActivity(mPackageManager) != null; - } - - private boolean isContactsIntentValid() { - return ALL_CONTACTS_INTENT.resolveActivity(mPackageManager) != null - || FALLBACK_INTENT.resolveActivity(mPackageManager) != null; - } } diff --git a/src/com/android/settings/notification/zen/ZenModeSettings.java b/src/com/android/settings/notification/zen/ZenModeSettings.java index 2cc4f8b35e8..a707e534d74 100644 --- a/src/com/android/settings/notification/zen/ZenModeSettings.java +++ b/src/com/android/settings/notification/zen/ZenModeSettings.java @@ -18,6 +18,7 @@ package com.android.settings.notification.zen; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS; +import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_EVENTS; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES; @@ -116,6 +117,7 @@ public class ZenModeSettings extends ZenModeSettingsBase { PRIORITY_CATEGORY_MEDIA, PRIORITY_CATEGORY_SYSTEM, PRIORITY_CATEGORY_MESSAGES, + PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_CATEGORY_EVENTS, PRIORITY_CATEGORY_REMINDERS, PRIORITY_CATEGORY_CALLS, @@ -168,12 +170,19 @@ public class ZenModeSettings extends ZenModeSettingsBase { String getMessagesSettingSummary(Policy policy) { List enabledCategories = getEnabledCategories(policy, - category -> PRIORITY_CATEGORY_MESSAGES == category, false); + category -> PRIORITY_CATEGORY_MESSAGES == category + || PRIORITY_CATEGORY_CONVERSATIONS == category, true); int numCategories = enabledCategories.size(); if (numCategories == 0) { return mContext.getString(R.string.zen_mode_none_messages); - } else { + } else if (numCategories == 1) { return enabledCategories.get(0); + } else { + // While this string name seems like a slight misnomer: it's borrowing the analogous + // calls-summary functionality to combine two permissions. + return mContext.getString(R.string.zen_mode_calls_summary_two, + enabledCategories.get(0), + enabledCategories.get(1)); } } @@ -250,6 +259,15 @@ public class ZenModeSettings extends ZenModeSettingsBase { continue; } + // For conversations, only the "priority conversations" setting is relevant; any + // other setting is subsumed by the messages-specific messaging. + if (category == Policy.PRIORITY_CATEGORY_CONVERSATIONS + && isCategoryEnabled(policy, Policy.PRIORITY_CATEGORY_CONVERSATIONS) + && policy.priorityConversationSenders + != Policy.CONVERSATION_SENDERS_IMPORTANT) { + continue; + } + enabledCategories.add(getCategory(category, policy, isFirst)); } } @@ -282,11 +300,20 @@ public class ZenModeSettings extends ZenModeSettingsBase { } else if (category == Policy.PRIORITY_CATEGORY_MESSAGES) { if (policy.priorityMessageSenders == Policy.PRIORITY_SENDERS_ANY) { return mContext.getString(R.string.zen_mode_from_anyone); - } else if (policy.priorityMessageSenders == Policy.PRIORITY_SENDERS_CONTACTS){ + } else if (policy.priorityMessageSenders == Policy.PRIORITY_SENDERS_CONTACTS) { return mContext.getString(R.string.zen_mode_from_contacts); } else { return mContext.getString(R.string.zen_mode_from_starred); } + } else if (category == Policy.PRIORITY_CATEGORY_CONVERSATIONS + && policy.priorityConversationSenders + == Policy.CONVERSATION_SENDERS_IMPORTANT) { + if (isFirst) { + return mContext.getString(R.string.zen_mode_from_important_conversations); + } else { + return mContext.getString( + R.string.zen_mode_from_important_conversations_second); + } } else if (category == Policy.PRIORITY_CATEGORY_EVENTS) { if (isFirst) { return mContext.getString(R.string.zen_mode_events_list_first); diff --git a/src/com/android/settings/notification/zen/ZenPrioritySendersHelper.java b/src/com/android/settings/notification/zen/ZenPrioritySendersHelper.java new file mode 100644 index 00000000000..5d0b71b7643 --- /dev/null +++ b/src/com/android/settings/notification/zen/ZenPrioritySendersHelper.java @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification.zen; + +import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE; +import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT; +import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_NONE; +import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY; + +import android.app.NotificationManager; +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; +import android.icu.text.MessageFormat; +import android.provider.Contacts; +import android.service.notification.ConversationChannelWrapper; +import android.view.View; + +import androidx.preference.PreferenceCategory; + +import com.android.settings.R; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.notification.NotificationBackend; +import com.android.settings.notification.app.ConversationListSettings; +import com.android.settingslib.widget.SelectorWithWidgetPreference; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * Shared class implementing priority senders logic to be used both for zen mode and zen custom + * rules, governing which senders can break through DND. This helper class controls creating + * and displaying the relevant preferences for either messages or calls mode, and determining + * what the priority and conversation senders settings should be given a click. + * + * The outer classes govern how those settings are stored -- for instance, where and how they + * are saved, and where they're read from to get current status. + */ +public class ZenPrioritySendersHelper { + public static final String TAG = "ZenPrioritySendersHelper"; + + static final int UNKNOWN = -10; + static final String KEY_ANY = "senders_anyone"; + static final String KEY_CONTACTS = "senders_contacts"; + static final String KEY_STARRED = "senders_starred_contacts"; + static final String KEY_IMPORTANT = "conversations_important"; + static final String KEY_NONE = "senders_none"; + + private int mNumImportantConversations = UNKNOWN; + + private static final Intent ALL_CONTACTS_INTENT = + new Intent(Contacts.Intents.UI.LIST_DEFAULT) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + private static final Intent STARRED_CONTACTS_INTENT = + new Intent(Contacts.Intents.UI.LIST_STARRED_ACTION) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + private static final Intent FALLBACK_INTENT = new Intent(Intent.ACTION_MAIN) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + + private final Context mContext; + private final ZenModeBackend mZenModeBackend; + private final NotificationBackend mNotificationBackend; + private final PackageManager mPackageManager; + private final boolean mIsMessages; // if this is false, then this preference is for calls + private final SelectorWithWidgetPreference.OnClickListener mSelectorClickListener; + + private PreferenceCategory mPreferenceCategory; + private List mSelectorPreferences = new ArrayList<>(); + + public ZenPrioritySendersHelper(Context context, boolean isMessages, + ZenModeBackend zenModeBackend, NotificationBackend notificationBackend, + SelectorWithWidgetPreference.OnClickListener clickListener) { + mContext = context; + mIsMessages = isMessages; + mZenModeBackend = zenModeBackend; + mNotificationBackend = notificationBackend; + mSelectorClickListener = clickListener; + + mPackageManager = mContext.getPackageManager(); + if (!FALLBACK_INTENT.hasCategory(Intent.CATEGORY_APP_CONTACTS)) { + FALLBACK_INTENT.addCategory(Intent.CATEGORY_APP_CONTACTS); + } + } + + void displayPreference(PreferenceCategory preferenceCategory) { + mPreferenceCategory = preferenceCategory; + if (mPreferenceCategory.getPreferenceCount() == 0) { + makeSelectorPreference(KEY_STARRED, + com.android.settings.R.string.zen_mode_from_starred, mIsMessages); + makeSelectorPreference(KEY_CONTACTS, + com.android.settings.R.string.zen_mode_from_contacts, mIsMessages); + if (mIsMessages) { + makeSelectorPreference(KEY_IMPORTANT, + com.android.settings.R.string.zen_mode_from_important_conversations, true); + updateChannelCounts(); + } + makeSelectorPreference(KEY_ANY, + com.android.settings.R.string.zen_mode_from_anyone, mIsMessages); + makeSelectorPreference(KEY_NONE, + com.android.settings.R.string.zen_mode_none_messages, mIsMessages); + updateSummaries(); + } + } + + void updateState(int currContactsSetting, int currConversationsSetting) { + for (SelectorWithWidgetPreference pref : mSelectorPreferences) { + // for each preference, check whether the current state matches what this state + // would look like if the button were checked. + final int[] checkedState = keyToSettingEndState(pref.getKey(), true); + final int checkedContactsSetting = checkedState[0]; + final int checkedConversationsSetting = checkedState[1]; + + boolean match = checkedContactsSetting == currContactsSetting; + if (mIsMessages && checkedConversationsSetting != UNKNOWN) { + // "UNKNOWN" in checkedContactsSetting means this preference doesn't govern + // the priority senders setting, so the full match happens when either + // the priority senders setting matches or if it's UNKNOWN so only the conversation + // setting needs to match. + match = (match || checkedContactsSetting == UNKNOWN) + && (checkedConversationsSetting == currConversationsSetting); + } + + pref.setChecked(match); + } + } + + void updateSummaries() { + for (SelectorWithWidgetPreference pref : mSelectorPreferences) { + pref.setSummary(getSummary(pref.getKey())); + } + } + + // Gets the desired end state of the priority senders and conversations for the given key + // and whether it is being checked or unchecked. UNKNOWN indicates no change in state. + // + // Returns an integer array with 2 entries. The first entry is the setting for priority senders + // and the second entry is for priority conversation senders; if isMessages is false, then + // no changes will ever be prescribed for conversation senders. + int[] keyToSettingEndState(String key, boolean checked) { + int[] endState = new int[]{ UNKNOWN, UNKNOWN }; + if (!checked) { + // Unchecking any priority-senders-based state should reset the state to NONE. + // "Unchecking" the NONE state doesn't do anything, in practice. + switch (key) { + case KEY_STARRED: + case KEY_CONTACTS: + case KEY_ANY: + case KEY_NONE: + endState[0] = ZenModeBackend.SOURCE_NONE; + } + + // For messages, unchecking "priority conversations" and "any" should reset conversation + // state to "NONE" as well. + if (mIsMessages) { + switch (key) { + case KEY_IMPORTANT: + case KEY_ANY: + case KEY_NONE: + endState[1] = CONVERSATION_SENDERS_NONE; + } + } + } else { + // All below is for the enabling (checked) state. + switch (key) { + case KEY_STARRED: + endState[0] = NotificationManager.Policy.PRIORITY_SENDERS_STARRED; + break; + case KEY_CONTACTS: + endState[0] = NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS; + break; + case KEY_ANY: + endState[0] = NotificationManager.Policy.PRIORITY_SENDERS_ANY; + break; + case KEY_NONE: + endState[0] = ZenModeBackend.SOURCE_NONE; + } + + // In the messages case *only*, also handle changing of conversation settings. + if (mIsMessages) { + switch (key) { + case KEY_IMPORTANT: + endState[1] = CONVERSATION_SENDERS_IMPORTANT; + break; + case KEY_ANY: + endState[1] = CONVERSATION_SENDERS_ANYONE; + break; + case KEY_NONE: + endState[1] = CONVERSATION_SENDERS_NONE; + } + } + } + + // Error case check: if somehow, after all of that, endState is still {UNKNOWN, UNKNOWN}, + // something has gone wrong. + if (endState[0] == UNKNOWN && endState[1] == UNKNOWN) { + throw new IllegalArgumentException("invalid key " + key); + } + + return endState; + } + + // Returns the preferences, if any, that should be newly saved for the specified setting and + // checked state in an array where index 0 is the new senders setting and 1 the new + // conversations setting. A return value of UNKNOWN indicates that nothing should change. + // + // The returned conversations setting will always be UNKNOWN (not to change) in the calls case. + // + // Checking and unchecking is mostly an operation of setting or unsetting the relevant + // preference, except for some special handling where the conversation setting overlaps: + // - setting or unsetting "priority contacts" or "contacts" has no effect on the + // priority conversation setting, and vice versa + // - if "priority conversations" is selected, and the user checks "anyone", the conversation + // setting is also set to any conversations + // - if "anyone" is previously selected, and the user clicks "priority conversations", then + // the contacts setting is additionally reset to "none". + // - if "anyone" is previously selected, and the user clicks one of the contacts values, + // then the conversations setting is additionally reset to "none". + int[] settingsToSaveOnClick(SelectorWithWidgetPreference preference, + int currSendersSetting, int currConvosSetting) { + int[] savedSettings = new int[]{ UNKNOWN, UNKNOWN }; + + // If the preference isn't a checkbox, always consider this to be "checking" the setting. + // Otherwise, toggle. + final int[] endState = keyToSettingEndState(preference.getKey(), + preference.isCheckBox() ? !preference.isChecked() : true); + final int prioritySendersSetting = endState[0]; + final int priorityConvosSetting = endState[1]; + + if (prioritySendersSetting != UNKNOWN && prioritySendersSetting != currSendersSetting) { + savedSettings[0] = prioritySendersSetting; + } + + // Only handle conversation settings for the messages case. If not messages, there should + // never be any change to the conversation senders setting. + if (mIsMessages) { + if (priorityConvosSetting != UNKNOWN + && priorityConvosSetting != currConvosSetting) { + savedSettings[1] = priorityConvosSetting; + } + + // Special-case handling for the "priority conversations" checkbox: + // If a specific selection exists for priority senders (starred, contacts), we leave + // it untouched. Otherwise (when the senders is set to "any"), set it to NONE. + if (preference.getKey() == KEY_IMPORTANT + && currSendersSetting == PRIORITY_SENDERS_ANY) { + savedSettings[0] = ZenModeBackend.SOURCE_NONE; + } + + // Flip-side special case for clicking either "contacts" option: if a specific selection + // exists for priority conversations, leave it untouched; otherwise, set to none. + if ((preference.getKey() == KEY_STARRED || preference.getKey() == KEY_CONTACTS) + && currConvosSetting == CONVERSATION_SENDERS_ANYONE) { + savedSettings[1] = CONVERSATION_SENDERS_NONE; + } + } + + return savedSettings; + } + + private String getSummary(String key) { + switch (key) { + case KEY_STARRED: + return mZenModeBackend.getStarredContactsSummary(mContext); + case KEY_CONTACTS: + return mZenModeBackend.getContactsNumberSummary(mContext); + case KEY_IMPORTANT: + return getConversationSummary(); + case KEY_ANY: + return mContext.getResources().getString(mIsMessages + ? R.string.zen_mode_all_messages_summary + : R.string.zen_mode_all_calls_summary); + case KEY_NONE: + default: + return null; + } + } + + private String getConversationSummary() { + final int numConversations = mNumImportantConversations; + + if (numConversations == UNKNOWN) { + return null; + } else { + MessageFormat msgFormat = new MessageFormat( + mContext.getString(R.string.zen_mode_conversations_count), + Locale.getDefault()); + Map args = new HashMap<>(); + args.put("count", numConversations); + return msgFormat.format(args); + } + } + + void updateChannelCounts() { + // Load conversations + ParceledListSlice impConversations = + mNotificationBackend.getConversations(true); + int numImportantConversations = 0; + if (impConversations != null) { + for (ConversationChannelWrapper conversation : impConversations.getList()) { + if (!conversation.getNotificationChannel().isDemoted()) { + numImportantConversations++; + } + } + } + mNumImportantConversations = numImportantConversations; + } + + private SelectorWithWidgetPreference makeSelectorPreference(String key, int titleId, + boolean isCheckbox) { + final SelectorWithWidgetPreference pref = + new SelectorWithWidgetPreference(mPreferenceCategory.getContext(), isCheckbox); + pref.setKey(key); + pref.setTitle(titleId); + pref.setOnClickListener(mSelectorClickListener); + + View.OnClickListener widgetClickListener = getWidgetClickListener(key); + if (widgetClickListener != null) { + pref.setExtraWidgetOnClickListener(widgetClickListener); + } + + mPreferenceCategory.addPreference(pref); + mSelectorPreferences.add(pref); + return pref; + } + + private View.OnClickListener getWidgetClickListener(String key) { + if (!KEY_CONTACTS.equals(key) && !KEY_STARRED.equals(key) && !KEY_IMPORTANT.equals(key)) { + return null; + } + + if (KEY_STARRED.equals(key) && !isStarredIntentValid()) { + return null; + } + + if (KEY_CONTACTS.equals(key) && !isContactsIntentValid()) { + return null; + } + + return new View.OnClickListener() { + @Override + public void onClick(View v) { + if (KEY_STARRED.equals(key) + && STARRED_CONTACTS_INTENT.resolveActivity(mPackageManager) != null) { + mContext.startActivity(STARRED_CONTACTS_INTENT); + } else if (KEY_CONTACTS.equals(key) + && ALL_CONTACTS_INTENT.resolveActivity(mPackageManager) != null) { + mContext.startActivity(ALL_CONTACTS_INTENT); + } else if (KEY_IMPORTANT.equals(key)) { + new SubSettingLauncher(mContext) + .setDestination(ConversationListSettings.class.getName()) + .setSourceMetricsCategory(SettingsEnums.DND_CONVERSATIONS) + .launch(); + } else { + mContext.startActivity(FALLBACK_INTENT); + } + } + }; + } + + private boolean isStarredIntentValid() { + return STARRED_CONTACTS_INTENT.resolveActivity(mPackageManager) != null + || FALLBACK_INTENT.resolveActivity(mPackageManager) != null; + } + + private boolean isContactsIntentValid() { + return ALL_CONTACTS_INTENT.resolveActivity(mPackageManager) != null + || FALLBACK_INTENT.resolveActivity(mPackageManager) != null; + } +} diff --git a/src/com/android/settings/notification/zen/ZenRulePrioritySendersPreferenceController.java b/src/com/android/settings/notification/zen/ZenRulePrioritySendersPreferenceController.java new file mode 100644 index 00000000000..cee496ee8b0 --- /dev/null +++ b/src/com/android/settings/notification/zen/ZenRulePrioritySendersPreferenceController.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification.zen; + +import static com.android.settings.notification.zen.ZenPrioritySendersHelper.UNKNOWN; + +import android.app.AutomaticZenRule; +import android.content.Context; +import android.os.AsyncTask; +import android.service.notification.ZenPolicy; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.notification.NotificationBackend; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.widget.SelectorWithWidgetPreference; + +/** + * Shared controller for custom rule priority senders settings for both messages and calls. + * + * Most functionality is the same as that of the main zen mode messages and calls settings; + * these controllers handle which senders are allowed to break through DND for messages or calls, + * with possible settings options being: starred contacts, all contacts, priority conversations + * (for messages only), anyone, or no one. + */ +public class ZenRulePrioritySendersPreferenceController + extends AbstractZenCustomRulePreferenceController { + private final boolean mIsMessages; // if this is false, then this preference is for calls + + private PreferenceCategory mPreferenceCategory; + private ZenPrioritySendersHelper mHelper; + + public ZenRulePrioritySendersPreferenceController(Context context, String key, + Lifecycle lifecycle, boolean isMessages, NotificationBackend notificationBackend) { + super(context, key, lifecycle); + mIsMessages = isMessages; + + mHelper = new ZenPrioritySendersHelper( + context, isMessages, mBackend, notificationBackend, mSelectorClickListener); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + mPreferenceCategory = screen.findPreference(getPreferenceKey()); + mHelper.displayPreference(mPreferenceCategory); + super.displayPreference(screen); + } + + @Override + public String getPreferenceKey() { + return KEY; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + if (mRule != null && mRule.getZenPolicy() != null) { + final int currContactsSetting = getPrioritySenders(); + final int currConversationsSetting = getPriorityConversationSenders(); + mHelper.updateState(currContactsSetting, currConversationsSetting); + } + } + + @Override + public void onResume(AutomaticZenRule rule, String id) { + super.onResume(rule, id); + if (mIsMessages) { + updateChannelCounts(); + } + mHelper.updateSummaries(); + } + + private void updateChannelCounts() { + // Load conversations + new AsyncTask() { + @Override + protected Void doInBackground(Void... unused) { + mHelper.updateChannelCounts(); + return null; + } + + @Override + protected void onPostExecute(Void unused) { + if (mContext == null) { + return; + } + updateState(mPreferenceCategory); + } + }.execute(); + } + + private int getPrioritySenders() { + if (mRule == null || mRule.getZenPolicy() == null) { + return UNKNOWN; + } + if (mIsMessages) { + return ZenModeBackend.getContactSettingFromZenPolicySetting( + mRule.getZenPolicy().getPriorityMessageSenders()); + } else { + return ZenModeBackend.getContactSettingFromZenPolicySetting( + mRule.getZenPolicy().getPriorityCallSenders()); + } + } + + private int getPriorityConversationSenders() { + if (mRule == null || mRule.getZenPolicy() == null) { + return UNKNOWN; + } + return mRule.getZenPolicy().getPriorityConversationSenders(); + } + + // Returns the ZenPolicySetting enum associated with the provided NotificationManager.Policy. + static @ZenPolicy.PeopleType int zenPolicySettingFromSender(int senderSetting) { + return ZenModeBackend.getZenPolicySettingFromPrefKey( + ZenModeBackend.getKeyFromSetting(senderSetting)); + } + + @VisibleForTesting + SelectorWithWidgetPreference.OnClickListener mSelectorClickListener = + new SelectorWithWidgetPreference.OnClickListener() { + @Override + public void onRadioButtonClicked(SelectorWithWidgetPreference preference) { + if (mRule == null || mRule.getZenPolicy() == null) { + return; + } + + final int[] settingsToSave = mHelper.settingsToSaveOnClick(preference, + getPrioritySenders(), getPriorityConversationSenders()); + final int prioritySendersSetting = settingsToSave[0]; + final int priorityConvosSetting = settingsToSave[1]; + + // if both are UNKNOWN then just return + if (prioritySendersSetting == UNKNOWN && priorityConvosSetting == UNKNOWN) { + return; + } + + if (prioritySendersSetting != UNKNOWN) { + if (mIsMessages) { + mRule.setZenPolicy(new ZenPolicy.Builder(mRule.getZenPolicy()) + .allowMessages( + zenPolicySettingFromSender(prioritySendersSetting)) + .build()); + } else { + mRule.setZenPolicy(new ZenPolicy.Builder(mRule.getZenPolicy()) + .allowCalls( + zenPolicySettingFromSender(prioritySendersSetting)) + .build()); + } + } + + if (mIsMessages && priorityConvosSetting != UNKNOWN) { + mRule.setZenPolicy(new ZenPolicy.Builder(mRule.getZenPolicy()) + .allowConversations(priorityConvosSetting) + .build()); + } + + // Save any changes + mBackend.updateZenRule(mId, mRule); + } + }; +} diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModePrioritySendersPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModePrioritySendersPreferenceControllerTest.java index 23dc71a44c9..99fa8e6cdd4 100644 --- a/tests/robotests/src/com/android/settings/notification/zen/ZenModePrioritySendersPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModePrioritySendersPreferenceControllerTest.java @@ -16,31 +16,30 @@ package com.android.settings.notification.zen; +import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE; +import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_NONE; +import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_STARRED; -import static com.android.settings.notification.zen.ZenModePrioritySendersPreferenceController.KEY_ANY; -import static com.android.settings.notification.zen.ZenModePrioritySendersPreferenceController.KEY_CONTACTS; -import static com.android.settings.notification.zen.ZenModePrioritySendersPreferenceController.KEY_NONE; -import static com.android.settings.notification.zen.ZenModePrioritySendersPreferenceController.KEY_STARRED; - -import static com.google.common.truth.Truth.assertThat; +import static com.android.settings.notification.zen.ZenModeBackend.SOURCE_NONE; +import static com.android.settings.notification.zen.ZenPrioritySendersHelper.KEY_ANY; +import static com.android.settings.notification.zen.ZenPrioritySendersHelper.KEY_CONTACTS; +import static com.android.settings.notification.zen.ZenPrioritySendersHelper.KEY_NONE; +import static com.android.settings.notification.zen.ZenPrioritySendersHelper.UNKNOWN; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.app.NotificationManager; -import android.content.ContentResolver; import android.content.Context; -import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; @@ -51,143 +50,145 @@ import com.android.settingslib.widget.SelectorWithWidgetPreference; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.util.ReflectionHelpers; -import java.util.List; - @RunWith(RobolectricTestRunner.class) public class ZenModePrioritySendersPreferenceControllerTest { - private ZenModePrioritySendersPreferenceController mMessagesController; + private ZenModePrioritySendersPreferenceController mCallsController; @Mock private ZenModeBackend mZenBackend; @Mock - private PreferenceCategory mMockPrefCategory; - @Mock - private NotificationManager.Policy mPolicy; + private PreferenceCategory mMockMessagesPrefCategory, mMockCallsPrefCategory; @Mock private PreferenceScreen mPreferenceScreen; @Mock private NotificationBackend mNotifBackend; + @Mock + private ZenPrioritySendersHelper mHelper; - private List mSelectorWithWidgetPreferences; - private ContentResolver mContentResolver; private Context mContext; + @Before public void setup() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; mMessagesController = new ZenModePrioritySendersPreferenceController( - mContext, "test_key_messages", mock(Lifecycle.class), true); + mContext, "test_key_messages", mock(Lifecycle.class), true, + mNotifBackend); ReflectionHelpers.setField(mMessagesController, "mBackend", mZenBackend); + ReflectionHelpers.setField(mMessagesController, "mHelper", mHelper); - when(mMockPrefCategory.getContext()).thenReturn(mContext); + mCallsController = new ZenModePrioritySendersPreferenceController( + mContext, "test_key_calls", mock(Lifecycle.class), false, + mNotifBackend); + ReflectionHelpers.setField(mCallsController, "mBackend", mZenBackend); + ReflectionHelpers.setField(mCallsController, "mHelper", mHelper); + + when(mMockMessagesPrefCategory.getContext()).thenReturn(mContext); + when(mMockCallsPrefCategory.getContext()).thenReturn(mContext); when(mPreferenceScreen.findPreference(mMessagesController.getPreferenceKey())) - .thenReturn(mMockPrefCategory); - captureRadioButtons(); + .thenReturn(mMockMessagesPrefCategory); + when(mPreferenceScreen.findPreference(mCallsController.getPreferenceKey())) + .thenReturn(mMockCallsPrefCategory); } @Test - public void displayPreference_radioButtonsCreatedOnlyOnce() { - when(mMockPrefCategory.findPreference(any())).thenReturn(mock(Preference.class)); - - // radio buttons were already created, so don't re-create them + public void displayPreference_delegatesToHelper() { mMessagesController.displayPreference(mPreferenceScreen); - verify(mMockPrefCategory, never()).addPreference(any()); + verify(mHelper, times(1)).displayPreference(mMockMessagesPrefCategory); + + mCallsController.displayPreference(mPreferenceScreen); + verify(mHelper, times(1)).displayPreference(mMockCallsPrefCategory); } @Test - public void clickAnySenders() { - // GIVEN current priority message senders are STARRED + public void clickPreference_Messages() { + // While most of the actual logical functionality for the preference key -> result + // is/should be controlled by the ZenPrioritySendersHelper, here we need to make sure + // the returned values from the helper are successfully passed through the click listener. + + // GIVEN current priority message senders are STARRED and conversation senders NONE when(mZenBackend.getPriorityMessageSenders()).thenReturn(PRIORITY_SENDERS_STARRED); + when(mZenBackend.getPriorityConversationSenders()).thenReturn(CONVERSATION_SENDERS_NONE); + + // When we ask mHelper for settings to save on click, it returns ANY for senders and + // conversations (what it would return if the user clicked "Anyone") + when(mHelper.settingsToSaveOnClick( + any(SelectorWithWidgetPreference.class), anyInt(), anyInt())) + .thenReturn(new int[]{PRIORITY_SENDERS_ANY, CONVERSATION_SENDERS_ANYONE}); // WHEN user clicks the any senders option - SelectorWithWidgetPreference allSendersRb = getButton(KEY_ANY); - allSendersRb.onClick(); + SelectorWithWidgetPreference anyPref = makePreference(KEY_ANY, true, true); + anyPref.onClick(); // THEN any senders gets saved as priority senders for messages + // and also allow any conversations verify(mZenBackend).saveSenders(PRIORITY_CATEGORY_MESSAGES, PRIORITY_SENDERS_ANY); + verify(mZenBackend).saveConversationSenders(CONVERSATION_SENDERS_ANYONE); } @Test - public void clickStarredSenders() { - // GIVEN current priority message senders are ANY - when(mZenBackend.getPriorityMessageSenders()).thenReturn(PRIORITY_SENDERS_ANY); + public void clickPreference_MessagesUnset() { + // Confirm that when asked to not set something, no ZenModeBackend call occurs. + // GIVEN current priority message senders are STARRED and conversation senders NONE + when(mZenBackend.getPriorityMessageSenders()).thenReturn(PRIORITY_SENDERS_STARRED); + when(mZenBackend.getPriorityConversationSenders()).thenReturn(CONVERSATION_SENDERS_NONE); + + when(mHelper.settingsToSaveOnClick( + any(SelectorWithWidgetPreference.class), anyInt(), anyInt())) + .thenReturn(new int[]{SOURCE_NONE, UNKNOWN}); // WHEN user clicks the starred contacts option - SelectorWithWidgetPreference starredRb = getButton(KEY_STARRED); - starredRb.onClick(); + SelectorWithWidgetPreference nonePref = makePreference(KEY_NONE, true, true); + nonePref.onClick(); - // THEN starred contacts gets saved as priority senders for messages - verify(mZenBackend).saveSenders(PRIORITY_CATEGORY_MESSAGES, PRIORITY_SENDERS_STARRED); + // THEN "none" gets saved as priority senders for messages + verify(mZenBackend).saveSenders(PRIORITY_CATEGORY_MESSAGES, SOURCE_NONE); + + // AND that no changes are made to conversation senders + verify(mZenBackend, never()).saveConversationSenders(anyInt()); } @Test - public void clickContactsSenders() { - // GIVEN current priority message senders are ANY - when(mZenBackend.getPriorityMessageSenders()).thenReturn(PRIORITY_SENDERS_ANY); + public void clickPreference_Calls() { + // GIVEN current priority call senders are ANY + when(mZenBackend.getPriorityCallSenders()).thenReturn(PRIORITY_SENDERS_ANY); - // WHEN user clicks the contacts only option - SelectorWithWidgetPreference contactsRb = getButton(KEY_CONTACTS); - contactsRb.onClick(); + // (and this shouldn't happen, but also be prepared to give an answer if asked for + // conversation senders) + when(mZenBackend.getPriorityConversationSenders()).thenReturn(CONVERSATION_SENDERS_ANYONE); - // THEN contacts gets saved as priority senders for messages - verify(mZenBackend).saveSenders(PRIORITY_CATEGORY_MESSAGES, PRIORITY_SENDERS_CONTACTS); + // Helper returns what would've happened to set priority senders to contacts + when(mHelper.settingsToSaveOnClick( + any(SelectorWithWidgetPreference.class), anyInt(), anyInt())) + .thenReturn(new int[]{PRIORITY_SENDERS_CONTACTS, CONVERSATION_SENDERS_NONE}); + + // WHEN user clicks the any senders option + SelectorWithWidgetPreference contactsPref = makePreference(KEY_CONTACTS, false, false); + contactsPref.onClick(); + + // THEN contacts gets saved as priority senders for calls + // and no conversation policies are modified + verify(mZenBackend).saveSenders(PRIORITY_CATEGORY_CALLS, PRIORITY_SENDERS_CONTACTS); + verify(mZenBackend, never()).saveConversationSenders(anyInt()); } - @Test - public void clickNoSenders() { - // GIVEN current priority message senders are ANY - when(mZenBackend.getPriorityMessageSenders()).thenReturn(PRIORITY_SENDERS_ANY); - - // WHEN user clicks the no senders option - SelectorWithWidgetPreference noSenders = getButton(KEY_NONE); - noSenders.onClick(); - - // THEN no senders gets saved as priority senders for messages - verify(mZenBackend).saveSenders(PRIORITY_CATEGORY_MESSAGES, ZenModeBackend.SOURCE_NONE); - } - - @Test - public void clickSameOptionMultipleTimes() { - // GIVEN current priority message senders are ANY - when(mZenBackend.getPriorityMessageSenders()).thenReturn(PRIORITY_SENDERS_ANY); - - // WHEN user clicks the any senders option multiple times again - SelectorWithWidgetPreference anySenders = getButton(KEY_ANY); - anySenders.onClick(); - anySenders.onClick(); - anySenders.onClick(); - - // THEN no senders are saved because this setting is already in effect - verify(mZenBackend, never()).saveSenders(PRIORITY_CATEGORY_MESSAGES, PRIORITY_SENDERS_ANY); - } - - private void captureRadioButtons() { - ArgumentCaptor rbCaptor = - ArgumentCaptor.forClass(SelectorWithWidgetPreference.class); - mMessagesController.displayPreference(mPreferenceScreen); - - // verifies 4 buttons were added - verify(mMockPrefCategory, times(4)).addPreference(rbCaptor.capture()); - mSelectorWithWidgetPreferences = rbCaptor.getAllValues(); - assertThat(mSelectorWithWidgetPreferences.size()).isEqualTo(4); - - reset(mMockPrefCategory); - } - - private SelectorWithWidgetPreference getButton(String key) { - for (SelectorWithWidgetPreference pref : mSelectorWithWidgetPreferences) { - if (key.equals(pref.getKey())) { - return pref; - } - } - return null; + // Makes a preference with the provided key and whether it's a checkbox with + // mSelectorClickListener as the onClickListener set. + private SelectorWithWidgetPreference makePreference( + String key, boolean isCheckbox, boolean isMessages) { + final SelectorWithWidgetPreference pref = + new SelectorWithWidgetPreference(mContext, isCheckbox); + pref.setKey(key); + pref.setOnClickListener( + isMessages ? mMessagesController.mSelectorClickListener + : mCallsController.mSelectorClickListener); + return pref; } } diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenRulePrioritySendersPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenRulePrioritySendersPreferenceControllerTest.java new file mode 100644 index 00000000000..86abf36e8a9 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/zen/ZenRulePrioritySendersPreferenceControllerTest.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification.zen; + +import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE; +import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_NONE; +import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY; +import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS; +import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_STARRED; + +import static com.android.settings.notification.zen.ZenModeBackend.SOURCE_NONE; +import static com.android.settings.notification.zen.ZenPrioritySendersHelper.KEY_ANY; +import static com.android.settings.notification.zen.ZenPrioritySendersHelper.KEY_CONTACTS; +import static com.android.settings.notification.zen.ZenPrioritySendersHelper.KEY_NONE; +import static com.android.settings.notification.zen.ZenPrioritySendersHelper.UNKNOWN; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.AutomaticZenRule; +import android.app.NotificationManager; +import android.content.Context; +import android.service.notification.ZenPolicy; + +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; + +import com.android.settings.notification.NotificationBackend; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.widget.SelectorWithWidgetPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(RobolectricTestRunner.class) +public class ZenRulePrioritySendersPreferenceControllerTest { + private ZenRulePrioritySendersPreferenceController mMessagesController; + private ZenRulePrioritySendersPreferenceController mCallsController; + + @Mock + private ZenModeBackend mZenBackend; + @Mock + private PreferenceCategory mMockMessagesPrefCategory, mMockCallsPrefCategory; + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock + private NotificationBackend mNotifBackend; + @Mock + private ZenPrioritySendersHelper mHelper; + + private Context mContext; + private final String mId = "test_zen_rule_id"; + private AutomaticZenRule mRule; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mRule = new AutomaticZenRule("test", null, null, null, null, + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + mMessagesController = new ZenRulePrioritySendersPreferenceController( + mContext, "test_key_messages", mock(Lifecycle.class), true, + mNotifBackend); + ReflectionHelpers.setField(mMessagesController, "mBackend", mZenBackend); + ReflectionHelpers.setField(mMessagesController, "mHelper", mHelper); + ReflectionHelpers.setField(mMessagesController, "mRule", mRule); + ReflectionHelpers.setField(mMessagesController, "mId", mId); + + mCallsController = new ZenRulePrioritySendersPreferenceController( + mContext, "test_key_calls", mock(Lifecycle.class), false, + mNotifBackend); + ReflectionHelpers.setField(mCallsController, "mBackend", mZenBackend); + ReflectionHelpers.setField(mCallsController, "mHelper", mHelper); + ReflectionHelpers.setField(mCallsController, "mRule", mRule); + ReflectionHelpers.setField(mCallsController, "mId", mId); + + when(mMockMessagesPrefCategory.getContext()).thenReturn(mContext); + when(mMockCallsPrefCategory.getContext()).thenReturn(mContext); + when(mPreferenceScreen.findPreference(mMessagesController.getPreferenceKey())) + .thenReturn(mMockMessagesPrefCategory); + when(mPreferenceScreen.findPreference(mCallsController.getPreferenceKey())) + .thenReturn(mMockCallsPrefCategory); + when(mZenBackend.getAutomaticZenRule(mId)).thenReturn(mRule); + } + + @Test + public void displayPreference_delegatesToHelper() { + mMessagesController.displayPreference(mPreferenceScreen); + verify(mHelper, times(1)).displayPreference(mMockMessagesPrefCategory); + + mCallsController.displayPreference(mPreferenceScreen); + verify(mHelper, times(1)).displayPreference(mMockCallsPrefCategory); + } + + @Test + public void clickPreference_Messages() { + // While most of the actual logical functionality for the preference key -> result + // is/should be controlled by the ZenPrioritySendersHelper, here we need to make sure + // the returned values from the helper are correctly saved to the zen policy in mRule. + + // GIVEN current priority message senders are STARRED and conversation senders NONE + setMessageSenders(PRIORITY_SENDERS_STARRED); + setConversationSenders(CONVERSATION_SENDERS_NONE); + + // When we ask mHelper for settings to save on click, it returns ANY for senders and + // conversations (what it would return if the user clicked "Anyone") + when(mHelper.settingsToSaveOnClick( + any(SelectorWithWidgetPreference.class), anyInt(), anyInt())) + .thenReturn(new int[]{PRIORITY_SENDERS_ANY, CONVERSATION_SENDERS_ANYONE}); + + // WHEN user clicks the any senders option + SelectorWithWidgetPreference anyPref = makePreference(KEY_ANY, true, true); + anyPref.onClick(); + + // THEN any senders gets saved as priority senders for messages + // and also allow any conversations + assertThat(getMessageSenders()).isEqualTo(PRIORITY_SENDERS_ANY); + assertThat(getConversationSenders()).isEqualTo(CONVERSATION_SENDERS_ANYONE); + } + + @Test + public void clickPreference_MessagesUnset() { + // Confirm that when asked to not set something, no change occurs. + // GIVEN current priority message senders are STARRED and conversation senders NONE + setMessageSenders(PRIORITY_SENDERS_STARRED); + setConversationSenders(CONVERSATION_SENDERS_NONE); + + when(mHelper.settingsToSaveOnClick( + any(SelectorWithWidgetPreference.class), anyInt(), anyInt())) + .thenReturn(new int[]{SOURCE_NONE, UNKNOWN}); + + // WHEN user clicks the starred contacts option + SelectorWithWidgetPreference nonePref = makePreference(KEY_NONE, true, true); + nonePref.onClick(); + + // THEN priority senders for messages is set to NONE + assertThat(getMessageSenders()).isEqualTo(SOURCE_NONE); + + // AND that conversation senders remains unchanged + assertThat(getConversationSenders()).isEqualTo(CONVERSATION_SENDERS_NONE); + } + + @Test + public void clickPreference_Calls() { + // GIVEN current priority call senders are ANY + setCallSenders(PRIORITY_SENDERS_ANY); + + // Helper returns what would've happened to set priority senders to contacts + when(mHelper.settingsToSaveOnClick( + any(SelectorWithWidgetPreference.class), anyInt(), anyInt())) + .thenReturn(new int[]{PRIORITY_SENDERS_CONTACTS, CONVERSATION_SENDERS_NONE}); + + // WHEN user clicks the any senders option + SelectorWithWidgetPreference contactsPref = makePreference(KEY_CONTACTS, false, false); + contactsPref.onClick(); + + // THEN contacts gets saved as priority senders for calls + assertThat(getCallSenders()).isEqualTo(PRIORITY_SENDERS_CONTACTS); + } + + private SelectorWithWidgetPreference makePreference( + String key, boolean isCheckbox, boolean isMessages) { + final SelectorWithWidgetPreference pref = + new SelectorWithWidgetPreference(mContext, isCheckbox); + pref.setKey(key); + pref.setOnClickListener( + isMessages ? mMessagesController.mSelectorClickListener + : mCallsController.mSelectorClickListener); + return pref; + } + + // Helper methods for setting up and reading current state on mRule. These are mostly helpful + // just to handle translating between the enums used in ZenPolicy from the ones used in + // the settings for message/call senders. + private void setMessageSenders(int messageSenders) { + mRule.setZenPolicy(new ZenPolicy.Builder(mRule.getZenPolicy()) + .allowMessages( + ZenRulePrioritySendersPreferenceController.zenPolicySettingFromSender( + messageSenders)) + .build()); + } + + private int getMessageSenders() { + return ZenModeBackend.getContactSettingFromZenPolicySetting( + mRule.getZenPolicy().getPriorityMessageSenders()); + } + + private void setCallSenders(int callSenders) { + mRule.setZenPolicy(new ZenPolicy.Builder(mRule.getZenPolicy()) + .allowCalls( + ZenRulePrioritySendersPreferenceController.zenPolicySettingFromSender( + callSenders)) + .build()); + } + + private int getCallSenders() { + return ZenModeBackend.getContactSettingFromZenPolicySetting( + mRule.getZenPolicy().getPriorityCallSenders()); + } + + // There's no enum conversion on the conversation senders, as they use the same enum, but + // these methods provide some convenient parallel usage compared to the others. + private void setConversationSenders(int conversationSenders) { + mRule.setZenPolicy(new ZenPolicy.Builder(mRule.getZenPolicy()) + .allowConversations(conversationSenders) + .build()); + } + + private int getConversationSenders() { + return mRule.getZenPolicy().getPriorityConversationSenders(); + } +} diff --git a/tests/unit/src/com/android/settings/notification/zen/ZenPrioritySendersHelperTest.java b/tests/unit/src/com/android/settings/notification/zen/ZenPrioritySendersHelperTest.java new file mode 100644 index 00000000000..d56818d04d7 --- /dev/null +++ b/tests/unit/src/com/android/settings/notification/zen/ZenPrioritySendersHelperTest.java @@ -0,0 +1,559 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification.zen; + +import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE; +import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY; +import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS; +import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_STARRED; +import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT; +import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_NONE; + +import static com.android.settings.notification.zen.ZenModeBackend.SOURCE_NONE; +import static com.android.settings.notification.zen.ZenPrioritySendersHelper.KEY_ANY; +import static com.android.settings.notification.zen.ZenPrioritySendersHelper.KEY_CONTACTS; +import static com.android.settings.notification.zen.ZenPrioritySendersHelper.KEY_IMPORTANT; +import static com.android.settings.notification.zen.ZenPrioritySendersHelper.KEY_NONE; +import static com.android.settings.notification.zen.ZenPrioritySendersHelper.KEY_STARRED; +import static com.android.settings.notification.zen.ZenPrioritySendersHelper.UNKNOWN; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; + +import androidx.preference.PreferenceCategory; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settings.notification.NotificationBackend; +import com.android.settingslib.widget.SelectorWithWidgetPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class ZenPrioritySendersHelperTest { + public static final String TAG = "ZenPrioritySendersHelperTest"; + @Mock + private PreferenceCategory mMockPrefCategory; + @Mock + private ZenModeBackend mZenBackend; + @Mock + private NotificationBackend mNotifBackend; + @Mock + private SelectorWithWidgetPreference.OnClickListener mClickListener; + + private Context mContext; + @Mock + private Resources mResources; + @Mock + private ContentResolver mContentResolver; + + // This class is simply a wrapper to override getSummary() in order to avoid ZenModeBackend + // calls. + private class ZenPrioritySendersHelperWrapper extends ZenPrioritySendersHelper { + ZenPrioritySendersHelperWrapper(Context context, boolean isMessages, + ZenModeBackend zenModeBackend, + NotificationBackend notificationBackend, + SelectorWithWidgetPreference.OnClickListener clickListener) { + super(context, isMessages, zenModeBackend, notificationBackend, clickListener); + } + + @Override + void updateSummaries() { + // Do nothing, so we don't try to get summaries from resources. + } + } + + // Extension of ArgumentMatcher to check that a preference argument has the correct preference + // key, but doesn't check any other properties. + private class PrefKeyMatcher implements ArgumentMatcher { + private String mKey; + PrefKeyMatcher(String key) { + mKey = key; + } + + public boolean matches(SelectorWithWidgetPreference pref) { + return pref.getKey() != null && pref.getKey().equals(mKey); + } + + public String toString() { + return "SelectorWithWidgetPreference matcher for key " + mKey; + } + } + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getContentResolver()).thenReturn(mContentResolver); + when(mContext.getResources()).thenReturn(mResources); + when(mMockPrefCategory.getContext()).thenReturn(mContext); + + // We don't care about resource contents, just make sure that attempting to access + // resources doesn't kill the test + when(mResources.getString(anyInt())).thenReturn("testString"); + } + + private ZenPrioritySendersHelper makeMessagesHelper() { + return new ZenPrioritySendersHelperWrapper( + mContext, true, mZenBackend, mNotifBackend, mClickListener); + } + + private ZenPrioritySendersHelper makeCallsHelper() { + return new ZenPrioritySendersHelperWrapper( + mContext, false, mZenBackend, mNotifBackend, mClickListener); + } + + private SelectorWithWidgetPreference makePreference(String key, boolean isCheckbox) { + final SelectorWithWidgetPreference pref = + new SelectorWithWidgetPreference(mContext, isCheckbox); + pref.setKey(key); + return pref; + } + + @Test + public void testDisplayPreferences_makeMessagesPrefs() { + ArgumentCaptor prefCaptor = + ArgumentCaptor.forClass(SelectorWithWidgetPreference.class); + when(mMockPrefCategory.getPreferenceCount()).thenReturn(0); // not yet created + ZenPrioritySendersHelper messagesHelper = makeMessagesHelper(); + messagesHelper.displayPreference(mMockPrefCategory); + + // Starred contacts, Contacts, Priority Conversations, Any, None + verify(mMockPrefCategory, times(5)) + .addPreference(prefCaptor.capture()); + + // First verify that the click listener has not been called yet before we start clicking on + // things. + verify(mClickListener, never()) + .onRadioButtonClicked(any(SelectorWithWidgetPreference.class)); + for (SelectorWithWidgetPreference pref : prefCaptor.getAllValues()) { + // Verify that the click listener got a click on something with this pref key. + pref.onClick(); + verify(mClickListener).onRadioButtonClicked(argThat(new PrefKeyMatcher(pref.getKey()))); + } + } + + @Test + public void testDisplayPreferences_makeCallsPrefs() { + ArgumentCaptor prefCaptor = + ArgumentCaptor.forClass(SelectorWithWidgetPreference.class); + when(mMockPrefCategory.getPreferenceCount()).thenReturn(0); // not yet created + ZenPrioritySendersHelper callsHelper = makeCallsHelper(); + callsHelper.displayPreference(mMockPrefCategory); + + // Starred contacts, Contacts, Any, None + verify(mMockPrefCategory, times(4)) + .addPreference(prefCaptor.capture()); + + // Make sure we never have the conversation one + verify(mMockPrefCategory, never()) + .addPreference(argThat(new PrefKeyMatcher(KEY_IMPORTANT))); + + verify(mClickListener, never()) + .onRadioButtonClicked(any(SelectorWithWidgetPreference.class)); + for (SelectorWithWidgetPreference pref : prefCaptor.getAllValues()) { + // Verify that the click listener got a click on something with this pref key. + pref.onClick(); + verify(mClickListener).onRadioButtonClicked(argThat(new PrefKeyMatcher(pref.getKey()))); + } + } + + @Test + public void testDisplayPreferences_createdOnlyOnce() { + // Return a nonzero number of child preference when asked. + // Then when displayPreference is called, it should never make any new preferences. + when(mMockPrefCategory.getPreferenceCount()).thenReturn(4); // already created + ZenPrioritySendersHelper callsHelper = makeCallsHelper(); + callsHelper.displayPreference(mMockPrefCategory); + callsHelper.displayPreference(mMockPrefCategory); + callsHelper.displayPreference(mMockPrefCategory); + + // Even though we called display 3 times we shouldn't add more preferences here. + verify(mMockPrefCategory, never()) + .addPreference(any(SelectorWithWidgetPreference.class)); + } + + @Test + public void testKeyToSettingEndState_messagesCheck() { + ZenPrioritySendersHelper messagesHelper = makeMessagesHelper(); + int[] endState; + + // For KEY_NONE everything should be none. + endState = messagesHelper.keyToSettingEndState(KEY_NONE, true); + assertThat(endState[0]).isEqualTo(SOURCE_NONE); + assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_NONE); + + // For KEY_ANY everything should be allowed. + endState = messagesHelper.keyToSettingEndState(KEY_ANY, true); + assertThat(endState[0]).isEqualTo(PRIORITY_SENDERS_ANY); + assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_ANYONE); + + // For [starred] contacts, we should set the priority senders, but not the conversations + endState = messagesHelper.keyToSettingEndState(KEY_STARRED, true); + assertThat(endState[0]).isEqualTo(PRIORITY_SENDERS_STARRED); + assertThat(endState[1]).isEqualTo(UNKNOWN); + + endState = messagesHelper.keyToSettingEndState(KEY_CONTACTS, true); + assertThat(endState[0]).isEqualTo(PRIORITY_SENDERS_CONTACTS); + assertThat(endState[1]).isEqualTo(UNKNOWN); + + // For priority conversations, we should set the conversations but not priority senders + endState = messagesHelper.keyToSettingEndState(KEY_IMPORTANT, true); + assertThat(endState[0]).isEqualTo(UNKNOWN); + assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_IMPORTANT); + } + + @Test + public void testKeyToSettingEndState_messagesUncheck() { + ZenPrioritySendersHelper messagesHelper = makeMessagesHelper(); + int[] endState; + + // For KEY_NONE, "unchecking" still means "none". + endState = messagesHelper.keyToSettingEndState(KEY_NONE, false); + assertThat(endState[0]).isEqualTo(SOURCE_NONE); + assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_NONE); + + // For KEY_ANY unchecking resets the state to "none". + endState = messagesHelper.keyToSettingEndState(KEY_ANY, false); + assertThat(endState[0]).isEqualTo(SOURCE_NONE); + assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_NONE); + + // For [starred] contacts, we should unset the priority senders, but not the conversations + endState = messagesHelper.keyToSettingEndState(KEY_STARRED, false); + assertThat(endState[0]).isEqualTo(SOURCE_NONE); + assertThat(endState[1]).isEqualTo(UNKNOWN); + + endState = messagesHelper.keyToSettingEndState(KEY_CONTACTS, false); + assertThat(endState[0]).isEqualTo(SOURCE_NONE); + assertThat(endState[1]).isEqualTo(UNKNOWN); + + // For priority conversations, we should set the conversations but not priority senders + endState = messagesHelper.keyToSettingEndState(KEY_IMPORTANT, false); + assertThat(endState[0]).isEqualTo(UNKNOWN); + assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_NONE); + } + + @Test + public void testKeyToSettingEndState_callsCheck() { + ZenPrioritySendersHelper callsHelper = makeCallsHelper(); + int[] endState; + + // For all of calls: we should never set conversations, as this is unrelated to calls. + // For KEY_NONE senders should be none. + endState = callsHelper.keyToSettingEndState(KEY_NONE, true); + assertThat(endState[0]).isEqualTo(SOURCE_NONE); + assertThat(endState[1]).isEqualTo(UNKNOWN); + + // For KEY_ANY senders should be ANY. + endState = callsHelper.keyToSettingEndState(KEY_ANY, true); + assertThat(endState[0]).isEqualTo(PRIORITY_SENDERS_ANY); + assertThat(endState[1]).isEqualTo(UNKNOWN); + + // For [starred] contacts, we should set the priority senders accordingly + endState = callsHelper.keyToSettingEndState(KEY_STARRED, true); + assertThat(endState[0]).isEqualTo(PRIORITY_SENDERS_STARRED); + assertThat(endState[1]).isEqualTo(UNKNOWN); + + endState = callsHelper.keyToSettingEndState(KEY_CONTACTS, true); + assertThat(endState[0]).isEqualTo(PRIORITY_SENDERS_CONTACTS); + assertThat(endState[1]).isEqualTo(UNKNOWN); + } + + @Test + public void testKeyToSettingEndState_callsUncheck() { + ZenPrioritySendersHelper callsHelper = makeCallsHelper(); + int[] endState; + + // A calls setup should never set conversations settings. + // For KEY_NONE, "unchecking" still means "none". + endState = callsHelper.keyToSettingEndState(KEY_NONE, false); + assertThat(endState[0]).isEqualTo(SOURCE_NONE); + assertThat(endState[1]).isEqualTo(UNKNOWN); + + // For KEY_ANY unchecking resets the state to "none". + endState = callsHelper.keyToSettingEndState(KEY_ANY, false); + assertThat(endState[0]).isEqualTo(SOURCE_NONE); + assertThat(endState[1]).isEqualTo(UNKNOWN); + + // For [starred] contacts, we should unset the priority senders, but not the conversations + endState = callsHelper.keyToSettingEndState(KEY_STARRED, false); + assertThat(endState[0]).isEqualTo(SOURCE_NONE); + assertThat(endState[1]).isEqualTo(UNKNOWN); + + endState = callsHelper.keyToSettingEndState(KEY_CONTACTS, false); + assertThat(endState[0]).isEqualTo(SOURCE_NONE); + assertThat(endState[1]).isEqualTo(UNKNOWN); + } + + @Test + public void testSettingsToSave_messagesNone() { + // Test coming from the same state (don't newly save redundant settings) and coming from + // different states (when settings to save should be "none" for both senders and + // conversations). + ZenPrioritySendersHelper messagesHelper = makeMessagesHelper(); + int[] savedSettings; + + // None preference; not a checkbox (so whenever we click it, it counts as "checking"). + SelectorWithWidgetPreference nonePref = makePreference(KEY_NONE, false); + + // Current settings already none; expect no settings to need to be saved + savedSettings = messagesHelper.settingsToSaveOnClick( + nonePref, SOURCE_NONE, CONVERSATION_SENDERS_NONE); + assertThat(savedSettings[0]).isEqualTo(UNKNOWN); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + + // Current settings are something else; save the "none" settings + savedSettings = messagesHelper.settingsToSaveOnClick( + nonePref, PRIORITY_SENDERS_ANY, CONVERSATION_SENDERS_ANYONE); + assertThat(savedSettings[0]).isEqualTo(SOURCE_NONE); + assertThat(savedSettings[1]).isEqualTo(CONVERSATION_SENDERS_NONE); + + // One but not the other + savedSettings = messagesHelper.settingsToSaveOnClick( + nonePref, SOURCE_NONE, CONVERSATION_SENDERS_IMPORTANT); + assertThat(savedSettings[0]).isEqualTo(UNKNOWN); + assertThat(savedSettings[1]).isEqualTo(CONVERSATION_SENDERS_NONE); + + savedSettings = messagesHelper.settingsToSaveOnClick( + nonePref, PRIORITY_SENDERS_CONTACTS, CONVERSATION_SENDERS_NONE); + assertThat(savedSettings[0]).isEqualTo(SOURCE_NONE); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + } + + @Test + public void testSettingsToSave_messagesAny() { + // Test coming from the same state (don't newly save redundant settings) and coming from + // different states (when settings to save should be "any" for both senders and + // conversations). + ZenPrioritySendersHelper messagesHelper = makeMessagesHelper(); + int[] savedSettings; + + // Any preference; checkbox. + SelectorWithWidgetPreference anyPref = makePreference(KEY_ANY, true); + + // Current settings already none; expect no settings to need to be saved + savedSettings = messagesHelper.settingsToSaveOnClick( + anyPref, PRIORITY_SENDERS_ANY, CONVERSATION_SENDERS_ANYONE); + assertThat(savedSettings[0]).isEqualTo(UNKNOWN); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + + // Current settings are something else; save the "any" settings + savedSettings = messagesHelper.settingsToSaveOnClick( + anyPref, PRIORITY_SENDERS_CONTACTS, CONVERSATION_SENDERS_IMPORTANT); + assertThat(savedSettings[0]).isEqualTo(PRIORITY_SENDERS_ANY); + assertThat(savedSettings[1]).isEqualTo(CONVERSATION_SENDERS_ANYONE); + + // It shouldn't be possible to have a starting state of one but not the other, but + // make sure it works anyway? + savedSettings = messagesHelper.settingsToSaveOnClick( + anyPref, PRIORITY_SENDERS_ANY, CONVERSATION_SENDERS_IMPORTANT); + assertThat(savedSettings[0]).isEqualTo(UNKNOWN); + assertThat(savedSettings[1]).isEqualTo(CONVERSATION_SENDERS_ANYONE); + + savedSettings = messagesHelper.settingsToSaveOnClick( + anyPref, PRIORITY_SENDERS_CONTACTS, CONVERSATION_SENDERS_ANYONE); + assertThat(savedSettings[0]).isEqualTo(PRIORITY_SENDERS_ANY); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + + // Test that unchecking the box results in a "none" state + anyPref.setChecked(true); + savedSettings = messagesHelper.settingsToSaveOnClick( + anyPref, PRIORITY_SENDERS_ANY, CONVERSATION_SENDERS_ANYONE); + assertThat(savedSettings[0]).isEqualTo(SOURCE_NONE); + assertThat(savedSettings[1]).isEqualTo(CONVERSATION_SENDERS_NONE); + } + + @Test + public void testSettingsToSave_messagesContacts() { + // Test coming from the same state (don't newly save redundant settings) and coming from + // different states. + // In addition, saving either starred or contacts has the special case where if we're + // coming from the "any" state it should also set the conversation senders to none. + ZenPrioritySendersHelper messagesHelper = makeMessagesHelper(); + int[] savedSettings; + + // Test both contacts-related preferences here. + SelectorWithWidgetPreference starredPref = makePreference(KEY_STARRED, true); + SelectorWithWidgetPreference contactsPref = makePreference(KEY_CONTACTS, true); + + // Current settings already the relevant ones; expect no settings to need to be saved + // Note that since these are checkboxes, this state shouldn't be reachable, but check it + // anyway just in case. + savedSettings = messagesHelper.settingsToSaveOnClick( + starredPref, PRIORITY_SENDERS_STARRED, CONVERSATION_SENDERS_NONE); + assertThat(savedSettings[0]).isEqualTo(UNKNOWN); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + + savedSettings = messagesHelper.settingsToSaveOnClick( + contactsPref, PRIORITY_SENDERS_CONTACTS, CONVERSATION_SENDERS_IMPORTANT); + assertThat(savedSettings[0]).isEqualTo(UNKNOWN); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + + // Current settings are something else (contacts setting or "none"); save new senders + // but do not change conversations. + savedSettings = messagesHelper.settingsToSaveOnClick( + starredPref, PRIORITY_SENDERS_CONTACTS, CONVERSATION_SENDERS_IMPORTANT); + assertThat(savedSettings[0]).isEqualTo(PRIORITY_SENDERS_STARRED); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + + savedSettings = messagesHelper.settingsToSaveOnClick( + contactsPref, SOURCE_NONE, CONVERSATION_SENDERS_NONE); + assertThat(savedSettings[0]).isEqualTo(PRIORITY_SENDERS_CONTACTS); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + + // Special additional case: if the settings are currently "any" for both, we additionally + // reset the conversation settings to none. + savedSettings = messagesHelper.settingsToSaveOnClick( + starredPref, PRIORITY_SENDERS_ANY, CONVERSATION_SENDERS_ANYONE); + assertThat(savedSettings[0]).isEqualTo(PRIORITY_SENDERS_STARRED); + assertThat(savedSettings[1]).isEqualTo(CONVERSATION_SENDERS_NONE); + + savedSettings = messagesHelper.settingsToSaveOnClick( + contactsPref, PRIORITY_SENDERS_ANY, CONVERSATION_SENDERS_ANYONE); + assertThat(savedSettings[0]).isEqualTo(PRIORITY_SENDERS_CONTACTS); + assertThat(savedSettings[1]).isEqualTo(CONVERSATION_SENDERS_NONE); + + // Test that un-checking works as well. + starredPref.setChecked(true); + contactsPref.setChecked(true); + + // Make sure we don't overwrite existing conversation senders setting when unchecking + savedSettings = messagesHelper.settingsToSaveOnClick( + starredPref, PRIORITY_SENDERS_STARRED, CONVERSATION_SENDERS_IMPORTANT); + assertThat(savedSettings[0]).isEqualTo(SOURCE_NONE); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + + savedSettings = messagesHelper.settingsToSaveOnClick( + contactsPref, PRIORITY_SENDERS_CONTACTS, CONVERSATION_SENDERS_NONE); + assertThat(savedSettings[0]).isEqualTo(SOURCE_NONE); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + } + + @Test + public void testSettingsToSave_messagesConversations() { + // Test coming from the same state (don't newly save redundant settings) and coming from + // different states. + // In addition, saving either starred or contacts has the special case where if we're + // coming from the "any" state it should also set the conversation senders to none. + ZenPrioritySendersHelper messagesHelper = makeMessagesHelper(); + int[] savedSettings; + + SelectorWithWidgetPreference convsPref = makePreference(KEY_IMPORTANT, true); + + // Current settings already the relevant ones; expect no settings to need to be saved + // Note that since these are checkboxes, this state shouldn't be reachable, but check it + // anyway just in case. + savedSettings = messagesHelper.settingsToSaveOnClick( + convsPref, PRIORITY_SENDERS_STARRED, CONVERSATION_SENDERS_IMPORTANT); + assertThat(savedSettings[0]).isEqualTo(UNKNOWN); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + + // Current settings are something else (only actual choice here is "none"); save + // new conversations but do not change senders. + savedSettings = messagesHelper.settingsToSaveOnClick( + convsPref, PRIORITY_SENDERS_CONTACTS, CONVERSATION_SENDERS_NONE); + assertThat(savedSettings[0]).isEqualTo(UNKNOWN); + assertThat(savedSettings[1]).isEqualTo(CONVERSATION_SENDERS_IMPORTANT); + + // Special additional case: if the settings are currently "any" for both, we additionally + // reset the senders settings to none. + savedSettings = messagesHelper.settingsToSaveOnClick( + convsPref, PRIORITY_SENDERS_ANY, CONVERSATION_SENDERS_ANYONE); + assertThat(savedSettings[0]).isEqualTo(SOURCE_NONE); + assertThat(savedSettings[1]).isEqualTo(CONVERSATION_SENDERS_IMPORTANT); + + // Test that un-checking works as well. + convsPref.setChecked(true); + + // Make sure we don't overwrite existing conversation senders setting when unchecking + savedSettings = messagesHelper.settingsToSaveOnClick( + convsPref, PRIORITY_SENDERS_STARRED, CONVERSATION_SENDERS_IMPORTANT); + assertThat(savedSettings[0]).isEqualTo(UNKNOWN); + assertThat(savedSettings[1]).isEqualTo(CONVERSATION_SENDERS_NONE); + } + + @Test + public void testSettingsToSave_calls() { + // Simpler test for calls: for each one, test that the relevant ones are saved if not + // already set, and that conversation settings are never changed. + ZenPrioritySendersHelper callsHelper = makeCallsHelper(); + int[] savedSettings; + + // None of the preferences are checkboxes. + SelectorWithWidgetPreference starredPref = makePreference(KEY_STARRED, false); + SelectorWithWidgetPreference contactsPref = makePreference(KEY_CONTACTS, false); + SelectorWithWidgetPreference anyPref = makePreference(KEY_ANY, false); + SelectorWithWidgetPreference nonePref = makePreference(KEY_NONE, false); + + // Test that if the settings are already what is set, nothing happens. + savedSettings = callsHelper.settingsToSaveOnClick( + starredPref, PRIORITY_SENDERS_STARRED, UNKNOWN); + assertThat(savedSettings[0]).isEqualTo(UNKNOWN); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + + savedSettings = callsHelper.settingsToSaveOnClick( + contactsPref, PRIORITY_SENDERS_CONTACTS, UNKNOWN); + assertThat(savedSettings[0]).isEqualTo(UNKNOWN); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + + savedSettings = callsHelper.settingsToSaveOnClick(anyPref, PRIORITY_SENDERS_ANY, UNKNOWN); + assertThat(savedSettings[0]).isEqualTo(UNKNOWN); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + + savedSettings = callsHelper.settingsToSaveOnClick(nonePref, SOURCE_NONE, UNKNOWN); + assertThat(savedSettings[0]).isEqualTo(UNKNOWN); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + + // Test that if the settings are something different, the relevant thing gets saved. + savedSettings = callsHelper.settingsToSaveOnClick( + starredPref, PRIORITY_SENDERS_CONTACTS, UNKNOWN); + assertThat(savedSettings[0]).isEqualTo(PRIORITY_SENDERS_STARRED); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + + savedSettings = callsHelper.settingsToSaveOnClick( + contactsPref, PRIORITY_SENDERS_ANY, UNKNOWN); + assertThat(savedSettings[0]).isEqualTo(PRIORITY_SENDERS_CONTACTS); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + + savedSettings = callsHelper.settingsToSaveOnClick(anyPref, SOURCE_NONE, UNKNOWN); + assertThat(savedSettings[0]).isEqualTo(PRIORITY_SENDERS_ANY); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + + savedSettings = callsHelper.settingsToSaveOnClick( + nonePref, PRIORITY_SENDERS_STARRED, UNKNOWN); + assertThat(savedSettings[0]).isEqualTo(SOURCE_NONE); + assertThat(savedSettings[1]).isEqualTo(UNKNOWN); + } +}