Merge messages & conversations settings for DND priority senders.

This change explicitly allows for multiselect between priority senders (starred, contacts) & priority conversations, and also allows unchecking boxes by clicking on the same ones again.

Also makes the screens for setting messages and calls in custom rules consistent in behavior with the main DND settings. Since much of the functionality is shared, this change refactors most of the logic into a new helper class used by both.

While these changes also affect how the "calls" screen is constructed, in effect there is no change to the functionality of priority call sender settings except under the hood.

Test: atest ZenPrioritySendersHelperTest; Settings robotests
Bug: 190180868
Bug: 197223270
Change-Id: I894775537a18feb7a891b2668b9a613a203a129c
This commit is contained in:
Yuri Lin
2021-07-12 12:02:53 -04:00
parent 0571ab940a
commit 00d824657d
17 changed files with 1585 additions and 302 deletions

View File

@@ -9801,6 +9801,8 @@
<string name="zen_mode_conversations_section_title">Conversations that can interrupt</string>
<string name="zen_mode_from_all_conversations">All conversations</string>
<string name="zen_mode_from_important_conversations">Priority conversations</string>
<!-- [CHAR LIMIT=40] Version of the above for "priority conversations" when it is a non-first member of a list -->
<string name="zen_mode_from_important_conversations_second">priority conversations</string>
<string name="zen_mode_from_no_conversations">None</string>
<!-- [CHAR LIMIT=NONE] Zen mode settings: Number of conversations allowed to bypass DND -->
<string name="zen_mode_conversations_count">
@@ -9874,6 +9876,8 @@
<string name="zen_mode_from_contacts">Contacts</string>
<!-- [CHAR LIMIT=40] Zen mode settings: Calls or messages option value: From starred contacts only -->
<string name="zen_mode_from_starred">Starred contacts</string>
<!-- [CHAR LIMIT=40] Zen mode settings: Messages option values: From some people (but not all or none), may be contacts or conversations -->
<string name="zen_mode_from_some">Some people or conversations</string>
<!-- Do not disturb settings, calls summary [CHAR LIMIT=100]-->
<string name="zen_calls_summary_starred_repeat">From starred contacts and repeat callers</string>

View File

@@ -26,12 +26,6 @@
android:key="zen_mode_settings_category_calls"
android:title="@string/zen_mode_calls_header"
settings:allowDividerBelow="true">
<!-- Senders image -->
<com.android.settingslib.widget.LayoutPreference
android:key="zen_mode_calls_image"
android:layout="@layout/zen_mode_senders_image"
android:selectable="false"/>
</PreferenceCategory>
<!-- Repeat callers -->

View File

@@ -23,16 +23,7 @@
<PreferenceCategory
android:title="@string/zen_mode_settings_category"
android:key="zen_mode_settings_category_messages">
<!-- Messages -->
<ListPreference
android:key="zen_mode_messages"
android:title="@string/zen_mode_messages"
android:entries="@array/zen_mode_contacts_messages_entries"
android:entryValues="@array/zen_mode_contacts_values"/>
<Preference
android:key="zen_mode_starred_contacts_messages"
android:title="@string/zen_mode_starred_contacts_title"/>
<!-- selector preferences added here by ZenRulePrioritySendersPreferenceController -->
</PreferenceCategory>
<com.android.settingslib.widget.FooterPreference/>

View File

@@ -25,12 +25,6 @@
<PreferenceCategory
android:key="zen_mode_settings_category_messages"
android:title="@string/zen_mode_messages_header">
<!-- Senders image -->
<com.android.settingslib.widget.LayoutPreference
android:key="zen_mode_messages_image"
android:layout="@layout/zen_mode_senders_image"
android:selectable="false"/>
</PreferenceCategory>
<com.android.settingslib.widget.FooterPreference/>

View File

@@ -20,37 +20,21 @@
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/zen_category_people" >
<!-- Conversations -->
<PreferenceCategory
android:key="zen_mode_settings_category_conversations"
android:title="@string/zen_mode_conversations_section_title">
<!-- Senders image -->
<com.android.settingslib.widget.LayoutPreference
android:key="zen_mode_conversations_image"
android:layout="@layout/zen_mode_senders_overlay_image"
android:selectable="false"/>
<Preference
android:key="zen_mode_conversations"
android:title="@string/zen_mode_conversations_title"
android:fragment="com.android.settings.notification.zen.ZenModeConversationsSettings"/>
</PreferenceCategory>
<!-- Calls & Messages -->
<PreferenceCategory
android:key="zen_mode_people_calls_messages_section"
android:title="@string/zen_mode_people_calls_messages_section_title">
<Preference
android:key="zen_mode_people_messages"
android:title="@string/zen_mode_messages_title"
android:fragment="com.android.settings.notification.zen.ZenModeMessagesSettings"/>
<Preference
android:key="zen_mode_people_calls"
android:title="@string/zen_mode_calls_title"
android:fragment="com.android.settings.notification.zen.ZenModeCallsSettings"/>
<Preference
android:key="zen_mode_people_messages"
android:title="@string/zen_mode_messages_title"
android:fragment="com.android.settings.notification.zen.ZenModeMessagesSettings"/>
</PreferenceCategory>
<!-- Footer that shows if user is put into alarms only or total silence mode by an app -->

View File

@@ -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<AbstractPreferenceController> 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()));

View File

@@ -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;

View File

@@ -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<AbstractPreferenceController> 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)));

View File

@@ -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<AbstractPreferenceController> buildPreferenceControllers(Context context,
Lifecycle lifecycle) {
List<AbstractPreferenceController> 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;

View File

@@ -56,10 +56,6 @@ public class ZenModePeopleSettings extends ZenModeSettingsBase implements Indexa
Lifecycle lifecycle, Application app, Fragment host, FragmentManager fragmentManager,
NotificationBackend notificationBackend) {
List<AbstractPreferenceController> 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,

View File

@@ -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<SelectorWithWidgetPreference> 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<Void, Void, Void>() {
@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;
}
}

View File

@@ -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<String> 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);

View File

@@ -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<SelectorWithWidgetPreference> 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<String, Object> args = new HashMap<>();
args.put("count", numConversations);
return msgFormat.format(args);
}
}
void updateChannelCounts() {
// Load conversations
ParceledListSlice<ConversationChannelWrapper> 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;
}
}

View File

@@ -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<Void, Void, Void>() {
@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);
}
};
}

View File

@@ -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<SelectorWithWidgetPreference> 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<SelectorWithWidgetPreference> 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;
}
}

View File

@@ -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();
}
}

View File

@@ -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<SelectorWithWidgetPreference> {
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<SelectorWithWidgetPreference> 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<SelectorWithWidgetPreference> 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);
}
}