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

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