DND Bypassing Apps redesign

- Add link in DND Conversations Page to the overall conversations list
Settings page
- Add custom_rule xml pages for custom schedule rule settings for
messages and calls (so the UI is the same as before the message/calls
redesign)
- Change app exceptions to display apps with subtext indicating which
notitfication channels are allowed to bypass dnd (previously, would
display each channel individually)
- Add individual AppBypassDnd channel pages where users can decide which
channels will bypass DND for an app on a single page
(AppChannelsBypassingDndSettings)
- Only remove dnd bypassing apps preferences from the preference list if the list changed,
else just update the preference itself to avoid the list from flashing

Test: make RunSettingsRoboTests7
Bug: 151845457
Change-Id: If12d8921e1405aefb1066acc2ef5c55d216fe47a
This commit is contained in:
Beverly
2020-03-24 08:54:30 -04:00
committed by Beverly Tai
parent cb90ffafbb
commit f707950ee7
26 changed files with 1128 additions and 523 deletions

View File

@@ -6908,6 +6908,7 @@
<!-- Summary for add user action, when it's disabled [CHAR LIMIT=100] -->
<string name="user_add_max_count">You can add up to <xliff:g id="user_count">%1$d</xliff:g> users</string>
<!-- Message to secondary users that only owner can manage users [CHAR LIMIT=none] -->
<string name="user_cannot_manage_message" product="tablet">Only the tablet\u2019s owner can manage users.</string>
<!-- Message to secondary users that only owner can manage users [CHAR LIMIT=none] -->
@@ -8973,8 +8974,22 @@
<string name="zen_mode_bypassing_apps">Allow apps to override</string>
<!-- [CHAR LIMIT=100] Zen mode settings: Allow apps to bypass DND header -->
<string name="zen_mode_bypassing_apps_header">Apps that can interrupt</string>
<!-- [CHAR LIMIT=100] Zen mode settings: Add apps to bypass DND header -->
<string name="zen_mode_bypassing_apps_add_header">Select more apps</string>
<!-- [CHAR LIMIT=120] Zen mode settings: No apps are bypassing DND -->
<string name="zen_mode_bypassing_apps_none">No apps selected</string>
<!-- [CHAR LIMIT=120] Zen mode settings: No apps are bypassing DND -->
<string name="zen_mode_bypassing_apps_subtext_none">No apps can interrupt</string>
<!-- [CHAR LIMIT=120] Zen mode settings: Preference to add apps that are allowed to bypass DND -->
<string name="zen_mode_bypassing_apps_add">Add apps</string>
<!-- [CHAR LIMIT=120] Zen mode settings: Summary indicating all notification channels can
bypass DND for this app -->
<string name="zen_mode_bypassing_apps_summary_all">All notifications</string>
<!-- [CHAR LIMIT=120] Zen mode settings: Summary indicating all notification channels can
bypass DND for this app -->
<string name="zen_mode_bypassing_apps_summary_some">Some notifications</string>
<!-- [CHAR LIMIT=NONE] Zen mode settings: Footer for DND bypassing apps settings -->
<string name="zen_mode_bypassing_apps_footer">Selected people can still reach you, even if you don\u2019t allow apps to interrupt</string>
<!-- [CHAR LIMIT=120] Zen mode settings: Allow apps to bypass DND -->
<plurals name="zen_mode_bypassing_apps_subtext">
<item quantity="one"><xliff:g id="app_name" example="Nest">%s</xliff:g> can interrupt</item>
@@ -8989,6 +9004,11 @@
<string name="zen_mode_bypassing_apps_all_summary">All notifications</string>
<!-- [CHAR LIMIT=100] Zen mode settings: App that can bypass DND's secondary text describing which notification channels from the app can bypass DND-->
<string name="zen_mode_bypassing_apps_some_summary">Some notifications</string>
<!-- [CHAR LIMIT=100] Zen mode settings: Allow notifications from an app to bypass DND header -->
<string name="zen_mode_bypassing_app_channels_header">Notifications that can interrupt</string>
<!-- [CHAR LIMIT=100] Zen mode settings: Allow all notifications from an app to bypass DND
toggle title -->
<string name="zen_mode_bypassing_app_channels_toggle_all">Allow all notifications</string>
<!-- [CHAR LIMIT=120] Zen mode settings: Summary for sound interruption settings -->
<plurals name="zen_mode_other_sounds_summary">
@@ -9022,9 +9042,9 @@
<!-- [CHAR LIMIT=50] Zen mode settings: Repeat callers (ie: repeat callers are allowed to bypass dnd) -->
<string name="zen_mode_repeat_callers_list">repeat callers</string>
<!-- [CHAR LIMIT=50] Zen mode settings: calls summary -->
<string name="zen_mode_calls_summary_one">Allow from <xliff:g id="caller type" example="contacts">%1$s</xliff:g></string>
<string name="zen_mode_calls_summary_one"><xliff:g id="caller type" example="contacts">%1$s</xliff:g></string>
<!-- [CHAR LIMIT=50] Zen mode settings: calls summary -->
<string name="zen_mode_calls_summary_two">Allow from <xliff:g id="caller type" example="starred contacts">%1$s</xliff:g> and <xliff:g id="callert tpye" example="repeat callers">%2$s</xliff:g></string>
<string name="zen_mode_calls_summary_two"><xliff:g id="caller type" example="starred contacts">%1$s</xliff:g> and <xliff:g id="caller type" example="repeat callers">%2$s</xliff:g></string>
<!-- [CHAR LIMIT=200] Zen mode settings: Repeat callers option summary -->
<string name="zen_mode_repeat_callers_summary">If the same person calls a second time within a <xliff:g id="minutes">%d</xliff:g> minute period</string>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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_mode_settings_title">
<com.android.settingslib.widget.LayoutPreference
android:key="pref_app_header"
android:layout="@layout/settings_entity_header" />
<PreferenceCategory
android:key="zen_mode_bypassing_app_channels_list"
android:title="@string/zen_mode_bypassing_app_channels_header">
<!-- add app channel toggles here -->
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -14,10 +14,11 @@
limitations under the License.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="conversation_list"
android:title="zen_mode_conversations_title">
android:title="@string/zen_mode_conversations_title">
<PreferenceCategory
android:key="important_conversations"

View File

@@ -17,4 +17,21 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/zen_mode_bypassing_apps_title" />
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/zen_mode_bypassing_apps_title">
<PreferenceCategory
android:key="zen_mode_bypassing_apps_list"
android:title="@string/zen_mode_bypassing_apps_header">
<!-- apps that have notifications that can bypass DND are added here -->
</PreferenceCategory>
<Preference
android:key="zen_mode_bypassing_apps_add"
android:title="@string/zen_mode_bypassing_apps_add"
android:icon="@drawable/ic_add_24dp"
settings:allowDividerAbove="true" />
<com.android.settingslib.widget.FooterPreference
android:title="@string/zen_mode_bypassing_apps_footer" />
</PreferenceScreen>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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:key="zen_mode_custom_rule_calls_settings_page"
android:title="@string/zen_mode_calls_title" >
<PreferenceCategory
android:key="zen_mode_settings_category_calls"
android:title="@string/zen_mode_settings_category">
<!-- Calls -->
<ListPreference
android:key="zen_mode_calls"
android:title="@string/zen_mode_calls"
android:entries="@array/zen_mode_contacts_calls_entries"
android:entryValues="@array/zen_mode_contacts_values"/>
<Preference
android:key="zen_mode_starred_contacts_callers"
android:title="@string/zen_mode_starred_contacts_title"/>
<!-- Repeat callers -->
<SwitchPreference
android:key="zen_mode_repeat_callers"
android:title="@string/zen_mode_repeat_callers_title" />
</PreferenceCategory>
<com.android.settingslib.widget.FooterPreference/>
</PreferenceScreen>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 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:key="zen_mode_custom_rule_messages_settings_page"
android:title="@string/zen_mode_messages_title" >
<PreferenceCategory
android:title="@string/zen_mode_settings_category"
android:key="zen_mode_settings_category_messages">
<!-- Messages -->
<ListPreference
android:key="zen_mode_messages"
android:title="@string/zen_mode_messages"
android:entries="@array/zen_mode_contacts_messages_entries"
android:entryValues="@array/zen_mode_contacts_values"/>
<Preference
android:key="zen_mode_starred_contacts_messages"
android:title="@string/zen_mode_starred_contacts_title"/>
</PreferenceCategory>
<com.android.settingslib.widget.FooterPreference/>
</PreferenceScreen>

View File

@@ -0,0 +1,227 @@
/*
* Copyright (C) 2020 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.app;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings;
import androidx.core.text.BidiFormatter;
import androidx.lifecycle.LifecycleObserver;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.widget.MasterSwitchPreference;
import com.android.settingslib.RestrictedSwitchPreference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Populates the PreferenceCategory with notification channels associated with the given app.
* Users can allow/disallow notification channels from bypassing DND on a single settings
* page.
*/
public class AppChannelsBypassingDndPreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin, LifecycleObserver {
private static final String KEY = "zen_mode_bypassing_app_channels_list";
private static final String ARG_FROM_SETTINGS = "fromSettings";
private RestrictedSwitchPreference mAllNotificationsToggle;
private PreferenceCategory mPreferenceCategory;
private final List<NotificationChannel> mChannels = new ArrayList<>();
public AppChannelsBypassingDndPreferenceController(
Context context,
NotificationBackend backend) {
super(context, backend);
}
@Override
public void displayPreference(PreferenceScreen screen) {
mPreferenceCategory = screen.findPreference(KEY);
mAllNotificationsToggle = new RestrictedSwitchPreference(mPreferenceCategory.getContext());
mAllNotificationsToggle.setTitle(R.string.zen_mode_bypassing_app_channels_toggle_all);
mAllNotificationsToggle.setDisabledByAdmin(mAdmin);
mAllNotificationsToggle.setEnabled(
(mAdmin == null || !mAllNotificationsToggle.isDisabledByAdmin()));
mAllNotificationsToggle.setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference pref) {
SwitchPreference preference = (SwitchPreference) pref;
final boolean bypassDnd = preference.isChecked();
for (NotificationChannel channel : mChannels) {
if (showNotification(channel) && isChannelConfigurable(channel)) {
channel.setBypassDnd(bypassDnd);
channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, channel);
}
}
// the 0th index is the mAllNotificationsToggle which allows users to
// toggle all notifications from this app to bypass DND
for (int i = 1; i < mPreferenceCategory.getPreferenceCount(); i++) {
MasterSwitchPreference childPreference =
(MasterSwitchPreference) mPreferenceCategory.getPreference(i);
childPreference.setChecked(showNotificationInDnd(mChannels.get(i - 1)));
}
return true;
}
});
loadAppChannels();
super.displayPreference(screen);
}
@Override
public String getPreferenceKey() {
return KEY;
}
@Override
public boolean isAvailable() {
return mAppRow != null;
}
@Override
public void updateState(Preference preference) {
if (mAppRow != null) {
loadAppChannels();
}
}
private void loadAppChannels() {
// Load channel settings
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... unused) {
List<NotificationChannelGroup> mChannelGroupList = mBackend.getGroups(mAppRow.pkg,
mAppRow.uid).getList();
mChannels.clear();
for (NotificationChannelGroup channelGroup : mChannelGroupList) {
for (NotificationChannel channel : channelGroup.getChannels()) {
if (!isConversation(channel)) {
mChannels.add(channel);
}
}
}
Collections.sort(mChannels, CHANNEL_COMPARATOR);
return null;
}
@Override
protected void onPostExecute(Void unused) {
if (mContext == null) {
return;
}
populateList();
}
}.execute();
}
private void populateList() {
if (mPreferenceCategory == null) {
return;
}
mPreferenceCategory.removeAll();
mPreferenceCategory.addPreference(mAllNotificationsToggle);
for (NotificationChannel channel : mChannels) {
MasterSwitchPreference channelPreference = new MasterSwitchPreference(mContext);
channelPreference.setDisabledByAdmin(mAdmin);
channelPreference.setSwitchEnabled(
(mAdmin == null || !channelPreference.isDisabledByAdmin())
&& isChannelConfigurable(channel)
&& showNotification(channel));
channelPreference.setTitle(BidiFormatter.getInstance().unicodeWrap(channel.getName()));
channelPreference.setChecked(showNotificationInDnd(channel));
channelPreference.setOnPreferenceChangeListener(
new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference pref, Object val) {
boolean switchOn = (Boolean) val;
channel.setBypassDnd(switchOn);
channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, channel);
mAllNotificationsToggle.setChecked(areAllChannelsBypassing());
return true;
}
});
Bundle channelArgs = new Bundle();
channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mAppRow.uid);
channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mAppRow.pkg);
channelArgs.putString(Settings.EXTRA_CHANNEL_ID, channel.getId());
channelArgs.putBoolean(ARG_FROM_SETTINGS, true);
channelPreference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ChannelNotificationSettings.class.getName())
.setArguments(channelArgs)
.setTitleRes(com.android.settings.R.string.notification_channel_title)
.setSourceMetricsCategory(SettingsEnums.DND_APPS_BYPASSING)
.toIntent());
mPreferenceCategory.addPreference(channelPreference);
}
mAllNotificationsToggle.setChecked(areAllChannelsBypassing());
}
private boolean areAllChannelsBypassing() {
boolean allChannelsBypassing = true;
for (NotificationChannel channel : mChannels) {
if (showNotification(channel)) {
allChannelsBypassing &= showNotificationInDnd(channel);
}
}
return allChannelsBypassing;
}
/**
* Whether notifications from this channel would show if DND were on.
*/
private boolean showNotificationInDnd(NotificationChannel channel) {
return channel.canBypassDnd() && showNotification(channel);
}
/**
* Whether notifications from this channel would show if DND weren't on.
*/
private boolean showNotification(NotificationChannel channel) {
return channel.getImportance() != IMPORTANCE_NONE;
}
/**
* Whether this notification channel is representing a conversation.
*/
private boolean isConversation(NotificationChannel channel) {
return channel.getConversationId() != null && !channel.isDemoted();
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (C) 2020 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.app;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import com.android.settings.R;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.List;
/**
* Per-app Settings page that shows a list of notification channels that a user can toggle for
* the channel to bypass DND.
*
* This can be found at:
* Settings > Sound > Do Not Disturb > Apps > (Choose app)
*/
public class AppChannelsBypassingDndSettings extends NotificationSettings {
private static final String TAG = "AppChannelsBypassingDndSettings";
@Override
public int getMetricsCategory() {
return SettingsEnums.DND_APPS_BYPASSING;
}
@Override
public void onResume() {
super.onResume();
if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) {
Log.w(TAG, "Missing package or uid or packageinfo");
finish();
return;
}
for (NotificationPreferenceController controller : mControllers) {
controller.onResume(mAppRow, null, null, null, null, mSuspendedAppsAdmin);
controller.displayPreference(getPreferenceScreen());
}
updatePreferenceStates();
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.app_channels_bypassing_dnd_settings;
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
mControllers = new ArrayList<>();
mControllers.add(new HeaderPreferenceController(context, this));
mControllers.add(new AppChannelsBypassingDndPreferenceController(context,
new NotificationBackend()));
return new ArrayList<>(mControllers);
}
}

View File

@@ -33,6 +33,11 @@ import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase;
@@ -43,14 +48,8 @@ import com.android.settingslib.RestrictedSwitchPreference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
import androidx.preference.SwitchPreference;
public class ChannelListPreferenceController extends NotificationPreferenceController {
private static final String KEY = "channels";
@@ -94,7 +93,7 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
@Override
protected Void doInBackground(Void... unused) {
mChannelGroupList = mBackend.getGroups(mAppRow.pkg, mAppRow.uid).getList();
Collections.sort(mChannelGroupList, mChannelGroupComparator);
Collections.sort(mChannelGroupList, CHANNEL_GROUP_COMPARATOR);
return null;
}
@@ -142,7 +141,7 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
}
if (!group.isBlocked()) {
final List<NotificationChannel> channels = group.getChannels();
Collections.sort(channels, mChannelComparator);
Collections.sort(channels, CHANNEL_COMPARATOR);
int N = channels.size();
for (int i = 0; i < N; i++) {
final NotificationChannel channel = channels.get(i);
@@ -274,7 +273,7 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
}
} else {
final List<NotificationChannel> channels = group.getChannels();
Collections.sort(channels, mChannelComparator);
Collections.sort(channels, CHANNEL_COMPARATOR);
int N = channels.size();
for (int i = 0; i < N; i++) {
final NotificationChannel channel = channels.get(i);
@@ -283,33 +282,4 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
}
}
}
private Comparator<NotificationChannelGroup> mChannelGroupComparator =
new Comparator<NotificationChannelGroup>() {
@Override
public int compare(NotificationChannelGroup left, NotificationChannelGroup right) {
// Non-grouped channels (in placeholder group with a null id) come last
if (left.getId() == null && right.getId() != null) {
return 1;
} else if (right.getId() == null && left.getId() != null) {
return -1;
}
return left.getId().compareTo(right.getId());
}
};
protected Comparator<NotificationChannel> mChannelComparator =
(left, right) -> {
if (left.isDeleted() != right.isDeleted()) {
return Boolean.compare(left.isDeleted(), right.isDeleted());
} else if (left.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
// Uncategorized/miscellaneous legacy channel goes last
return 1;
} else if (right.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
return -1;
}
return left.getId().compareTo(right.getId());
};
}

View File

@@ -35,6 +35,7 @@ import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.Comparator;
import java.util.Objects;
/**
@@ -172,4 +173,31 @@ public abstract class NotificationPreferenceController extends AbstractPreferenc
}
return Objects.equals(NotificationChannel.DEFAULT_CHANNEL_ID, mChannel.getId());
}
public static final Comparator<NotificationChannelGroup> CHANNEL_GROUP_COMPARATOR =
new Comparator<NotificationChannelGroup>() {
@Override
public int compare(NotificationChannelGroup left, NotificationChannelGroup right) {
// Non-grouped channels (in placeholder group with a null id) come last
if (left.getId() == null && right.getId() != null) {
return 1;
} else if (right.getId() == null && left.getId() != null) {
return -1;
}
return left.getId().compareTo(right.getId());
}
};
public static final Comparator<NotificationChannel> CHANNEL_COMPARATOR = (left, right) -> {
if (left.isDeleted() != right.isDeleted()) {
return Boolean.compare(left.isDeleted(), right.isDeleted());
} else if (left.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
// Uncategorized/miscellaneous legacy channel goes last
return 1;
} else if (right.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
return -1;
}
return left.getId().compareTo(right.getId());
};
}

View File

@@ -44,7 +44,7 @@ public class ZenCustomRuleCallsSettings extends ZenCustomRuleSettingsBase {
@Override
protected int getPreferenceScreenResId() {
return com.android.settings.R.xml.zen_mode_calls_settings;
return com.android.settings.R.xml.zen_mode_custom_rule_calls_settings;
}
@Override

View File

@@ -37,7 +37,7 @@ public class ZenCustomRuleMessagesSettings extends ZenCustomRuleSettingsBase {
@Override
protected int getPreferenceScreenResId() {
return com.android.settings.R.xml.zen_mode_messages_settings;
return com.android.settings.R.xml.zen_mode_custom_rule_messages_settings;
}
@Override

View File

@@ -0,0 +1,228 @@
/*
* Copyright (C) 2020 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 android.app.Application;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.VisibleForTesting;
import androidx.core.text.BidiFormatter;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.notification.app.AppChannelsBypassingDndSettings;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.widget.apppreference.AppPreference;
import java.util.ArrayList;
import java.util.List;
/**
* When clicked, populates the PreferenceScreen with apps that aren't already bypassing DND. The
* user can click on these Preferences to allow notification channels from the app to bypass DND.
*/
public class ZenModeAddBypassingAppsPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin {
private static final String KEY = "zen_mode_non_bypassing_apps_list";
private static final String KEY_ADD = "zen_mode_bypassing_apps_add";
private final NotificationBackend mNotificationBackend;
@VisibleForTesting ApplicationsState mApplicationsState;
@VisibleForTesting PreferenceScreen mPreferenceScreen;
@VisibleForTesting PreferenceCategory mPreferenceCategory;
@VisibleForTesting Context mPrefContext;
private Preference mAddPreference;
private ApplicationsState.Session mAppSession;
private Fragment mHostFragment;
public ZenModeAddBypassingAppsPreferenceController(Context context, Application app,
Fragment host, NotificationBackend notificationBackend) {
this(context, app == null ? null : ApplicationsState.getInstance(app), host,
notificationBackend);
}
private ZenModeAddBypassingAppsPreferenceController(Context context, ApplicationsState appState,
Fragment host, NotificationBackend notificationBackend) {
super(context);
mNotificationBackend = notificationBackend;
mApplicationsState = appState;
mHostFragment = host;
}
@Override
public void displayPreference(PreferenceScreen screen) {
mPreferenceScreen = screen;
mAddPreference = screen.findPreference(KEY_ADD);
mAddPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
mAddPreference.setVisible(false);
if (mApplicationsState != null && mHostFragment != null) {
mAppSession = mApplicationsState.newSession(mAppSessionCallbacks,
mHostFragment.getLifecycle());
}
return true;
}
});
mPrefContext = screen.getContext();
super.displayPreference(screen);
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public String getPreferenceKey() {
return KEY;
}
/**
* Call this method to trigger the app list to refresh.
*/
public void updateAppList() {
if (mAppSession == null) {
return;
}
ApplicationsState.AppFilter filter = ApplicationsState.FILTER_ALL_ENABLED;
List<ApplicationsState.AppEntry> apps = mAppSession.rebuild(filter,
ApplicationsState.ALPHA_COMPARATOR);
updateAppList(apps);
}
@VisibleForTesting
void updateAppList(List<ApplicationsState.AppEntry> apps) {
if (apps == null) {
return;
}
if (mPreferenceCategory == null) {
mPreferenceCategory = new PreferenceCategory(mPrefContext);
mPreferenceCategory.setTitle(R.string.zen_mode_bypassing_apps_add_header);
mPreferenceScreen.addPreference(mPreferenceCategory);
}
List<Preference> appsWithNoBypassingDndNotificationChannels = new ArrayList<>();
for (ApplicationsState.AppEntry entry : apps) {
String pkg = entry.info.packageName;
mApplicationsState.ensureIcon(entry);
final int appChannels = mNotificationBackend.getChannelCount(pkg, entry.info.uid);
final int appChannelsBypassingDnd = mNotificationBackend
.getNotificationChannelsBypassingDnd(pkg, entry.info.uid).getList().size();
if (appChannelsBypassingDnd == 0 && appChannels > 0) {
final String key = ZenModeAllBypassingAppsPreferenceController.getKey(pkg);
Preference pref = mPreferenceCategory.findPreference("");
if (pref == null) {
pref = new AppPreference(mPrefContext);
pref.setKey(key);
pref.setOnPreferenceClickListener(preference -> {
Bundle args = new Bundle();
args.putString(AppInfoBase.ARG_PACKAGE_NAME, entry.info.packageName);
args.putInt(AppInfoBase.ARG_PACKAGE_UID, entry.info.uid);
new SubSettingLauncher(mContext)
.setDestination(AppChannelsBypassingDndSettings.class.getName())
.setArguments(args)
.setResultListener(mHostFragment, 0)
.setSourceMetricsCategory(
SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP)
.launch();
return true;
});
}
pref.setTitle(BidiFormatter.getInstance().unicodeWrap(entry.label));
pref.setIcon(entry.icon);
appsWithNoBypassingDndNotificationChannels.add(pref);
}
}
if (appsWithNoBypassingDndNotificationChannels.size() == 0) {
Preference pref = mPreferenceCategory.findPreference(
ZenModeAllBypassingAppsPreferenceController.KEY_NO_APPS);
if (pref == null) {
pref = new Preference(mPrefContext);
pref.setKey(ZenModeAllBypassingAppsPreferenceController.KEY_NO_APPS);
pref.setTitle(R.string.zen_mode_bypassing_apps_subtext_none);
}
mPreferenceCategory.addPreference(pref);
}
if (ZenModeAllBypassingAppsPreferenceController.hasAppListChanged(
appsWithNoBypassingDndNotificationChannels, mPreferenceCategory)) {
mPreferenceCategory.removeAll();
for (Preference prefToAdd : appsWithNoBypassingDndNotificationChannels) {
mPreferenceCategory.addPreference(prefToAdd);
}
}
}
private final ApplicationsState.Callbacks mAppSessionCallbacks =
new ApplicationsState.Callbacks() {
@Override
public void onRunningStateChanged(boolean running) {
updateAppList();
}
@Override
public void onPackageListChanged() {
updateAppList();
}
@Override
public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
updateAppList(apps);
}
@Override
public void onPackageIconChanged() {
updateAppList();
}
@Override
public void onPackageSizeChanged(String packageName) {
updateAppList();
}
@Override
public void onAllSizesComputed() { }
@Override
public void onLauncherInfoChanged() {
updateAppList();
}
@Override
public void onLoadEntriesCompleted() {
updateAppList();
}
};
}

View File

@@ -17,57 +17,59 @@
package com.android.settings.notification.zen;
import android.app.Application;
import android.app.NotificationChannel;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
import androidx.core.text.BidiFormatter;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.app.ChannelNotificationSettings;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.notification.app.AppChannelsBypassingDndSettings;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.widget.apppreference.AppPreference;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Adds a preference to the PreferenceScreen for each notification channel that can bypass DND.
*/
public class ZenModeAllBypassingAppsPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin {
public static final String KEY_NO_APPS = getKey("none");
private static final String KEY = "zen_mode_bypassing_apps_list";
private final String KEY = "zen_mode_bypassing_apps_category";
private final NotificationBackend mNotificationBackend;
@VisibleForTesting ApplicationsState mApplicationsState;
@VisibleForTesting PreferenceScreen mPreferenceScreen;
@VisibleForTesting PreferenceCategory mPreferenceCategory;
@VisibleForTesting Context mPrefContext;
private ApplicationsState.Session mAppSession;
private NotificationBackend mNotificationBackend = new NotificationBackend();
private Fragment mHostFragment;
public ZenModeAllBypassingAppsPreferenceController(Context context, Application app,
Fragment host) {
this(context, app == null ? null : ApplicationsState.getInstance(app), host);
Fragment host, NotificationBackend notificationBackend) {
this(context, app == null ? null : ApplicationsState.getInstance(app), host,
notificationBackend);
}
private ZenModeAllBypassingAppsPreferenceController(Context context, ApplicationsState appState,
Fragment host) {
Fragment host, NotificationBackend notificationBackend) {
super(context);
mNotificationBackend = notificationBackend;
mApplicationsState = appState;
mHostFragment = host;
@@ -78,9 +80,9 @@ public class ZenModeAllBypassingAppsPreferenceController extends AbstractPrefere
@Override
public void displayPreference(PreferenceScreen screen) {
mPreferenceScreen = screen;
mPrefContext = mPreferenceScreen.getContext();
updateNotificationChannelList();
mPreferenceCategory = screen.findPreference(KEY);
mPrefContext = screen.getContext();
updateAppList();
super.displayPreference(screen);
}
@@ -95,9 +97,9 @@ public class ZenModeAllBypassingAppsPreferenceController extends AbstractPrefere
}
/**
* Call this method to trigger the notification channels list to refresh.
* Call this method to trigger the app list to refresh.
*/
public void updateNotificationChannelList() {
public void updateAppList() {
if (mAppSession == null) {
return;
}
@@ -105,92 +107,123 @@ public class ZenModeAllBypassingAppsPreferenceController extends AbstractPrefere
ApplicationsState.AppFilter filter = ApplicationsState.FILTER_ALL_ENABLED;
List<ApplicationsState.AppEntry> apps = mAppSession.rebuild(filter,
ApplicationsState.ALPHA_COMPARATOR);
updateNotificationChannelList(apps);
updateAppList(apps);
}
@VisibleForTesting
void updateNotificationChannelList(List<ApplicationsState.AppEntry> apps) {
if (mPreferenceScreen == null || apps == null) {
void updateAppList(List<ApplicationsState.AppEntry> apps) {
if (mPreferenceCategory == null || apps == null) {
return;
}
boolean showEmptyState = true;
List<Preference> channelsBypassingDnd = new ArrayList<>();
for (ApplicationsState.AppEntry entry : apps) {
String pkg = entry.info.packageName;
mApplicationsState.ensureIcon(entry);
for (NotificationChannel channel : mNotificationBackend
.getNotificationChannelsBypassingDnd(pkg, entry.info.uid).getList()) {
if (!TextUtils.isEmpty(channel.getConversationId())) {
// conversation channels that bypass dnd will be shown on the People page
continue;
}
Preference pref = new AppPreference(mPrefContext);
pref.setKey(pkg + "|" + channel.getId());
pref.setTitle(BidiFormatter.getInstance().unicodeWrap(entry.label));
pref.setIcon(entry.icon);
pref.setSummary(BidiFormatter.getInstance().unicodeWrap(channel.getName()));
List<Preference> appsBypassingDnd = new ArrayList<>();
for (ApplicationsState.AppEntry app : apps) {
String pkg = app.info.packageName;
mApplicationsState.ensureIcon(app);
final int appChannels = mNotificationBackend.getChannelCount(pkg, app.info.uid);
final int appChannelsBypassingDnd = mNotificationBackend
.getNotificationChannelsBypassingDnd(pkg, app.info.uid).getList().size();
if (appChannelsBypassingDnd > 0) {
final String key = getKey(pkg);
// re-use previously created preference when possible
Preference pref = mPreferenceCategory.findPreference(key);
if (pref == null) {
pref = new AppPreference(mPrefContext);
pref.setKey(key);
pref.setOnPreferenceClickListener(preference -> {
Bundle args = new Bundle();
args.putString(AppInfoBase.ARG_PACKAGE_NAME, entry.info.packageName);
args.putInt(AppInfoBase.ARG_PACKAGE_UID, entry.info.uid);
args.putString(Settings.EXTRA_CHANNEL_ID, channel.getId());
args.putString(AppInfoBase.ARG_PACKAGE_NAME, app.info.packageName);
args.putInt(AppInfoBase.ARG_PACKAGE_UID, app.info.uid);
new SubSettingLauncher(mContext)
.setDestination(ChannelNotificationSettings.class.getName())
.setDestination(AppChannelsBypassingDndSettings.class.getName())
.setArguments(args)
.setTitleRes(R.string.notification_channel_title)
.setResultListener(mHostFragment, 0)
.setSourceMetricsCategory(
SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP)
.launch();
return true;
});
channelsBypassingDnd.add(pref);
showEmptyState = false;
}
pref.setTitle(BidiFormatter.getInstance().unicodeWrap(app.label));
pref.setIcon(app.icon);
if (appChannels > appChannelsBypassingDnd) {
pref.setSummary(R.string.zen_mode_bypassing_apps_summary_some);
} else {
pref.setSummary(R.string.zen_mode_bypassing_apps_summary_all);
}
mPreferenceScreen.removeAll();
if (channelsBypassingDnd.size() > 0) {
for (Preference prefToAdd : channelsBypassingDnd) {
mPreferenceScreen.addPreference(prefToAdd);
appsBypassingDnd.add(pref);
}
}
if (showEmptyState) {
Preference pref = new Preference(mPrefContext);
pref.setTitle(R.string.zen_mode_bypassing_apps_subtext_none);
mPreferenceScreen.addPreference(pref);
if (appsBypassingDnd.size() == 0) {
Preference pref = mPreferenceCategory.findPreference(KEY_NO_APPS);
if (pref == null) {
pref = new Preference(mPrefContext);
pref.setKey(KEY_NO_APPS);
pref.setTitle(R.string.zen_mode_bypassing_apps_none);
}
appsBypassingDnd.add(pref);
}
if (hasAppListChanged(appsBypassingDnd, mPreferenceCategory)) {
mPreferenceCategory.removeAll();
for (Preference prefToAdd : appsBypassingDnd) {
mPreferenceCategory.addPreference(prefToAdd);
}
}
}
static boolean hasAppListChanged(List<Preference> newAppPreferences,
PreferenceCategory preferenceCategory) {
if (newAppPreferences.size() != preferenceCategory.getPreferenceCount()) {
return true;
}
for (int i = 0; i < newAppPreferences.size(); i++) {
Preference newAppPref = newAppPreferences.get(i);
Preference pref = preferenceCategory.getPreference(i);
if (!Objects.equals(newAppPref.getKey(), pref.getKey())) {
return true;
}
}
return false;
}
/**
* Create a unique key to idenfity an AppPreference
*/
static String getKey(String pkg) {
return pkg;
}
private final ApplicationsState.Callbacks mAppSessionCallbacks =
new ApplicationsState.Callbacks() {
@Override
public void onRunningStateChanged(boolean running) {
updateNotificationChannelList();
updateAppList();
}
@Override
public void onPackageListChanged() {
updateNotificationChannelList();
updateAppList();
}
@Override
public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
updateNotificationChannelList(apps);
updateAppList(apps);
}
@Override
public void onPackageIconChanged() {
updateNotificationChannelList();
updateAppList();
}
@Override
public void onPackageSizeChanged(String packageName) {
updateNotificationChannelList();
updateAppList();
}
@Override
@@ -198,12 +231,12 @@ public class ZenModeAllBypassingAppsPreferenceController extends AbstractPrefere
@Override
public void onLauncherInfoChanged() {
updateNotificationChannelList();
updateAppList();
}
@Override
public void onLoadEntriesCompleted() {
updateNotificationChannelList();
updateAppList();
}
};
}

View File

@@ -287,44 +287,15 @@ public class ZenModeBackend {
protected int getAlarmsTotalSilencePeopleSummary(int category) {
if (category == NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) {
return R.string.zen_mode_from_none_messages;
return R.string.zen_mode_from_none;
} else if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CALLS){
return R.string.zen_mode_from_none_calls;
return R.string.zen_mode_from_none;
} else if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS) {
return R.string.zen_mode_from_no_conversations;
}
return R.string.zen_mode_from_none;
}
protected int getContactsSummary(int category) {
int contactType = -1;
if (category == NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) {
if (isPriorityCategoryEnabled(category)) {
contactType = getPriorityMessageSenders();
}
} else if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CALLS) {
if (isPriorityCategoryEnabled(category)) {
contactType = getPriorityCallSenders();
}
}
switch (contactType) {
case NotificationManager.Policy.PRIORITY_SENDERS_ANY:
return R.string.zen_mode_from_anyone;
case NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS:
return R.string.zen_mode_from_contacts;
case NotificationManager.Policy.PRIORITY_SENDERS_STARRED:
return R.string.zen_mode_from_starred;
case SOURCE_NONE:
default:
if (category == NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) {
return R.string.zen_mode_from_none_messages;
} else {
return R.string.zen_mode_from_none_calls;
}
}
}
protected int getConversationSummary() {
int conversationType = getPriorityConversationSenders();
@@ -366,7 +337,7 @@ public class ZenModeBackend {
return R.string.zen_mode_from_starred;
case ZenPolicy.PEOPLE_TYPE_NONE:
default:
return R.string.zen_mode_from_none_messages;
return R.string.zen_mode_from_none;
}
}
@@ -384,20 +355,6 @@ public class ZenModeBackend {
}
}
protected static int getSettingFromPrefKey(String key) {
switch (key) {
case ZEN_MODE_FROM_ANYONE:
return NotificationManager.Policy.PRIORITY_SENDERS_ANY;
case ZEN_MODE_FROM_CONTACTS:
return NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS;
case ZEN_MODE_FROM_STARRED:
return NotificationManager.Policy.PRIORITY_SENDERS_STARRED;
case ZEN_MODE_FROM_NONE:
default:
return SOURCE_NONE;
}
}
public boolean removeZenRule(String ruleId) {
return NotificationManager.from(mContext).removeAutomaticZenRule(ruleId);
}

View File

@@ -6,6 +6,7 @@ import android.content.Context;
import android.icu.text.ListFormatter;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
import androidx.core.text.BidiFormatter;
import androidx.fragment.app.Fragment;
@@ -21,6 +22,7 @@ import com.android.settingslib.core.lifecycle.Lifecycle;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* Controls the summary for preference found at:
@@ -102,7 +104,7 @@ public class ZenModeBypassingAppsPreferenceController extends AbstractZenModePre
return;
}
List<String> appsBypassingDnd = new ArrayList<>();
Set<String> appsBypassingDnd = new ArraySet<>();
for (ApplicationsState.AppEntry entry : apps) {
String pkg = entry.info.packageName;
for (NotificationChannel channel : mNotificationBackend
@@ -116,7 +118,8 @@ public class ZenModeBypassingAppsPreferenceController extends AbstractZenModePre
}
}
if (appsBypassingDnd.size() == 0) {
final int numAppsBypassingDnd = appsBypassingDnd.size();
if (numAppsBypassingDnd == 0) {
mSummary = mContext.getResources().getString(
R.string.zen_mode_bypassing_apps_subtext_none);
refreshSummary(mPreference);
@@ -124,18 +127,20 @@ public class ZenModeBypassingAppsPreferenceController extends AbstractZenModePre
}
List<String> displayAppsBypassing = new ArrayList<>();
if (appsBypassingDnd.size() <= 2) {
displayAppsBypassing = appsBypassingDnd;
if (numAppsBypassingDnd <= 2) {
displayAppsBypassing.addAll(appsBypassingDnd);
} else {
displayAppsBypassing.add(appsBypassingDnd.get(0));
displayAppsBypassing.add(appsBypassingDnd.get(1));
String[] appsBypassingDndArr =
appsBypassingDnd.toArray(new String[numAppsBypassingDnd]);
displayAppsBypassing.add(appsBypassingDndArr[0]);
displayAppsBypassing.add(appsBypassingDndArr[1]);
displayAppsBypassing.add(mContext.getResources().getString(
R.string.zen_mode_apps_bypassing_list_count,
appsBypassingDnd.size() - 2));
numAppsBypassingDnd - 2));
}
mSummary = mContext.getResources().getQuantityString(
R.plurals.zen_mode_bypassing_apps_subtext,
appsBypassingDnd.size(),
numAppsBypassingDnd,
ListFormatter.getInstance().format(displayAppsBypassing));
refreshSummary(mPreference);
}

View File

@@ -24,6 +24,7 @@ import android.content.Context;
import androidx.fragment.app.Fragment;
import com.android.settings.R;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.Indexable;
@@ -46,13 +47,16 @@ public class ZenModeBypassingAppsSettings extends ZenModeSettingsBase implements
} else {
app = null;
}
return buildPreferenceControllers(context, app, this);
return buildPreferenceControllers(context, app, this, new NotificationBackend());
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
Application app, Fragment host) {
Application app, Fragment host, NotificationBackend notificationBackend) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new ZenModeAllBypassingAppsPreferenceController(context, app, host));
controllers.add(new ZenModeAllBypassingAppsPreferenceController(context, app, host,
notificationBackend));
controllers.add(new ZenModeAddBypassingAppsPreferenceController(context, app, host,
notificationBackend));
return controllers;
}
@@ -80,7 +84,7 @@ public class ZenModeBypassingAppsSettings extends ZenModeSettingsBase implements
@Override
public List<AbstractPreferenceController> createPreferenceControllers(
Context context) {
return buildPreferenceControllers(context, null, null);
return buildPreferenceControllers(context, null, null, null);
}
};
}

View File

@@ -30,7 +30,7 @@ import java.util.ArrayList;
import java.util.List;
/**
* Settings > Sound > Do Not Disturb > Conversationss
* Settings > Sound > Do Not Disturb > Conversations
*/
@SearchIndexable
public class ZenModeConversationsSettings extends ZenModeSettingsBase {

View File

@@ -1,108 +0,0 @@
/*
* Copyright (C) 2018 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 android.app.NotificationManager;
import android.content.Context;
import android.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settingslib.core.lifecycle.Lifecycle;
public class ZenModePriorityCallsPreferenceController extends AbstractZenModePreferenceController
implements Preference.OnPreferenceChangeListener {
protected static final String KEY = "zen_mode_calls";
private final ZenModeBackend mBackend;
private ListPreference mPreference;
private final String[] mListValues;
public ZenModePriorityCallsPreferenceController(Context context, Lifecycle lifecycle) {
super(context, KEY, lifecycle);
mBackend = ZenModeBackend.getInstance(context);
mListValues = context.getResources().getStringArray(R.array.zen_mode_contacts_values);
}
@Override
public String getPreferenceKey() {
return KEY;
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(KEY);
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
updateFromContactsValue(preference);
}
@Override
public boolean onPreferenceChange(Preference preference, Object selectedContactsFrom) {
mBackend.saveSenders(NotificationManager.Policy.PRIORITY_CATEGORY_CALLS,
ZenModeBackend.getSettingFromPrefKey(selectedContactsFrom.toString()));
updateFromContactsValue(preference);
return true;
}
private void updateFromContactsValue(Preference preference) {
mPreference = (ListPreference) preference;
switch (getZenMode()) {
case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS:
case Settings.Global.ZEN_MODE_ALARMS:
mPreference.setEnabled(false);
mPreference.setValue(ZenModeBackend.ZEN_MODE_FROM_NONE);
mPreference.setSummary(mBackend.getAlarmsTotalSilencePeopleSummary(
NotificationManager.Policy.PRIORITY_CATEGORY_CALLS));
break;
default:
preference.setEnabled(true);
preference.setSummary(mBackend.getContactsSummary(
NotificationManager.Policy.PRIORITY_CATEGORY_CALLS));
final String currentVal = ZenModeBackend.getKeyFromSetting(
mBackend.getPriorityCallSenders());
mPreference.setValue(mListValues[getIndexOfSendersValue(currentVal)]);
}
}
@VisibleForTesting
protected int getIndexOfSendersValue(String currentVal) {
int index = 3; // defaults to "none" based on R.array.zen_mode_contacts_values
for (int i = 0; i < mListValues.length; i++) {
if (TextUtils.equals(currentVal, mListValues[i])) {
return i;
}
}
return index;
}
}

View File

@@ -16,11 +16,16 @@
package com.android.settings.notification.zen;
import static com.android.settings.widget.RadioButtonPreferenceWithExtraWidget.EXTRA_WIDGET_VISIBILITY_GONE;
import static com.android.settings.widget.RadioButtonPreferenceWithExtraWidget.EXTRA_WIDGET_VISIBILITY_SETTING;
import android.app.NotificationManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.AsyncTask;
import android.service.notification.ConversationChannelWrapper;
import android.view.View;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
@@ -28,7 +33,10 @@ import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.notification.app.ConversationListSettings;
import com.android.settings.widget.RadioButtonPreferenceWithExtraWidget;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.widget.RadioButtonPreference;
@@ -51,6 +59,7 @@ public class ZenModePriorityConversationsPreferenceController
private int mNumConversations = UNSET;
private PreferenceCategory mPreferenceCategory;
private List<RadioButtonPreference> mRadioButtonPreferences = new ArrayList<>();
private Context mPreferenceScreenContext;
public ZenModePriorityConversationsPreferenceController(Context context, String key,
Lifecycle lifecycle, NotificationBackend notificationBackend) {
@@ -60,6 +69,7 @@ public class ZenModePriorityConversationsPreferenceController
@Override
public void displayPreference(PreferenceScreen screen) {
mPreferenceScreenContext = screen.getContext();
mPreferenceCategory = screen.findPreference(getPreferenceKey());
if (mPreferenceCategory.findPreference(KEY_ALL) == null) {
makeRadioPreference(KEY_ALL, R.string.zen_mode_from_all_conversations);
@@ -125,7 +135,7 @@ public class ZenModePriorityConversationsPreferenceController
R.string.zen_mode_conversations_count_none);
} else {
return mContext.getResources().getQuantityString(
R.plurals.zen_mode_conversations_count, numConversations);
R.plurals.zen_mode_conversations_count, numConversations, numConversations);
}
}
@@ -136,14 +146,27 @@ public class ZenModePriorityConversationsPreferenceController
protected Void doInBackground(Void... unused) {
ParceledListSlice<ConversationChannelWrapper> allConversations =
mNotificationBackend.getConversations(false);
int numConversations = 0;
if (allConversations != null) {
mNumConversations = allConversations.getList().size();
for (ConversationChannelWrapper conversation : allConversations.getList()) {
if (!conversation.getNotificationChannel().isDemoted()) {
numConversations++;
}
ParceledListSlice<ConversationChannelWrapper> importantConversations =
}
}
mNumConversations = numConversations;
ParceledListSlice<ConversationChannelWrapper> impConversations =
mNotificationBackend.getConversations(true);
if (importantConversations != null) {
mNumImportantConversations = importantConversations.getList().size();
int numImportantConversations = 0;
if (impConversations != null) {
for (ConversationChannelWrapper conversation : impConversations.getList()) {
if (!conversation.getNotificationChannel().isDemoted()) {
numImportantConversations++;
}
}
}
mNumImportantConversations = numImportantConversations;
return null;
}
@@ -158,7 +181,14 @@ public class ZenModePriorityConversationsPreferenceController
}
private RadioButtonPreference makeRadioPreference(String key, int titleId) {
RadioButtonPreference pref = new RadioButtonPreference(mPreferenceCategory.getContext());
RadioButtonPreferenceWithExtraWidget pref =
new RadioButtonPreferenceWithExtraWidget(mPreferenceCategory.getContext());
if (KEY_ALL.equals(key) || KEY_IMPORTANT.equals(key)) {
pref.setExtraWidgetOnClickListener(mConversationSettingsWidgetClickListener);
pref.setExtraWidgetVisibility(EXTRA_WIDGET_VISIBILITY_SETTING);
} else {
pref.setExtraWidgetVisibility(EXTRA_WIDGET_VISIBILITY_GONE);
}
pref.setKey(key);
pref.setTitle(titleId);
pref.setOnClickListener(mRadioButtonClickListener);
@@ -167,6 +197,17 @@ public class ZenModePriorityConversationsPreferenceController
return pref;
}
private View.OnClickListener mConversationSettingsWidgetClickListener =
new View.OnClickListener() {
@Override
public void onClick(View v) {
new SubSettingLauncher(mPreferenceScreenContext)
.setDestination(ConversationListSettings.class.getName())
.setSourceMetricsCategory(SettingsEnums.DND_CONVERSATIONS)
.launch();
}
};
private RadioButtonPreference.OnClickListener mRadioButtonClickListener =
new RadioButtonPreference.OnClickListener() {
@Override

View File

@@ -153,7 +153,7 @@ public class ZenModeSettings extends ZenModeSettingsBase {
String getCallsSettingSummary(Policy policy) {
List<String> enabledCategories = getEnabledCategories(policy,
category -> PRIORITY_CATEGORY_CALLS == category
|| PRIORITY_CATEGORY_REPEAT_CALLERS == category, false);
|| PRIORITY_CATEGORY_REPEAT_CALLERS == category, true);
int numCategories = enabledCategories.size();
if (numCategories == 0) {
return mContext.getString(R.string.zen_mode_from_none_calls);
@@ -303,10 +303,19 @@ public class ZenModeSettings extends ZenModeSettingsBase {
}
} else if (category == Policy.PRIORITY_CATEGORY_CALLS) {
if (policy.priorityCallSenders == Policy.PRIORITY_SENDERS_ANY) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_from_anyone);
}
return mContext.getString(R.string.zen_mode_all_callers);
} else if (policy.priorityCallSenders == Policy.PRIORITY_SENDERS_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 == Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) {

View File

@@ -16,19 +16,8 @@
package com.android.settings.notification.app;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -37,24 +26,17 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.os.Bundle;
import android.os.UserManager;
import android.provider.Settings;
import android.service.notification.ConversationChannelWrapper;
import android.view.View;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.RestrictedLockUtils;
import org.junit.Before;
import org.junit.Test;
@@ -67,7 +49,6 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowApplication;
import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
public class ConversationListPreferenceControllerTest {
@@ -116,8 +97,6 @@ public class ConversationListPreferenceControllerTest {
list.add(ccw);
mController.populateList(list, outerContainer);
verify(outerContainer).setVisible(true);
verify(outerContainer, times(1)).addPreference(any());
}

View File

@@ -0,0 +1,164 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification.zen;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.NotificationChannel;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.ParceledListSlice;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.applications.ApplicationsState;
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 java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
public class ZenModeAddBypassingAppsPreferenceControllerTest {
@Mock
private NotificationBackend mBackend;
@Mock
private PreferenceCategory mPreferenceCategory;
@Mock
private ApplicationsState mApplicationState;
private ZenModeAddBypassingAppsPreferenceController mController;
private Context mContext;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mController = new ZenModeAddBypassingAppsPreferenceController(
mContext, null, mock(Fragment.class), mBackend);
mController.mPreferenceCategory = mPreferenceCategory;
mController.mApplicationsState = mApplicationState;
mController.mPrefContext = mContext;
}
@Test
public void testIsAvailable() {
assertThat(mController.isAvailable()).isTrue();
}
@Test
public void testUpdateAppList() {
// GIVEN there's an app with bypassing channels, app without any channels, and then an app
// with notification channels but none that can bypass DND
ApplicationsState.AppEntry appWithBypassingChannels =
mock(ApplicationsState.AppEntry.class);
appWithBypassingChannels.info = new ApplicationInfo();
appWithBypassingChannels.info.packageName = "appWithBypassingChannels";
appWithBypassingChannels.info.uid = 0;
when(mBackend.getNotificationChannelsBypassingDnd(
appWithBypassingChannels.info.packageName,
appWithBypassingChannels.info.uid))
.thenReturn(new ParceledListSlice<>(
Arrays.asList(mock(NotificationChannel.class))));
when(mBackend.getChannelCount(
appWithBypassingChannels.info.packageName,
appWithBypassingChannels.info.uid))
.thenReturn(5);
ApplicationsState.AppEntry appWithoutChannels = mock(ApplicationsState.AppEntry.class);
appWithoutChannels.info = new ApplicationInfo();
appWithoutChannels.info.packageName = "appWithoutChannels";
appWithoutChannels.info.uid = 0;
when(mBackend.getChannelCount(
appWithoutChannels.info.packageName,
appWithoutChannels.info.uid))
.thenReturn(0);
when(mBackend.getNotificationChannelsBypassingDnd(
appWithoutChannels.info.packageName,
appWithoutChannels.info.uid))
.thenReturn(new ParceledListSlice<>(new ArrayList<>()));
ApplicationsState.AppEntry appWithChannelsNoneBypassing =
mock(ApplicationsState.AppEntry.class);
appWithChannelsNoneBypassing.info = new ApplicationInfo();
appWithChannelsNoneBypassing.info.packageName = "appWithChannelsNoneBypassing";
appWithChannelsNoneBypassing.info.uid = 0;
when(mBackend.getChannelCount(
appWithChannelsNoneBypassing.info.packageName,
appWithChannelsNoneBypassing.info.uid))
.thenReturn(5);
when(mBackend.getNotificationChannelsBypassingDnd(
appWithChannelsNoneBypassing.info.packageName,
appWithChannelsNoneBypassing.info.uid))
.thenReturn(new ParceledListSlice<>(new ArrayList<>()));
List<ApplicationsState.AppEntry> appEntries = new ArrayList<>();
appEntries.add(appWithBypassingChannels);
appEntries.add(appWithoutChannels);
appEntries.add(appWithChannelsNoneBypassing);
// WHEN the controller updates the app list with the app entries
mController.updateAppList(appEntries);
// THEN only the appWithChannelsNoneBypassing makes it to the app list
ArgumentCaptor<Preference> prefCaptor = ArgumentCaptor.forClass(Preference.class);
verify(mPreferenceCategory).addPreference(prefCaptor.capture());
Preference pref = prefCaptor.getValue();
assertThat(pref.getKey()).isEqualTo(
ZenModeAllBypassingAppsPreferenceController.getKey(
appWithChannelsNoneBypassing.info.packageName));
}
@Test
public void testUpdateAppList_nullApps() {
mController.updateAppList(null);
verify(mPreferenceCategory, never()).addPreference(any());
}
@Test
public void testUpdateAppList_emptyAppList() {
// WHEN there are no apps
mController.updateAppList(new ArrayList<>());
// THEN only the appWithChannelsNoneBypassing makes it to the app list
ArgumentCaptor<Preference> prefCaptor = ArgumentCaptor.forClass(Preference.class);
verify(mPreferenceCategory).addPreference(prefCaptor.capture());
Preference pref = prefCaptor.getValue();
assertThat(pref.getKey()).isEqualTo(
ZenModeAllBypassingAppsPreferenceController.KEY_NO_APPS);
}
}

View File

@@ -19,6 +19,8 @@ package com.android.settings.notification.zen;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -30,25 +32,25 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.ParceledListSlice;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.notification.zen.ZenModeAllBypassingAppsPreferenceController;
import com.android.settingslib.applications.ApplicationsState;
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.ArrayList;
import java.util.List;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceScreen;
@RunWith(RobolectricTestRunner.class)
public class ZenModeAllBypassingAppsPreferenceControllerTest {
private ZenModeAllBypassingAppsPreferenceController mController;
@@ -57,7 +59,7 @@ public class ZenModeAllBypassingAppsPreferenceControllerTest {
@Mock
private NotificationBackend mBackend;
@Mock
private PreferenceScreen mPreferenceScreen;
private PreferenceCategory mPreferenceCategory;
@Mock
private ApplicationsState mApplicationState;
@@ -67,11 +69,10 @@ public class ZenModeAllBypassingAppsPreferenceControllerTest {
mContext = RuntimeEnvironment.application;
mController = new ZenModeAllBypassingAppsPreferenceController(
mContext, null, mock(Fragment.class));
mController.mPreferenceScreen = mPreferenceScreen;
mContext, null, mock(Fragment.class), mBackend);
mController.mPreferenceCategory = mPreferenceCategory;
mController.mApplicationsState = mApplicationState;
mController.mPrefContext = mContext;
ReflectionHelpers.setField(mController, "mNotificationBackend", mBackend);
}
@Test
@@ -80,36 +81,49 @@ public class ZenModeAllBypassingAppsPreferenceControllerTest {
}
@Test
public void testUpdateNotificationChannelList() {
ApplicationsState.AppEntry entry = mock(ApplicationsState.AppEntry.class);
entry.info = new ApplicationInfo();
entry.info.packageName = "test";
entry.info.uid = 0;
public void testUpdateAppList() {
// WHEN there's two apps with notification channels that bypass DND
ApplicationsState.AppEntry entry1 = mock(ApplicationsState.AppEntry.class);
entry1.info = new ApplicationInfo();
entry1.info.packageName = "test";
entry1.info.uid = 0;
ApplicationsState.AppEntry entry2 = mock(ApplicationsState.AppEntry.class);
entry2.info = new ApplicationInfo();
entry2.info.packageName = "test2";
entry2.info.uid = 0;
List<ApplicationsState.AppEntry> appEntries = new ArrayList<>();
appEntries.add(entry);
appEntries.add(entry1);
appEntries.add(entry2);
List<NotificationChannel> channelsBypassing = new ArrayList<>();
channelsBypassing.add(mock(NotificationChannel.class));
channelsBypassing.add(mock(NotificationChannel.class));
channelsBypassing.add(mock(NotificationChannel.class));
when(mBackend.getNotificationChannelsBypassingDnd(anyString(),
anyInt())).thenReturn(new ParceledListSlice<>(channelsBypassing));
when(mBackend.getNotificationChannelsBypassingDnd(entry.info.packageName,
entry.info.uid)).thenReturn(new ParceledListSlice<>(channelsBypassing));
mController.updateNotificationChannelList(appEntries);
verify(mPreferenceScreen, times(3)).addPreference(any());
// THEN there's are two preferences
mController.updateAppList(appEntries);
verify(mPreferenceCategory, times(2)).addPreference(any());
}
@Test
public void testUpdateNotificationChannelList_nullChannels() {
mController.updateNotificationChannelList(null);
verify(mPreferenceScreen, never()).addPreference(any());
public void testUpdateAppList_nullApps() {
mController.updateAppList(null);
verify(mPreferenceCategory, never()).addPreference(any());
}
@Test
public void testUpdateNotificationChannelList_emptyChannelsList() {
mController.updateNotificationChannelList(new ArrayList<>());
verify(mPreferenceScreen, never()).addPreference(any());
public void testUpdateAppList_emptyAppList() {
// WHEN there are no apps
mController.updateAppList(new ArrayList<>());
// THEN only the appWithChannelsNoneBypassing makes it to the app list
ArgumentCaptor<Preference> prefCaptor = ArgumentCaptor.forClass(Preference.class);
verify(mPreferenceCategory).addPreference(prefCaptor.capture());
Preference pref = prefCaptor.getValue();
assertThat(pref.getKey()).isEqualTo(
ZenModeAllBypassingAppsPreferenceController.KEY_NO_APPS);
}
}

View File

@@ -1,178 +0,0 @@
/*
* Copyright (C) 2018 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.provider.Settings.Global.ZEN_MODE;
import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
import static org.mockito.Mockito.mock;
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 android.provider.Settings;
import androidx.preference.ListPreference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settingslib.core.lifecycle.Lifecycle;
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.shadows.ShadowApplication;
import org.robolectric.util.ReflectionHelpers;
@RunWith(RobolectricTestRunner.class)
public class ZenModePriorityCallsPreferenceControllerTest {
private ZenModePriorityCallsPreferenceController mController;
@Mock
private ZenModeBackend mBackend;
@Mock
private NotificationManager mNotificationManager;
@Mock
private ListPreference mockPref;
@Mock
private NotificationManager.Policy mPolicy;
@Mock
private PreferenceScreen mPreferenceScreen;
private ContentResolver mContentResolver;
private Context mContext;
/**
* Array Values Key
* 0: anyone
* 1: contacts
* 2: starred
* 3: none
*/
private String[] mValues;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
ShadowApplication shadowApplication = ShadowApplication.getInstance();
shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager);
mContext = RuntimeEnvironment.application;
mValues = mContext.getResources().getStringArray(R.array.zen_mode_contacts_values);
mContentResolver = RuntimeEnvironment.application.getContentResolver();
when(mNotificationManager.getNotificationPolicy()).thenReturn(mPolicy);
when(mBackend.getPriorityCallSenders())
.thenReturn(NotificationManager.Policy.PRIORITY_SENDERS_STARRED);
when(mBackend.getAlarmsTotalSilencePeopleSummary(
NotificationManager.Policy.PRIORITY_CATEGORY_CALLS)).thenCallRealMethod();
when(mBackend.getContactsSummary(NotificationManager.Policy.PRIORITY_CATEGORY_CALLS))
.thenCallRealMethod();
mController = new ZenModePriorityCallsPreferenceController(mContext, mock(Lifecycle.class));
ReflectionHelpers.setField(mController, "mBackend", mBackend);
when(mPreferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn(mockPref);
mController.displayPreference(mPreferenceScreen);
}
@Test
public void updateState_TotalSilence() {
Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_NO_INTERRUPTIONS);
when(mBackend.isPriorityCategoryEnabled(
NotificationManager.Policy.PRIORITY_CATEGORY_CALLS))
.thenReturn(false);
final ListPreference mockPref = mock(ListPreference.class);
mController.updateState(mockPref);
verify(mockPref).setEnabled(false);
verify(mockPref).setSummary(R.string.zen_mode_from_none_calls);
}
@Test
public void updateState_AlarmsOnly() {
Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_ALARMS);
final ListPreference mockPref = mock(ListPreference.class);
mController.updateState(mockPref);
verify(mockPref).setEnabled(false);
verify(mockPref).setSummary(R.string.zen_mode_from_none_calls);
}
@Test
public void updateState_Priority() {
Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
when(mBackend.isPriorityCategoryEnabled(
NotificationManager.Policy.PRIORITY_CATEGORY_CALLS))
.thenReturn(true);
mController.updateState(mockPref);
verify(mockPref).setEnabled(true);
verify(mockPref).setSummary(R.string.zen_mode_from_starred);
}
@Test
public void onPreferenceChange_setSelectedContacts_any() {
Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
when(mBackend.getPriorityCallSenders()).thenReturn(
NotificationManager.Policy.PRIORITY_SENDERS_ANY);
mController.updateState(mockPref);
verify(mockPref).setValue(mValues[mController.getIndexOfSendersValue(
ZenModeBackend.ZEN_MODE_FROM_ANYONE)]);
}
@Test
public void onPreferenceChange_setSelectedContacts_none() {
Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
when(mBackend.getPriorityCallSenders()).thenReturn(ZenModeBackend.SOURCE_NONE);
mController.updateState(mockPref);
verify(mockPref).setValue(mValues[mController.getIndexOfSendersValue(
ZenModeBackend.ZEN_MODE_FROM_NONE)]);
}
@Test
public void onPreferenceChange_setSelectedContacts_starred() {
Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
when(mBackend.getPriorityCallSenders()).thenReturn(
NotificationManager.Policy.PRIORITY_SENDERS_STARRED);
mController.updateState(mockPref);
verify(mockPref).setValue(mValues[mController.getIndexOfSendersValue(
ZenModeBackend.ZEN_MODE_FROM_STARRED)]);
}
@Test
public void onPreferenceChange_setSelectedContacts_contacts() {
Settings.Global.putInt(mContentResolver, ZEN_MODE, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
when(mBackend.getPriorityCallSenders()).thenReturn(
NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS);
mController.updateState(mockPref);
verify(mockPref).setValue(mValues[mController.getIndexOfSendersValue(
ZenModeBackend.ZEN_MODE_FROM_CONTACTS)]);
}
}

View File

@@ -79,13 +79,13 @@ public class ZenModeSettingsTest {
public void testGetCallsSettingSummary_contacts() {
Policy policy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | Policy.PRIORITY_CATEGORY_CALLS,
Policy.PRIORITY_SENDERS_CONTACTS, 0, 0);
assertThat(mBuilder.getCallsSettingSummary(policy)).isEqualTo("Allow from contacts");
assertThat(mBuilder.getCallsSettingSummary(policy)).isEqualTo("Contacts");
}
@Test
public void testGetCallsSettingSummary_repeatCallers() {
Policy policy = new Policy(Policy.PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0);
assertThat(mBuilder.getCallsSettingSummary(policy)).isEqualTo("Allow from repeat callers");
assertThat(mBuilder.getCallsSettingSummary(policy)).isEqualTo("Repeat callers");
}
@Test
@@ -94,7 +94,7 @@ public class ZenModeSettingsTest {
Policy.PRIORITY_CATEGORY_REPEAT_CALLERS | Policy.PRIORITY_CATEGORY_CALLS,
Policy.PRIORITY_SENDERS_STARRED, 0, 0);
assertThat(mBuilder.getCallsSettingSummary(policy))
.isEqualTo("Allow from starred contacts and repeat callers");
.isEqualTo("Starred contacts and repeat callers");
}
@Test