Migrate people settings to new modes UI

Flag: android.app.modes_ui
Bug: 337079247
Test: make -j RunSettingsRoboTests ROBOTEST_FILTER=com.android.settings.notification.modes
Change-Id: If5e7b82a006e856b4aceca7acdfc8cc0d11092a7
This commit is contained in:
Julia Reynolds
2024-05-17 09:34:31 -04:00
parent f60595d0d5
commit 2bd1799fb9
21 changed files with 2152 additions and 4 deletions

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2024 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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="zen_mode_calls_settings_page"
settings:searchable="false"
android:title="@string/zen_mode_calls_title">
<PreferenceCategory
android:key="zen_mode_settings_category_calls"
android:title="@string/zen_mode_calls_header"
settings:allowDividerBelow="true">
</PreferenceCategory>
<!-- Repeat callers -->
<SwitchPreferenceCompat
android:key="zen_mode_repeat_callers"
android:title="@string/zen_mode_repeat_callers_title"
settings:allowDividerAbove="true"/>
</PreferenceScreen>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2024 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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="zen_mode_messages_settings_page"
settings:searchable="false"
android:title="@string/zen_mode_messages_title" >
<PreferenceCategory
android:key="zen_mode_settings_category_messages"
android:title="@string/zen_mode_messages_header">
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2024 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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/zen_category_people" >
<!-- 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"/>
<Preference
android:key="zen_mode_people_calls"
android:title="@string/zen_mode_calls_title"/>
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -22,4 +22,8 @@
android:key="header"
android:layout="@layout/settings_entity_header" />
<Preference
android:key="zen_mode_people"
android:title="@string/zen_category_people"/>
</PreferenceScreen>

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2024 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.modes;
import android.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.List;
/**
* DND Calls Settings page to determine which priority senders can bypass DND when this mode is
* activated.
*/
public class ZenModeCallsFragment extends ZenModeFragmentBase {
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new ZenModePrioritySendersPreferenceController(context,
"zen_mode_settings_category_calls", false, mBackend));
controllers.add(new ZenModeRepeatCallersPreferenceController(context,
"zen_mode_repeat_callers", mBackend,
context.getResources().getInteger(com.android.internal.R.integer
.config_zen_repeat_callers_threshold)));
return controllers;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.modes_calls_settings;
}
@Override
public int getMetricsCategory() {
// TODO: b/332937635 - make this the correct metrics category
return SettingsEnums.DND_CALLS;
}
@Override
public void onResume() {
super.onResume();
use(ZenModePrioritySendersPreferenceController.class).onResume();
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2024 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.modes;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import android.content.Context;
import android.os.Bundle;
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
public class ZenModeCallsLinkPreferenceController extends AbstractZenModePreferenceController {
private ZenModeSummaryHelper mSummaryHelper;
public ZenModeCallsLinkPreferenceController(Context context, String key,
ZenModesBackend backend) {
super(context, key, backend);
mSummaryHelper = new ZenModeSummaryHelper(context, backend);
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, getMode().getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeCallsFragment.class.getName())
.setSourceMetricsCategory(0)
.setArguments(bundle)
.toIntent());
preference.setSummary(mSummaryHelper.getCallsSettingSummary(getMode()));
}
}

View File

@@ -20,9 +20,6 @@ import android.app.AutomaticZenRule;
import android.app.settings.SettingsEnums;
import android.content.Context;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -42,6 +39,8 @@ public class ZenModeFragment extends ZenModeFragmentBase {
// {@link AbstractZenModePreferenceController}.
List<AbstractPreferenceController> prefControllers = new ArrayList<>();
prefControllers.add(new ZenModeHeaderController(context, "header", this, mBackend));
prefControllers.add(new ZenModePeopleLinkPreferenceController(
context, "zen_mode_people", mBackend));
return prefControllers;
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2024 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.modes;
import android.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.List;
/**
* DND Messages Settings page to determine which priority senders can bypass DND.
* "Messages" include SMS, MMS, and messaging apps.
*/
public class ZenModeMessagesFragment extends ZenModeFragmentBase {
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new ZenModePrioritySendersPreferenceController(context,
"zen_mode_settings_category_messages", true, mBackend));
return controllers;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.modes_messages_settings;
}
@Override
public int getMetricsCategory() {
// TODO: b/332937635 - make this the correct metrics category
return SettingsEnums.DND_MESSAGES;
}
@Override
public void onResume() {
super.onResume();
use(ZenModePrioritySendersPreferenceController.class).onResume();
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2024 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.modes;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import android.content.Context;
import android.os.Bundle;
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
public class ZenModeMessagesLinkPreferenceController extends AbstractZenModePreferenceController {
private final ZenModeSummaryHelper mSummaryHelper;
public ZenModeMessagesLinkPreferenceController(Context context, String key,
ZenModesBackend backend) {
super(context, key, backend);
mSummaryHelper = new ZenModeSummaryHelper(context, backend);
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, getMode().getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeMessagesFragment.class.getName())
.setSourceMetricsCategory(0)
.setArguments(bundle)
.toIntent());
preference.setEnabled(true);
preference.setSummary(mSummaryHelper.getMessagesSettingSummary(getMode().getPolicy()));
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2024 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.modes;
import android.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.List;
/**
* Settings page that shows what calls and messages will break through the mode and links to the
* configuration pages for both.
*/
public class ZenModePeopleFragment extends ZenModeFragmentBase {
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
List<AbstractPreferenceController> prefControllers = new ArrayList<>();
prefControllers.add(new ZenModeCallsLinkPreferenceController(
context, "zen_mode_people_calls", mBackend));
prefControllers.add(new ZenModeMessagesLinkPreferenceController(
context, "zen_mode_people_messages", mBackend));
return prefControllers;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.modes_people_settings;
}
@Override
public int getMetricsCategory() {
// TODO: b/332937635 - make this the correct metrics category
return SettingsEnums.DND_PEOPLE;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2024 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.modes;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import android.content.Context;
import android.os.Bundle;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
/**
* Preference with a link and summary about what calls and messages can break through the mode
*/
public class ZenModePeopleLinkPreferenceController extends AbstractZenModePreferenceController {
ZenModeSummaryHelper mSummaryHelper;
public ZenModePeopleLinkPreferenceController(Context context, String key,
ZenModesBackend backend) {
super(context, key, backend);
mSummaryHelper = new ZenModeSummaryHelper(mContext, mBackend);
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, getMode().getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModePeopleFragment.class.getName())
.setSourceMetricsCategory(0)
.setArguments(bundle)
.toIntent());
preference.setSummary(mSummaryHelper.getPeopleSummary(getMode()));
}
}

View File

@@ -0,0 +1,447 @@
/*
* Copyright (C) 2024 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.modes;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_NONE;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_UNSET;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_UNSET;
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.service.notification.ZenPolicy;
import android.view.View;
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.core.SubSettingLauncher;
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;
/**
* Common preference controller functionality for zen mode priority senders preferences for both
* messages and calls.
*
* These controllers handle the settings regarding which priority senders that are allowed to
* bypass DND for calls or messages, which may be one of the following values: starred contacts, all
* contacts, priority conversations (for messages only), anyone, or no one.
*/
public class ZenModePrioritySendersPreferenceController
extends AbstractZenModePreferenceController {
private final boolean mIsMessages; // if this is false, then this preference is for calls
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 = CONVERSATION_SENDERS_UNSET;
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 PreferenceCategory mPreferenceCategory;
private List<SelectorWithWidgetPreference> mSelectorPreferences = new ArrayList<>();
private final ZenModeSummaryHelper mZenModeSummaryHelper;
public ZenModePrioritySendersPreferenceController(Context context, String key,
boolean isMessages, ZenModesBackend backend) {
super(context, key, backend);
mIsMessages = isMessages;
String contactsPackage = context.getString(R.string.config_contacts_package_name);
ALL_CONTACTS_INTENT.setPackage(contactsPackage);
STARRED_CONTACTS_INTENT.setPackage(contactsPackage);
FALLBACK_INTENT.setPackage(contactsPackage);
mPackageManager = mContext.getPackageManager();
if (!FALLBACK_INTENT.hasCategory(Intent.CATEGORY_APP_CONTACTS)) {
FALLBACK_INTENT.addCategory(Intent.CATEGORY_APP_CONTACTS);
}
mZenModeSummaryHelper = new ZenModeSummaryHelper(mContext, mBackend);
}
@Override
public void displayPreference(PreferenceScreen screen) {
mPreferenceCategory = screen.findPreference(getPreferenceKey());
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);
}
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);
}
super.displayPreference(screen);
}
@Override
public void updateState(Preference preference) {
if (mIsMessages) {
updateChannelCounts();
}
final int currContactsSetting = getPrioritySenders();
final int currConversationsSetting = getPriorityConversationSenders();
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 != CONVERSATION_SENDERS_UNSET) {
// "CONVERSATION_SENDERS_UNSET" 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 CONVERSATION_SENDERS_UNSET
// so only the conversation setting needs to match.
match = (match || checkedContactsSetting == PEOPLE_TYPE_UNSET)
&& (checkedConversationsSetting == currConversationsSetting);
}
pref.setChecked(match);
}
updateSummaries();
}
public void onResume() {
if (mIsMessages) {
updateChannelCounts();
}
updateSummaries();
}
private void updateChannelCounts() {
ParceledListSlice<ConversationChannelWrapper> impConversations =
mBackend.getConversations(true);
int numImportantConversations = 0;
if (impConversations != null) {
for (ConversationChannelWrapper conversation : impConversations.getList()) {
if (!conversation.getNotificationChannel().isDemoted()) {
numImportantConversations++;
}
}
}
mNumImportantConversations = numImportantConversations;
}
private int getPrioritySenders() {
if (mIsMessages) {
return getMode().getPolicy().getPriorityMessageSenders();
} else {
return getMode().getPolicy().getPriorityCallSenders();
}
}
private int getPriorityConversationSenders() {
if (mIsMessages) {
return getMode().getPolicy().getPriorityConversationSenders();
}
return CONVERSATION_SENDERS_UNSET;
}
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 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;
}
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. [type]_UNSET 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[]{ PEOPLE_TYPE_UNSET, CONVERSATION_SENDERS_UNSET };
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] = PEOPLE_TYPE_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] = PEOPLE_TYPE_STARRED;
break;
case KEY_CONTACTS:
endState[0] = PEOPLE_TYPE_CONTACTS;
break;
case KEY_ANY:
endState[0] = PEOPLE_TYPE_ANYONE;
break;
case KEY_NONE:
endState[0] = PEOPLE_TYPE_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
// {PEOPLE_TYPE_UNSET, CONVERSATION_SENDERS_UNSET}, something has gone wrong.
if (endState[0] == PEOPLE_TYPE_UNSET && endState[1] == CONVERSATION_SENDERS_UNSET) {
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 [type]_UNSET indicates that nothing should
// change.
//
// The returned conversations setting will always be CONVERSATION_SENDERS_UNSET (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[]{ PEOPLE_TYPE_UNSET, CONVERSATION_SENDERS_UNSET };
// 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 != PEOPLE_TYPE_UNSET
&& 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 != CONVERSATION_SENDERS_UNSET
&& 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 == PEOPLE_TYPE_ANYONE) {
savedSettings[0] = PEOPLE_TYPE_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 mZenModeSummaryHelper.getStarredContactsSummary();
case KEY_CONTACTS:
return mZenModeSummaryHelper.getContactsNumberSummary();
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 == CONVERSATION_SENDERS_UNSET) {
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);
}
}
@VisibleForTesting
SelectorWithWidgetPreference.OnClickListener mSelectorClickListener =
new SelectorWithWidgetPreference.OnClickListener() {
@Override
public void onRadioButtonClicked(SelectorWithWidgetPreference preference) {
// The settingsToSaveOnClick function takes whether the preference is a
// checkbox into account to determine whether this selection is checked or unchecked.
final int[] settingsToSave = settingsToSaveOnClick(preference,
getPrioritySenders(), getPriorityConversationSenders());
final int prioritySendersSetting = settingsToSave[0];
final int priorityConvosSetting = settingsToSave[1];
ZenPolicy.Builder diffPolicy = new ZenPolicy.Builder();
if (prioritySendersSetting != PEOPLE_TYPE_UNSET) {
if (mIsMessages) {
diffPolicy.allowMessages(prioritySendersSetting);
} else {
diffPolicy.allowCalls(prioritySendersSetting);
}
}
if (mIsMessages && priorityConvosSetting != CONVERSATION_SENDERS_UNSET) {
diffPolicy.allowConversations(priorityConvosSetting);
}
getMode().setPolicy(diffPolicy.build());
mBackend.updateMode(getMode());
}
};
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2024 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.modes;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
import static android.service.notification.ZenPolicy.STATE_ALLOW;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.provider.Settings;
import android.service.notification.ZenPolicy;
import android.util.Log;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
import com.android.settings.R;
public class ZenModeRepeatCallersPreferenceController extends AbstractZenModePreferenceController
implements Preference.OnPreferenceChangeListener {
private final int mRepeatCallersThreshold;
public ZenModeRepeatCallersPreferenceController(Context context,
String key, ZenModesBackend backend, int repeatCallersThreshold) {
super(context, key, backend);
mRepeatCallersThreshold = repeatCallersThreshold;
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
TwoStatePreference pref = (TwoStatePreference) preference;
boolean anyCallersCanBypassDnd =
getMode().getPolicy().getPriorityCategoryCalls() == STATE_ALLOW
&& getMode().getPolicy().getPriorityCallSenders() == PEOPLE_TYPE_ANYONE;
// if any caller can bypass dnd then repeat callers preference is disabled
if (anyCallersCanBypassDnd) {
pref.setEnabled(false);
pref.setChecked(true);
} else {
pref.setEnabled(true);
pref.setChecked(
getMode().getPolicy().getPriorityCategoryRepeatCallers() == STATE_ALLOW);
}
setRepeatCallerSummary(preference);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean allowRepeatCallers = (Boolean) newValue;
ZenPolicy diffPolicy = new ZenPolicy.Builder()
.allowRepeatCallers(allowRepeatCallers)
.build();
getMode().setPolicy(diffPolicy);
mBackend.updateMode(getMode());
return true;
}
private void setRepeatCallerSummary(Preference preference) {
preference.setSummary(mContext.getString(R.string.zen_mode_repeat_callers_summary,
mRepeatCallersThreshold));
}
}

View File

@@ -0,0 +1,295 @@
/*
* Copyright (C) 2024 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.modes;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_NONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_ALARMS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_CALLS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_CONVERSATIONS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_EVENTS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MEDIA;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MESSAGES;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
import android.content.Context;
import android.icu.text.MessageFormat;
import android.service.notification.ZenPolicy;
import com.android.settings.R;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Predicate;
public class ZenModeSummaryHelper {
private Context mContext;
private ZenModesBackend mBackend;
public ZenModeSummaryHelper(Context context, ZenModesBackend backend) {
mContext = context;
mBackend = backend;
}
private static final int[] ALL_PRIORITY_CATEGORIES = {
PRIORITY_CATEGORY_ALARMS,
PRIORITY_CATEGORY_MEDIA,
PRIORITY_CATEGORY_SYSTEM,
PRIORITY_CATEGORY_MESSAGES,
PRIORITY_CATEGORY_CONVERSATIONS,
PRIORITY_CATEGORY_EVENTS,
PRIORITY_CATEGORY_REMINDERS,
PRIORITY_CATEGORY_CALLS,
PRIORITY_CATEGORY_REPEAT_CALLERS,
};
String getOtherSoundCategoriesSummary(ZenMode zenMode) {
List<String> enabledCategories = getEnabledCategories(
zenMode.getPolicy(),
category -> PRIORITY_CATEGORY_ALARMS == category
|| PRIORITY_CATEGORY_MEDIA == category
|| PRIORITY_CATEGORY_SYSTEM == category
|| PRIORITY_CATEGORY_REMINDERS == category
|| PRIORITY_CATEGORY_EVENTS == category,
true);
int numCategories = enabledCategories.size();
MessageFormat msgFormat = new MessageFormat(
mContext.getString(R.string.zen_mode_other_sounds_summary),
Locale.getDefault());
Map<String, Object> args = new HashMap<>();
args.put("count", numCategories);
if (numCategories >= 1) {
args.put("sound_category_1", enabledCategories.get(0));
if (numCategories >= 2) {
args.put("sound_category_2", enabledCategories.get(1));
if (numCategories == 3) {
args.put("sound_category_3", enabledCategories.get(2));
}
}
}
return msgFormat.format(args);
}
String getCallsSettingSummary(ZenMode zenMode) {
List<String> enabledCategories = getEnabledCategories(zenMode.getPolicy(),
category -> PRIORITY_CATEGORY_CALLS == category
|| PRIORITY_CATEGORY_REPEAT_CALLERS == category, true);
int numCategories = enabledCategories.size();
if (numCategories == 0) {
return mContext.getString(R.string.zen_mode_none_calls);
} else if (numCategories == 1) {
return mContext.getString(R.string.zen_mode_calls_summary_one,
enabledCategories.get(0));
} else {
return mContext.getString(R.string.zen_mode_calls_summary_two,
enabledCategories.get(0),
enabledCategories.get(1));
}
}
String getMessagesSettingSummary(ZenPolicy policy) {
List<String> enabledCategories = getEnabledCategories(policy,
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 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));
}
}
String getBlockedEffectsSummary(ZenMode zenMode) {
if (zenMode.getPolicy().shouldShowAllVisualEffects()) {
return mContext.getResources().getString(
R.string.zen_mode_restrict_notifications_summary_muted);
} else if (zenMode.getPolicy().shouldHideAllVisualEffects()) {
return mContext.getResources().getString(
R.string.zen_mode_restrict_notifications_summary_hidden);
} else {
return mContext.getResources().getString(
R.string.zen_mode_restrict_notifications_summary_custom);
}
}
private List<String> getEnabledCategories(ZenPolicy policy,
Predicate<Integer> filteredCategories, boolean capitalizeFirstInList) {
List<String> enabledCategories = new ArrayList<>();
for (int category : ALL_PRIORITY_CATEGORIES) {
boolean isFirst = capitalizeFirstInList && enabledCategories.isEmpty();
if (filteredCategories.test(category) && policy.isCategoryAllowed(category, false)) {
if (category == PRIORITY_CATEGORY_REPEAT_CALLERS
&& policy.isCategoryAllowed(PRIORITY_CATEGORY_CALLS, false)
&& policy.getPriorityCallSenders() == PEOPLE_TYPE_ANYONE) {
continue;
}
// For conversations, only the "priority conversations" setting is relevant; any
// other setting is subsumed by the messages-specific messaging.
if (category == PRIORITY_CATEGORY_CONVERSATIONS
&& policy.isCategoryAllowed(PRIORITY_CATEGORY_CONVERSATIONS, false)
&& policy.getPriorityConversationSenders()
!= CONVERSATION_SENDERS_IMPORTANT) {
continue;
}
enabledCategories.add(getCategory(category, policy, isFirst));
}
}
return enabledCategories;
}
private String getCategory(int category, ZenPolicy policy, boolean isFirst) {
if (category == PRIORITY_CATEGORY_ALARMS) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_alarms_list_first);
} else {
return mContext.getString(R.string.zen_mode_alarms_list);
}
} else if (category == PRIORITY_CATEGORY_MEDIA) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_media_list_first);
} else {
return mContext.getString(R.string.zen_mode_media_list);
}
} else if (category == PRIORITY_CATEGORY_SYSTEM) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_system_list_first);
} else {
return mContext.getString(R.string.zen_mode_system_list);
}
} else if (category == PRIORITY_CATEGORY_MESSAGES) {
if (policy.getPriorityMessageSenders() == PEOPLE_TYPE_ANYONE) {
return mContext.getString(R.string.zen_mode_from_anyone);
} else if (policy.getPriorityMessageSenders() == PEOPLE_TYPE_CONTACTS) {
return mContext.getString(R.string.zen_mode_from_contacts);
} else {
return mContext.getString(R.string.zen_mode_from_starred);
}
} else if (category == PRIORITY_CATEGORY_CONVERSATIONS
&& policy.getPriorityConversationSenders() == 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 == PRIORITY_CATEGORY_EVENTS) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_events_list_first);
} else {
return mContext.getString(R.string.zen_mode_events_list);
}
} else if (category == PRIORITY_CATEGORY_REMINDERS) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_reminders_list_first);
} else {
return mContext.getString(R.string.zen_mode_reminders_list);
}
} else if (category == PRIORITY_CATEGORY_CALLS) {
if (policy.getPriorityCallSenders() == PEOPLE_TYPE_ANYONE) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_from_anyone);
}
return mContext.getString(R.string.zen_mode_all_callers);
} else if (policy.getPriorityCallSenders() == PEOPLE_TYPE_CONTACTS) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_from_contacts);
}
return mContext.getString(R.string.zen_mode_contacts_callers);
} else {
if (isFirst) {
return mContext.getString(R.string.zen_mode_from_starred);
}
return mContext.getString(R.string.zen_mode_starred_callers);
}
} else if (category == PRIORITY_CATEGORY_REPEAT_CALLERS) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_repeat_callers);
} else {
return mContext.getString(R.string.zen_mode_repeat_callers_list);
}
}
return "";
}
public String getStarredContactsSummary() {
List<String> starredContacts = mBackend.getStarredContacts();
int numStarredContacts = starredContacts.size();
MessageFormat msgFormat = new MessageFormat(
mContext.getString(R.string.zen_mode_starred_contacts_summary_contacts),
Locale.getDefault());
Map<String, Object> args = new HashMap<>();
args.put("count", numStarredContacts);
if (numStarredContacts >= 1) {
args.put("contact_1", starredContacts.get(0));
if (numStarredContacts >= 2) {
args.put("contact_2", starredContacts.get(1));
if (numStarredContacts == 3) {
args.put("contact_3", starredContacts.get(2));
}
}
}
return msgFormat.format(args);
}
public String getContactsNumberSummary() {
MessageFormat msgFormat = new MessageFormat(
mContext.getString(R.string.zen_mode_contacts_count),
Locale.getDefault());
Map<String, Object> args = new HashMap<>();
args.put("count", mBackend.queryAllContactsData().getCount());
return msgFormat.format(args);
}
public String getPeopleSummary(ZenMode zenMode) {
final int callersAllowed = zenMode.getPolicy().getPriorityCallSenders();
final int messagesAllowed = zenMode.getPolicy().getPriorityMessageSenders();
final int conversationsAllowed = zenMode.getPolicy().getPriorityConversationSenders();
final boolean areRepeatCallersAllowed =
zenMode.getPolicy().isCategoryAllowed(PRIORITY_CATEGORY_REPEAT_CALLERS, false);
if (callersAllowed == PEOPLE_TYPE_ANYONE
&& messagesAllowed == PEOPLE_TYPE_ANYONE
&& conversationsAllowed == CONVERSATION_SENDERS_ANYONE) {
return mContext.getResources().getString(R.string.zen_mode_people_all);
} else if (callersAllowed == PEOPLE_TYPE_NONE
&& messagesAllowed == PEOPLE_TYPE_NONE
&& conversationsAllowed == CONVERSATION_SENDERS_NONE
&& !areRepeatCallersAllowed) {
return mContext.getResources().getString(R.string.zen_mode_people_none);
} else {
return mContext.getResources().getString(R.string.zen_mode_people_some);
}
}
}

View File

@@ -21,14 +21,22 @@ import static java.util.Objects.requireNonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AutomaticZenRule;
import android.app.INotificationManager;
import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.database.Cursor;
import android.net.Uri;
import android.os.ServiceManager;
import android.provider.ContactsContract;
import android.provider.Settings;
import android.service.notification.Condition;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.ZenAdapters;
import android.service.notification.ZenModeConfig;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import java.time.Duration;
@@ -51,6 +59,8 @@ class ZenModesBackend {
private static ZenModesBackend sInstance;
private final NotificationManager mNotificationManager;
static INotificationManager sINM = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
private final Context mContext;
@@ -105,6 +115,54 @@ class ZenModesBackend {
}
}
public ParceledListSlice<ConversationChannelWrapper> getConversations(boolean onlyImportant) {
try {
return sINM.getConversations(onlyImportant);
} catch (Exception e) {
Log.w(TAG, "Error calling NoMan", e);
return ParceledListSlice.emptyList();
}
}
public List<String> getStarredContacts() {
Cursor cursor = null;
try {
cursor = queryStarredContactsData();
return getStarredContacts(cursor);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
@VisibleForTesting
List<String> getStarredContacts(Cursor cursor) {
List<String> starredContacts = new ArrayList<>();
if (cursor != null && cursor.moveToFirst()) {
do {
String contact = cursor.getString(0);
starredContacts.add(contact != null ? contact :
mContext.getString(R.string.zen_mode_starred_contacts_empty_name));
} while (cursor.moveToNext());
}
return starredContacts;
}
private Cursor queryStarredContactsData() {
return mContext.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,
new String[]{ContactsContract.Contacts.DISPLAY_NAME_PRIMARY},
ContactsContract.Data.STARRED + "=1", null,
ContactsContract.Data.TIMES_CONTACTED);
}
Cursor queryAllContactsData() {
return mContext.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,
new String[]{ContactsContract.Contacts.DISPLAY_NAME_PRIMARY},
null, null, null);
}
private ZenMode getManualDndMode(ZenModeConfig config) {
// TODO: b/333530553 - Read ZenDeviceEffects of manual DND.
// TODO: b/333682392 - Replace with final strings for name & trigger description

View File

@@ -57,9 +57,9 @@ abstract class ZenModesFragmentBase extends RestrictedDashboardFragment {
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
mContext = context;
mBackend = ZenModesBackend.getInstance(context);
super.onAttach(context);
}
@Override
@@ -77,6 +77,12 @@ abstract class ZenModesFragmentBase extends RestrictedDashboardFragment {
}
}
@Override
public void onResume() {
super.onResume();
updateZenModeState();
}
@Override
public void onStop() {
super.onStop();

View File

@@ -0,0 +1,80 @@
/*
* Copyright (C) 2024 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.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
import org.junit.Before;
import org.junit.Rule;
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.shadows.ShadowApplication;
import org.robolectric.util.ReflectionHelpers;
@RunWith(RobolectricTestRunner.class)
public final class ZenModeCallsLinkPreferenceControllerTest {
private ZenModeCallsLinkPreferenceController mController;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
@Mock
private ZenModesBackend mBackend;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mController = new ZenModeCallsLinkPreferenceController(
mContext, "something", mBackend);
}
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
public void testHasSummary() {
Preference pref = mock(Preference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
.build(), true);
mController.updateZenMode(pref, zenMode);
verify(pref).setSummary(any());
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2024 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.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
import org.junit.Before;
import org.junit.Rule;
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;
@RunWith(RobolectricTestRunner.class)
public final class ZenModeMessagesLinkPreferenceControllerTest {
private ZenModeMessagesLinkPreferenceController mController;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
@Mock
private ZenModesBackend mBackend;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mController = new ZenModeMessagesLinkPreferenceController(
mContext, "something", mBackend);
}
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
public void testHasSummary() {
Preference pref = mock(Preference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
.build(), true);
mController.updateZenMode(pref, zenMode);
verify(pref).setSummary(any());
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2024 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.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
import org.junit.Before;
import org.junit.Rule;
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;
@RunWith(RobolectricTestRunner.class)
public final class ZenModePeopleLinkPreferenceControllerTest {
private ZenModePeopleLinkPreferenceController mController;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
@Mock
private ZenModesBackend mBackend;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mController = new ZenModePeopleLinkPreferenceController(
mContext, "something", mBackend);
}
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
public void testHasSummary() {
Preference pref = mock(Preference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
.build(), true);
mController.updateZenMode(pref, zenMode);
verify(pref).setSummary(any());
}
}

View File

@@ -0,0 +1,509 @@
/*
* Copyright (C) 2024 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.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_NONE;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_UNSET;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_UNSET;
import static android.service.notification.ZenPolicy.STATE_ALLOW;
import static android.service.notification.ZenPolicy.STATE_DISALLOW;
import static android.service.notification.ZenPolicy.STATE_UNSET;
import static com.android.settings.notification.modes.ZenModePrioritySendersPreferenceController.KEY_ANY;
import static com.android.settings.notification.modes.ZenModePrioritySendersPreferenceController.KEY_CONTACTS;
import static com.android.settings.notification.modes.ZenModePrioritySendersPreferenceController.KEY_IMPORTANT;
import static com.android.settings.notification.modes.ZenModePrioritySendersPreferenceController.KEY_NONE;
import static com.android.settings.notification.modes.ZenModePrioritySendersPreferenceController.KEY_STARRED;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.never;
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.Flags;
import android.content.Context;
import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import org.junit.Before;
import org.junit.Rule;
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;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
@EnableFlags(Flags.FLAG_MODES_UI)
public final class ZenModePrioritySendersPreferenceControllerTest {
private ZenModePrioritySendersPreferenceController mCallsController;
private ZenModePrioritySendersPreferenceController mMessagesController;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
@Mock
private ZenModesBackend mBackend;
@Mock
private PreferenceCategory mMockMessagesPrefCategory, mMockCallsPrefCategory;
@Mock
private PreferenceScreen mPreferenceScreen;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mMessagesController = new ZenModePrioritySendersPreferenceController(
mContext, "messages", true, mBackend);
mCallsController = new ZenModePrioritySendersPreferenceController(
mContext, "calls", false, mBackend);
when(mMockMessagesPrefCategory.getContext()).thenReturn(mContext);
when(mMockCallsPrefCategory.getContext()).thenReturn(mContext);
when(mPreferenceScreen.findPreference(mMessagesController.getPreferenceKey()))
.thenReturn(mMockMessagesPrefCategory);
when(mPreferenceScreen.findPreference(mCallsController.getPreferenceKey()))
.thenReturn(mMockCallsPrefCategory);
}
// 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;
}
// 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;
}
}
@Test
public void testDisplayPreferences_makeMessagesPrefs() {
ArgumentCaptor<SelectorWithWidgetPreference> prefCaptor =
ArgumentCaptor.forClass(SelectorWithWidgetPreference.class);
when(mMockMessagesPrefCategory.getPreferenceCount()).thenReturn(0); // not yet created
mMessagesController.displayPreference(mPreferenceScreen);
// Starred contacts, Contacts, Priority Conversations, Any, None
verify(mMockMessagesPrefCategory, times(5)).addPreference(prefCaptor.capture());
}
@Test
public void testDisplayPreferences_makeCallsPrefs() {
ArgumentCaptor<SelectorWithWidgetPreference> prefCaptor =
ArgumentCaptor.forClass(SelectorWithWidgetPreference.class);
when(mMockCallsPrefCategory.getPreferenceCount()).thenReturn(0); // not yet created
mCallsController.displayPreference(mPreferenceScreen);
// Starred contacts, Contacts, Any, None
verify(mMockCallsPrefCategory, times(4)).addPreference(prefCaptor.capture());
// Make sure we never have the conversation one
verify(mMockCallsPrefCategory, never())
.addPreference(argThat(new PrefKeyMatcher(KEY_IMPORTANT)));
}
@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(mMockCallsPrefCategory.getPreferenceCount()).thenReturn(4); // already created
mCallsController.displayPreference(mPreferenceScreen);
mCallsController.displayPreference(mPreferenceScreen);
mCallsController.displayPreference(mPreferenceScreen);
// Even though we called display 3 times we shouldn't add more preferences here.
verify(mMockCallsPrefCategory, never())
.addPreference(any(SelectorWithWidgetPreference.class));
}
@Test
public void testKeyToSettingEndState_messagesCheck() {
int[] endState;
// For KEY_NONE everything should be none.
endState = mMessagesController.keyToSettingEndState(KEY_NONE, true);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_NONE);
// For KEY_ANY everything should be allowed.
endState = mMessagesController.keyToSettingEndState(KEY_ANY, true);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_ANYONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_ANYONE);
// For [starred] contacts, we should set the priority senders, but not the conversations
endState = mMessagesController.keyToSettingEndState(KEY_STARRED, true);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_STARRED);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
endState = mMessagesController.keyToSettingEndState(KEY_CONTACTS, true);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_CONTACTS);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For priority conversations, we should set the conversations but not priority senders
endState = mMessagesController.keyToSettingEndState(KEY_IMPORTANT, true);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_UNSET);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_IMPORTANT);
}
@Test
public void testKeyToSettingEndState_messagesUncheck() {
int[] endState;
// For KEY_NONE, "unchecking" still means "none".
endState = mMessagesController.keyToSettingEndState(KEY_NONE, false);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_NONE);
// For KEY_ANY unchecking resets the state to "none".
endState = mMessagesController.keyToSettingEndState(KEY_ANY, false);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_NONE);
// For [starred] contacts, we should unset the priority senders, but not the conversations
endState = mMessagesController.keyToSettingEndState(KEY_STARRED, false);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
endState = mMessagesController.keyToSettingEndState(KEY_CONTACTS, false);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For priority conversations, we should set the conversations but not priority senders
endState = mMessagesController.keyToSettingEndState(KEY_IMPORTANT, false);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_UNSET);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_NONE);
}
@Test
public void testKeyToSettingEndState_callsCheck() {
int[] endState;
// For calls: we should never set conversations, as this is unrelated to calls.
// For KEY_NONE senders should be none.
endState = mCallsController.keyToSettingEndState(KEY_NONE, true);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For KEY_ANY senders should be ANY.
endState = mCallsController.keyToSettingEndState(KEY_ANY, true);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_ANYONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For [starred] contacts, we should set the priority senders accordingly
endState = mCallsController.keyToSettingEndState(KEY_STARRED, true);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_STARRED);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
endState = mCallsController.keyToSettingEndState(KEY_CONTACTS, true);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_CONTACTS);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
}
@Test
public void testKeyToSettingEndState_callsUncheck() {
int[] endState;
// A calls setup should never set conversations settings.
// For KEY_NONE, "unchecking" still means "none".
endState = mCallsController.keyToSettingEndState(KEY_NONE, false);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For KEY_ANY unchecking resets the state to "none".
endState = mCallsController.keyToSettingEndState(KEY_ANY, false);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For [starred] contacts, we should unset the priority senders, but not the conversations
endState = mCallsController.keyToSettingEndState(KEY_STARRED, false);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
endState = mCallsController.keyToSettingEndState(KEY_CONTACTS, false);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
}
@Test
public void testSettingsToSaveOnClick_messagesCheck() {
SelectorWithWidgetPreference anyPref = makePreference(KEY_ANY, true, true);
SelectorWithWidgetPreference nonePref = makePreference(KEY_NONE, true, true);
SelectorWithWidgetPreference contactsPref = makePreference(KEY_CONTACTS, true, true);
SelectorWithWidgetPreference starredPref = makePreference(KEY_STARRED, true, true);
SelectorWithWidgetPreference impPref = makePreference(KEY_IMPORTANT, true, true);
int[] endState;
// For KEY_NONE everything should be none.
nonePref.setChecked(true);
endState = mMessagesController.settingsToSaveOnClick(
nonePref, PEOPLE_TYPE_ANYONE, CONVERSATION_SENDERS_ANYONE);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_NONE);
// For KEY_ANY everything should be allowed.
anyPref.setChecked(true);
endState = mMessagesController.settingsToSaveOnClick(
anyPref, PEOPLE_TYPE_NONE, CONVERSATION_SENDERS_NONE);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_ANYONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_ANYONE);
// For [starred] contacts, we should set the priority senders, but not the conversations
starredPref.setChecked(true);
endState = mMessagesController.settingsToSaveOnClick(
starredPref, PEOPLE_TYPE_NONE, CONVERSATION_SENDERS_NONE);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_STARRED);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
contactsPref.setChecked(true);
endState = mMessagesController.settingsToSaveOnClick(
contactsPref, PEOPLE_TYPE_NONE, CONVERSATION_SENDERS_NONE);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_CONTACTS);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For priority conversations, we should set the conversations but not priority senders
impPref.setChecked(true);
endState = mMessagesController.settingsToSaveOnClick(
impPref, PEOPLE_TYPE_NONE, CONVERSATION_SENDERS_NONE);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_UNSET);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_IMPORTANT);
}
@Test
public void testSettingsToSaveOnClick_messagesUncheck() {
int[] endState;
SelectorWithWidgetPreference anyPref = makePreference(KEY_ANY, true, true);
SelectorWithWidgetPreference nonePref = makePreference(KEY_NONE, true, true);
SelectorWithWidgetPreference contactsPref = makePreference(KEY_CONTACTS, true, true);
SelectorWithWidgetPreference starredPref = makePreference(KEY_STARRED, true, true);
SelectorWithWidgetPreference impPref = makePreference(KEY_IMPORTANT, true, true);
// For KEY_NONE, "unchecking" still means "none".
nonePref.setChecked(false);
endState = mMessagesController.settingsToSaveOnClick(
nonePref, PEOPLE_TYPE_NONE, CONVERSATION_SENDERS_NONE);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_UNSET);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For KEY_ANY unchecking resets the state to "none".
anyPref.setChecked(false);
endState = mMessagesController.settingsToSaveOnClick(
anyPref, PEOPLE_TYPE_ANYONE, CONVERSATION_SENDERS_ANYONE);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_NONE);
// For [starred] contacts, we should unset the priority senders, but not the conversations
starredPref.setChecked(false);
endState = mMessagesController.settingsToSaveOnClick(
starredPref, PEOPLE_TYPE_STARRED, CONVERSATION_SENDERS_IMPORTANT);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
contactsPref.setChecked(false);
endState = mMessagesController.settingsToSaveOnClick(
contactsPref, PEOPLE_TYPE_CONTACTS, CONVERSATION_SENDERS_IMPORTANT);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For priority conversations, we should set the conversations but not priority senders
impPref.setChecked(false);
endState = mMessagesController.settingsToSaveOnClick(
impPref, PEOPLE_TYPE_CONTACTS, CONVERSATION_SENDERS_IMPORTANT);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_UNSET);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_NONE);
}
@Test
public void testSettingsToSaveOnClick_callsCheck() {
int[] endState;
SelectorWithWidgetPreference anyPref = makePreference(KEY_ANY, true, true);
SelectorWithWidgetPreference nonePref = makePreference(KEY_NONE, true, true);
SelectorWithWidgetPreference contactsPref = makePreference(KEY_CONTACTS, true, true);
SelectorWithWidgetPreference starredPref = makePreference(KEY_STARRED, true, true);
// For calls: we should never set conversations, as this is unrelated to calls.
// For KEY_NONE senders should be none.
nonePref.setChecked(true);
endState = mCallsController.settingsToSaveOnClick(
nonePref, PEOPLE_TYPE_CONTACTS, CONVERSATION_SENDERS_IMPORTANT);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For KEY_ANY senders should be ANY.
anyPref.setChecked(true);
endState = mCallsController.settingsToSaveOnClick(
anyPref, PEOPLE_TYPE_CONTACTS, CONVERSATION_SENDERS_IMPORTANT);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_ANYONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For [starred] contacts, we should set the priority senders accordingly
starredPref.setChecked(true);
endState = mCallsController.settingsToSaveOnClick(
starredPref, PEOPLE_TYPE_CONTACTS, CONVERSATION_SENDERS_IMPORTANT);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_STARRED);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
contactsPref.setChecked(true);
endState = mCallsController.settingsToSaveOnClick(
contactsPref, PEOPLE_TYPE_STARRED, CONVERSATION_SENDERS_IMPORTANT);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_CONTACTS);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
}
@Test
public void testSettingsToSaveOnClick_callsUncheck() {
int[] endState;
SelectorWithWidgetPreference anyPref = makePreference(KEY_ANY, true, true);
SelectorWithWidgetPreference nonePref = makePreference(KEY_NONE, true, true);
SelectorWithWidgetPreference contactsPref = makePreference(KEY_CONTACTS, true, true);
SelectorWithWidgetPreference starredPref = makePreference(KEY_STARRED, true, true);
// A calls setup should never set conversations settings.
// For KEY_NONE, "unchecking" still means "none".
nonePref.setChecked(false);
endState = mCallsController.settingsToSaveOnClick(
nonePref, PEOPLE_TYPE_NONE, CONVERSATION_SENDERS_NONE);
assertThat(endState[0]).isEqualTo(STATE_UNSET);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For KEY_ANY unchecking resets the state to "none".
anyPref.setChecked(false);
endState = mCallsController.settingsToSaveOnClick(
anyPref, PEOPLE_TYPE_ANYONE, CONVERSATION_SENDERS_ANYONE);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For [starred] contacts, we should unset the priority senders, but not the conversations
starredPref.setChecked(false);
endState = mCallsController.settingsToSaveOnClick(
starredPref, PEOPLE_TYPE_STARRED, CONVERSATION_SENDERS_IMPORTANT);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
contactsPref.setChecked(false);
endState = mCallsController.settingsToSaveOnClick(
contactsPref, PEOPLE_TYPE_CONTACTS, CONVERSATION_SENDERS_IMPORTANT);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
}
@Test
public void testSettingsToSave_messages_noChange() {
int[] savedSettings;
SelectorWithWidgetPreference nonePref = makePreference(KEY_NONE, true, true);
nonePref.setChecked(true);
savedSettings = mMessagesController.settingsToSaveOnClick(
nonePref, PEOPLE_TYPE_NONE, CONVERSATION_SENDERS_NONE);
assertThat(savedSettings[0]).isEqualTo(STATE_UNSET);
assertThat(savedSettings[1]).isEqualTo(STATE_UNSET);
SelectorWithWidgetPreference anyPref = makePreference(KEY_ANY, true, true);
anyPref.setChecked(true);
savedSettings = mMessagesController.settingsToSaveOnClick(
anyPref, PEOPLE_TYPE_ANYONE, CONVERSATION_SENDERS_ANYONE);
assertThat(savedSettings[0]).isEqualTo(STATE_UNSET);
assertThat(savedSettings[1]).isEqualTo(STATE_UNSET);
SelectorWithWidgetPreference starredPref = makePreference(KEY_STARRED, true, true);
SelectorWithWidgetPreference contactsPref = makePreference(KEY_CONTACTS, true, true);
starredPref.setChecked(true);
savedSettings = mMessagesController.settingsToSaveOnClick(
starredPref, PEOPLE_TYPE_STARRED, CONVERSATION_SENDERS_ANYONE);
assertThat(savedSettings[0]).isEqualTo(STATE_UNSET);
contactsPref.setChecked(true);
savedSettings = mMessagesController.settingsToSaveOnClick(
contactsPref, PEOPLE_TYPE_CONTACTS, CONVERSATION_SENDERS_ANYONE);
assertThat(savedSettings[0]).isEqualTo(STATE_UNSET);
SelectorWithWidgetPreference impPref = makePreference(KEY_IMPORTANT, true, true);
impPref.setChecked(true);
savedSettings = mMessagesController.settingsToSaveOnClick(
impPref, PEOPLE_TYPE_CONTACTS, CONVERSATION_SENDERS_IMPORTANT);
assertThat(savedSettings[1]).isEqualTo(STATE_UNSET);
}
@Test
public void testSettingsToSave_calls_noChange() {
int[] savedSettings;
SelectorWithWidgetPreference nonePref = makePreference(KEY_NONE, false, false);
savedSettings = mMessagesController.settingsToSaveOnClick(
nonePref, PEOPLE_TYPE_NONE, CONVERSATION_SENDERS_NONE);
assertThat(savedSettings[0]).isEqualTo(STATE_UNSET);
assertThat(savedSettings[1]).isEqualTo(STATE_UNSET);
SelectorWithWidgetPreference anyPref = makePreference(KEY_ANY, false, false);
savedSettings = mMessagesController.settingsToSaveOnClick(
anyPref, PEOPLE_TYPE_ANYONE, CONVERSATION_SENDERS_ANYONE);
assertThat(savedSettings[0]).isEqualTo(STATE_UNSET);
assertThat(savedSettings[1]).isEqualTo(STATE_UNSET);
SelectorWithWidgetPreference starredPref = makePreference(KEY_STARRED, false, false);
SelectorWithWidgetPreference contactsPref = makePreference(KEY_CONTACTS, false, false);
savedSettings = mMessagesController.settingsToSaveOnClick(
starredPref, PEOPLE_TYPE_STARRED, CONVERSATION_SENDERS_ANYONE);
assertThat(savedSettings[0]).isEqualTo(STATE_UNSET);
savedSettings = mMessagesController.settingsToSaveOnClick(
contactsPref, PEOPLE_TYPE_CONTACTS, CONVERSATION_SENDERS_ANYONE);
assertThat(savedSettings[0]).isEqualTo(STATE_UNSET);
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2024 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.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
import static com.google.common.truth.Truth.assertThat;
import android.app.AutomaticZenRule;
import android.content.Context;
import android.net.Uri;
import android.service.notification.ZenPolicy;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class ZenModesSummaryHelperTest {
private Context mContext;
private ZenModesBackend mBackend;
private ZenModeSummaryHelper mSummaryHelper;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mBackend = new ZenModesBackend(mContext);
mSummaryHelper = new ZenModeSummaryHelper(mContext, mBackend);
}
@Test
public void getPeopleSummary_noOne() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
.setType(AutomaticZenRule.TYPE_BEDTIME)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
.build();
ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getPeopleSummary(zenMode)).isEqualTo("No one can interrupt");
}
@Test
public void getPeopleSummary_some() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
.setType(AutomaticZenRule.TYPE_BEDTIME)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowCalls(PEOPLE_TYPE_CONTACTS).build())
.build();
ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getPeopleSummary(zenMode)).isEqualTo("Some people can interrupt");
}
@Test
public void getPeopleSummary_all() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
.setType(AutomaticZenRule.TYPE_BEDTIME)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowCalls(PEOPLE_TYPE_ANYONE).
allowConversations(CONVERSATION_SENDERS_ANYONE)
.allowMessages(PEOPLE_TYPE_ANYONE).build())
.build();
ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getPeopleSummary(zenMode)).isEqualTo("All people can interrupt");
}
}