Merge "DND Bypassing Apps redesign" into rvc-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 46d75e8..fd869d4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6908,7 +6908,8 @@
     <!-- 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] -->
+
+    <!-- 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] -->
     <string name="user_cannot_manage_message" product="default">Only the phone\u2019s owner can manage users.</string>
@@ -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>
 
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 0000000..4f6291d
--- /dev/null
+++ b/res/xml/app_channels_bypassing_dnd_settings.xml
@@ -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>
\ No newline at end of file
diff --git a/res/xml/conversation_list_settings.xml b/res/xml/conversation_list_settings.xml
index 90a7279..e18b4e5 100644
--- a/res/xml/conversation_list_settings.xml
+++ b/res/xml/conversation_list_settings.xml
@@ -14,10 +14,11 @@
      limitations under the License.
 -->
 
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:settings="http://schemas.android.com/apk/res-auto"
-        android:key="conversation_list"
-        android:title="zen_mode_conversations_title">
+<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="@string/zen_mode_conversations_title">
 
     <PreferenceCategory
         android:key="important_conversations"
diff --git a/res/xml/zen_mode_bypassing_apps.xml b/res/xml/zen_mode_bypassing_apps.xml
index bd46c67..9039470 100644
--- a/res/xml/zen_mode_bypassing_apps.xml
+++ b/res/xml/zen_mode_bypassing_apps.xml
@@ -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>
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 0000000..4dca2ad
--- /dev/null
+++ b/res/xml/zen_mode_custom_rule_calls_settings.xml
@@ -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>
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 0000000..66091ec
--- /dev/null
+++ b/res/xml/zen_mode_custom_rule_messages_settings.xml
@@ -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>
\ 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 0000000..4403873
--- /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<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();
+    }
+}
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 0000000..4dcbcfc
--- /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<AbstractPreferenceController> 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 187e7e1..b19fc71 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.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 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 @@
             @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 @@
             }
             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 @@
                 }
             } 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 @@
             }
         }
     }
-
-    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());
-            };
 }
diff --git a/src/com/android/settings/notification/app/NotificationPreferenceController.java b/src/com/android/settings/notification/app/NotificationPreferenceController.java
index ecba95b..14c98cf 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.settingslib.RestrictedLockUtils;
 import com.android.settingslib.core.AbstractPreferenceController;
 
+import java.util.Comparator;
 import java.util.Objects;
 
 /**
@@ -172,4 +173,31 @@
         }
         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());
+    };
 }
diff --git a/src/com/android/settings/notification/zen/ZenCustomRuleCallsSettings.java b/src/com/android/settings/notification/zen/ZenCustomRuleCallsSettings.java
index 37cd558..459e418 100644
--- a/src/com/android/settings/notification/zen/ZenCustomRuleCallsSettings.java
+++ b/src/com/android/settings/notification/zen/ZenCustomRuleCallsSettings.java
@@ -44,7 +44,7 @@
 
     @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 e592e75..d4d3730 100644
--- a/src/com/android/settings/notification/zen/ZenCustomRuleMessagesSettings.java
+++ b/src/com/android/settings/notification/zen/ZenCustomRuleMessagesSettings.java
@@ -37,7 +37,7 @@
 
     @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 0000000..15a46fe
--- /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<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();
+                }
+            };
+}
diff --git a/src/com/android/settings/notification/zen/ZenModeAllBypassingAppsPreferenceController.java b/src/com/android/settings/notification/zen/ZenModeAllBypassingAppsPreferenceController.java
index 9484a09..52c872c 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 @@
 
     @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 @@
     }
 
     /**
-     * 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 @@
         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;
+        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, 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<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 =
@@ -170,27 +203,27 @@
 
                 @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 @@
 
                 @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 bb406c1..9291d55 100644
--- a/src/com/android/settings/notification/zen/ZenModeBackend.java
+++ b/src/com/android/settings/notification/zen/ZenModeBackend.java
@@ -287,44 +287,15 @@
 
     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 @@
                 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 @@
         }
     }
 
-    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 7459394..6b1f46c 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.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 java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Controls the summary for preference found at:
@@ -102,7 +104,7 @@
             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 @@
             }
         }
 
-        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 @@
         }
 
         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);
     }
diff --git a/src/com/android/settings/notification/zen/ZenModeBypassingAppsSettings.java b/src/com/android/settings/notification/zen/ZenModeBypassingAppsSettings.java
index bd1a041..89a80f0 100644
--- a/src/com/android/settings/notification/zen/ZenModeBypassingAppsSettings.java
+++ b/src/com/android/settings/notification/zen/ZenModeBypassingAppsSettings.java
@@ -24,6 +24,7 @@
 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 @@
         } 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 @@
                 @Override
                 public List<AbstractPreferenceController> 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 4307538..3daefd5 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.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 6b538dc..0000000
--- 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 b6824a9..4ae16bc 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.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 @@
     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 @@
 
     @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 @@
                     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 @@
             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 @@
     }
 
     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 @@
         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 c92099a..d67c353 100644
--- a/src/com/android/settings/notification/zen/ZenModeSettings.java
+++ b/src/com/android/settings/notification/zen/ZenModeSettings.java
@@ -153,7 +153,7 @@
         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 @@
                 }
             } 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 ad2e74a..6cc20d7 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.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.shadows.ShadowApplication;
 
 import java.util.ArrayList;
-import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
 public class ConversationListPreferenceControllerTest {
@@ -116,8 +97,6 @@
         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 0000000..52f2c51
--- /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<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);
+    }
+}
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 c2d54f4..1fff49c 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 @@
 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.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 @@
     @Mock
     private NotificationBackend mBackend;
     @Mock
-    private PreferenceScreen mPreferenceScreen;
+    private PreferenceCategory mPreferenceCategory;
     @Mock
     private ApplicationsState mApplicationState;
 
@@ -67,11 +69,10 @@
         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 @@
     }
 
     @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);
     }
 }
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 d1f80b8..0000000
--- 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 d3c3a85..ea75056 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 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 @@
                 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