From f707950ee7afb2c1da6628ba2b7c34b1ba0aecd9 Mon Sep 17 00:00:00 2001 From: Beverly Date: Tue, 24 Mar 2020 08:54:30 -0400 Subject: [PATCH] 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 --- res/values/strings.xml | 26 +- .../app_channels_bypassing_dnd_settings.xml | 29 +++ res/xml/conversation_list_settings.xml | 9 +- res/xml/zen_mode_bypassing_apps.xml | 19 +- .../zen_mode_custom_rule_calls_settings.xml | 45 ++++ ...zen_mode_custom_rule_messages_settings.xml | 40 +++ ...nnelsBypassingDndPreferenceController.java | 227 +++++++++++++++++ .../app/AppChannelsBypassingDndSettings.java | 80 ++++++ .../app/ChannelListPreferenceController.java | 46 +--- .../app/NotificationPreferenceController.java | 28 +++ .../zen/ZenCustomRuleCallsSettings.java | 2 +- .../zen/ZenCustomRuleMessagesSettings.java | 2 +- ...eAddBypassingAppsPreferenceController.java | 228 ++++++++++++++++++ ...eAllBypassingAppsPreferenceController.java | 175 ++++++++------ .../notification/zen/ZenModeBackend.java | 49 +--- ...ModeBypassingAppsPreferenceController.java | 21 +- .../zen/ZenModeBypassingAppsSettings.java | 12 +- .../zen/ZenModeConversationsSettings.java | 2 +- ...ModePriorityCallsPreferenceController.java | 108 --------- ...rityConversationsPreferenceController.java | 53 +++- .../notification/zen/ZenModeSettings.java | 11 +- ...versationListPreferenceControllerTest.java | 21 -- ...BypassingAppsPreferenceControllerTest.java | 164 +++++++++++++ ...BypassingAppsPreferenceControllerTest.java | 70 +++--- ...PriorityCallsPreferenceControllerTest.java | 178 -------------- .../notification/zen/ZenModeSettingsTest.java | 6 +- 26 files changed, 1128 insertions(+), 523 deletions(-) create mode 100644 res/xml/app_channels_bypassing_dnd_settings.xml create mode 100644 res/xml/zen_mode_custom_rule_calls_settings.xml create mode 100644 res/xml/zen_mode_custom_rule_messages_settings.xml create mode 100644 src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java create mode 100644 src/com/android/settings/notification/app/AppChannelsBypassingDndSettings.java create mode 100644 src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceController.java delete mode 100644 src/com/android/settings/notification/zen/ZenModePriorityCallsPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceControllerTest.java delete mode 100644 tests/robotests/src/com/android/settings/notification/zen/ZenModePriorityCallsPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 46d75e82680..fd869d425e4 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6908,7 +6908,8 @@ You can add up to %1$d users - + + Only the tablet\u2019s owner can manage users. Only the phone\u2019s owner can manage users. @@ -8973,8 +8974,22 @@ Allow apps to override Apps that can interrupt + + Select more apps + + No apps selected No apps can interrupt + + Add apps + + All notifications + + Some notifications + + Selected people can still reach you, even if you don\u2019t allow apps to interrupt %s can interrupt @@ -8989,6 +9004,11 @@ All notifications Some notifications + + Notifications that can interrupt + + Allow all notifications @@ -9022,9 +9042,9 @@ repeat callers - Allow from %1$s + %1$s - Allow from %1$s and %2$s + %1$s and %2$s If the same person calls a second time within a %d minute period diff --git a/res/xml/app_channels_bypassing_dnd_settings.xml b/res/xml/app_channels_bypassing_dnd_settings.xml new file mode 100644 index 00000000000..4f6291dc447 --- /dev/null +++ b/res/xml/app_channels_bypassing_dnd_settings.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/conversation_list_settings.xml b/res/xml/conversation_list_settings.xml index 90a7279cf17..e18b4e57a42 100644 --- a/res/xml/conversation_list_settings.xml +++ b/res/xml/conversation_list_settings.xml @@ -14,10 +14,11 @@ limitations under the License. --> - + + xmlns:settings="http://schemas.android.com/apk/res-auto" + android:title="@string/zen_mode_bypassing_apps_title"> + + + + + + + + + diff --git a/res/xml/zen_mode_custom_rule_calls_settings.xml b/res/xml/zen_mode_custom_rule_calls_settings.xml new file mode 100644 index 00000000000..4dca2adf36f --- /dev/null +++ b/res/xml/zen_mode_custom_rule_calls_settings.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + diff --git a/res/xml/zen_mode_custom_rule_messages_settings.xml b/res/xml/zen_mode_custom_rule_messages_settings.xml new file mode 100644 index 00000000000..66091ec7e3e --- /dev/null +++ b/res/xml/zen_mode_custom_rule_messages_settings.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java b/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java new file mode 100644 index 00000000000..4403873c693 --- /dev/null +++ b/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java @@ -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 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() { + @Override + protected Void doInBackground(Void... unused) { + List 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(); + } +} diff --git a/src/com/android/settings/notification/app/AppChannelsBypassingDndSettings.java b/src/com/android/settings/notification/app/AppChannelsBypassingDndSettings.java new file mode 100644 index 00000000000..4dcbcfc9044 --- /dev/null +++ b/src/com/android/settings/notification/app/AppChannelsBypassingDndSettings.java @@ -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 createPreferenceControllers(Context context) { + mControllers = new ArrayList<>(); + mControllers.add(new HeaderPreferenceController(context, this)); + mControllers.add(new AppChannelsBypassingDndPreferenceController(context, + new NotificationBackend())); + return new ArrayList<>(mControllers); + } +} diff --git a/src/com/android/settings/notification/app/ChannelListPreferenceController.java b/src/com/android/settings/notification/app/ChannelListPreferenceController.java index 187e7e1ea5c..b19fc71d422 100644 --- a/src/com/android/settings/notification/app/ChannelListPreferenceController.java +++ b/src/com/android/settings/notification/app/ChannelListPreferenceController.java @@ -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 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 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 mChannelGroupComparator = - new Comparator() { - - @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 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()); - }; } diff --git a/src/com/android/settings/notification/app/NotificationPreferenceController.java b/src/com/android/settings/notification/app/NotificationPreferenceController.java index ecba95b8985..14c98cfa5bb 100644 --- a/src/com/android/settings/notification/app/NotificationPreferenceController.java +++ b/src/com/android/settings/notification/app/NotificationPreferenceController.java @@ -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 CHANNEL_GROUP_COMPARATOR = + new Comparator() { + @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 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()); + }; } diff --git a/src/com/android/settings/notification/zen/ZenCustomRuleCallsSettings.java b/src/com/android/settings/notification/zen/ZenCustomRuleCallsSettings.java index 37cd5587672..459e418f2ef 100644 --- a/src/com/android/settings/notification/zen/ZenCustomRuleCallsSettings.java +++ b/src/com/android/settings/notification/zen/ZenCustomRuleCallsSettings.java @@ -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 diff --git a/src/com/android/settings/notification/zen/ZenCustomRuleMessagesSettings.java b/src/com/android/settings/notification/zen/ZenCustomRuleMessagesSettings.java index e592e75150f..d4d37300afb 100644 --- a/src/com/android/settings/notification/zen/ZenCustomRuleMessagesSettings.java +++ b/src/com/android/settings/notification/zen/ZenCustomRuleMessagesSettings.java @@ -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 diff --git a/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceController.java b/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceController.java new file mode 100644 index 00000000000..15a46fe1e1e --- /dev/null +++ b/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceController.java @@ -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 apps = mAppSession.rebuild(filter, + ApplicationsState.ALPHA_COMPARATOR); + updateAppList(apps); + } + + @VisibleForTesting + void updateAppList(List 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 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 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(); + } + }; +} diff --git a/src/com/android/settings/notification/zen/ZenModeAllBypassingAppsPreferenceController.java b/src/com/android/settings/notification/zen/ZenModeAllBypassingAppsPreferenceController.java index 9484a09cb92..52c872cbe50 100644 --- a/src/com/android/settings/notification/zen/ZenModeAllBypassingAppsPreferenceController.java +++ b/src/com/android/settings/notification/zen/ZenModeAllBypassingAppsPreferenceController.java @@ -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,64 +107,95 @@ public class ZenModeAllBypassingAppsPreferenceController extends AbstractPrefere ApplicationsState.AppFilter filter = ApplicationsState.FILTER_ALL_ENABLED; List apps = mAppSession.rebuild(filter, ApplicationsState.ALPHA_COMPARATOR); - updateNotificationChannelList(apps); + updateAppList(apps); } @VisibleForTesting - void updateNotificationChannelList(List apps) { - if (mPreferenceScreen == null || apps == null) { + void updateAppList(List apps) { + if (mPreferenceCategory == null || apps == null) { return; } - boolean showEmptyState = true; - - List 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; + List 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, app.info.packageName); + args.putInt(AppInfoBase.ARG_PACKAGE_UID, app.info.uid); + new SubSettingLauncher(mContext) + .setDestination(AppChannelsBypassingDndSettings.class.getName()) + .setArguments(args) + .setResultListener(mHostFragment, 0) + .setSourceMetricsCategory( + SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP) + .launch(); + return true; + }); } - 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())); - - 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()); - new SubSettingLauncher(mContext) - .setDestination(ChannelNotificationSettings.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; - } - - mPreferenceScreen.removeAll(); - if (channelsBypassingDnd.size() > 0) { - for (Preference prefToAdd : channelsBypassingDnd) { - mPreferenceScreen.addPreference(prefToAdd); + 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); } - } - if (showEmptyState) { - Preference pref = new Preference(mPrefContext); - pref.setTitle(R.string.zen_mode_bypassing_apps_subtext_none); - mPreferenceScreen.addPreference(pref); + + appsBypassingDnd.add(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 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 = @@ -170,27 +203,27 @@ public class ZenModeAllBypassingAppsPreferenceController extends AbstractPrefere @Override public void onRunningStateChanged(boolean running) { - updateNotificationChannelList(); + updateAppList(); } @Override public void onPackageListChanged() { - updateNotificationChannelList(); + updateAppList(); } @Override public void onRebuildComplete(ArrayList 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(); } }; } diff --git a/src/com/android/settings/notification/zen/ZenModeBackend.java b/src/com/android/settings/notification/zen/ZenModeBackend.java index bb406c1b827..9291d55a320 100644 --- a/src/com/android/settings/notification/zen/ZenModeBackend.java +++ b/src/com/android/settings/notification/zen/ZenModeBackend.java @@ -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); } diff --git a/src/com/android/settings/notification/zen/ZenModeBypassingAppsPreferenceController.java b/src/com/android/settings/notification/zen/ZenModeBypassingAppsPreferenceController.java index 7459394d701..6b1f46ca9b1 100644 --- a/src/com/android/settings/notification/zen/ZenModeBypassingAppsPreferenceController.java +++ b/src/com/android/settings/notification/zen/ZenModeBypassingAppsPreferenceController.java @@ -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 appsBypassingDnd = new ArrayList<>(); + Set 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 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); } diff --git a/src/com/android/settings/notification/zen/ZenModeBypassingAppsSettings.java b/src/com/android/settings/notification/zen/ZenModeBypassingAppsSettings.java index bd1a041d040..89a80f06b6a 100644 --- a/src/com/android/settings/notification/zen/ZenModeBypassingAppsSettings.java +++ b/src/com/android/settings/notification/zen/ZenModeBypassingAppsSettings.java @@ -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 buildPreferenceControllers(Context context, - Application app, Fragment host) { + Application app, Fragment host, NotificationBackend notificationBackend) { final List 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 createPreferenceControllers( Context context) { - return buildPreferenceControllers(context, null, null); + return buildPreferenceControllers(context, null, null, null); } }; } diff --git a/src/com/android/settings/notification/zen/ZenModeConversationsSettings.java b/src/com/android/settings/notification/zen/ZenModeConversationsSettings.java index 4307538c0f5..3daefd56f14 100644 --- a/src/com/android/settings/notification/zen/ZenModeConversationsSettings.java +++ b/src/com/android/settings/notification/zen/ZenModeConversationsSettings.java @@ -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 { diff --git a/src/com/android/settings/notification/zen/ZenModePriorityCallsPreferenceController.java b/src/com/android/settings/notification/zen/ZenModePriorityCallsPreferenceController.java deleted file mode 100644 index 6b538dc2c93..00000000000 --- a/src/com/android/settings/notification/zen/ZenModePriorityCallsPreferenceController.java +++ /dev/null @@ -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; - } -} diff --git a/src/com/android/settings/notification/zen/ZenModePriorityConversationsPreferenceController.java b/src/com/android/settings/notification/zen/ZenModePriorityConversationsPreferenceController.java index b6824a90f18..4ae16bcec9a 100644 --- a/src/com/android/settings/notification/zen/ZenModePriorityConversationsPreferenceController.java +++ b/src/com/android/settings/notification/zen/ZenModePriorityConversationsPreferenceController.java @@ -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 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 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 importantConversations = + mNumConversations = numConversations; + + ParceledListSlice 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 diff --git a/src/com/android/settings/notification/zen/ZenModeSettings.java b/src/com/android/settings/notification/zen/ZenModeSettings.java index c92099a6db4..d67c3535e78 100644 --- a/src/com/android/settings/notification/zen/ZenModeSettings.java +++ b/src/com/android/settings/notification/zen/ZenModeSettings.java @@ -153,7 +153,7 @@ public class ZenModeSettings extends ZenModeSettingsBase { String getCallsSettingSummary(Policy policy) { List 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) { diff --git a/tests/robotests/src/com/android/settings/notification/app/ConversationListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/ConversationListPreferenceControllerTest.java index ad2e74affc0..6cc20d78acd 100644 --- a/tests/robotests/src/com/android/settings/notification/app/ConversationListPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/app/ConversationListPreferenceControllerTest.java @@ -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()); } diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceControllerTest.java new file mode 100644 index 00000000000..52f2c51a3f4 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceControllerTest.java @@ -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 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 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 prefCaptor = ArgumentCaptor.forClass(Preference.class); + verify(mPreferenceCategory).addPreference(prefCaptor.capture()); + + Preference pref = prefCaptor.getValue(); + assertThat(pref.getKey()).isEqualTo( + ZenModeAllBypassingAppsPreferenceController.KEY_NO_APPS); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeAllBypassingAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeAllBypassingAppsPreferenceControllerTest.java index c2d54f4c8c1..1fff49c2951 100644 --- a/tests/robotests/src/com/android/settings/notification/zen/ZenModeAllBypassingAppsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeAllBypassingAppsPreferenceControllerTest.java @@ -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 appEntries = new ArrayList<>(); - appEntries.add(entry); - + appEntries.add(entry1); + appEntries.add(entry2); List 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 prefCaptor = ArgumentCaptor.forClass(Preference.class); + verify(mPreferenceCategory).addPreference(prefCaptor.capture()); + + Preference pref = prefCaptor.getValue(); + assertThat(pref.getKey()).isEqualTo( + ZenModeAllBypassingAppsPreferenceController.KEY_NO_APPS); } } diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModePriorityCallsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModePriorityCallsPreferenceControllerTest.java deleted file mode 100644 index d1f80b8fc3d..00000000000 --- a/tests/robotests/src/com/android/settings/notification/zen/ZenModePriorityCallsPreferenceControllerTest.java +++ /dev/null @@ -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)]); - } -} \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeSettingsTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeSettingsTest.java index d3c3a85a5ad..ea750560572 100644 --- a/tests/robotests/src/com/android/settings/notification/zen/ZenModeSettingsTest.java +++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeSettingsTest.java @@ -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