Merge "Add list of selected/excluded convos to bubble settings" into rvc-dev
diff --git a/res/drawable/ic_delete_x.xml b/res/drawable/ic_delete_x.xml
new file mode 100644
index 0000000..345daf0
--- /dev/null
+++ b/res/drawable/ic_delete_x.xml
@@ -0,0 +1,25 @@
+<?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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+<path
+        android:fillColor="#FF000000"
+        android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
+</vector>
diff --git a/res/layout/bubble_conversation_remove_button.xml b/res/layout/bubble_conversation_remove_button.xml
new file mode 100644
index 0000000..6ec48dc
--- /dev/null
+++ b/res/layout/bubble_conversation_remove_button.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<ImageButton
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/button"
+    android:layout_width="48dp"
+    android:layout_height="48dp"
+    android:background="?android:attr/selectableItemBackground"
+    android:scaleType="centerInside"
+    android:tint="?android:attr/textColorSecondary"
+    android:src="@drawable/ic_delete_x"/>
\ No newline at end of file
diff --git a/res/xml/app_bubble_notification_settings.xml b/res/xml/app_bubble_notification_settings.xml
index 3f52ad3..976d9f0 100644
--- a/res/xml/app_bubble_notification_settings.xml
+++ b/res/xml/app_bubble_notification_settings.xml
@@ -27,4 +27,12 @@
             android:title="@string/notification_bubbles_title"
             settings:allowDividerBelow="false"/>
 
+        <!-- Selected or excluded conversations get added here -->
+        <PreferenceCategory
+            android:title="@string/bubble_app_setting_selected_conversation_title"
+            android:key="bubble_conversations"
+            android:visibility="gone"
+            settings:allowDividerAbove="false"
+            settings:allowDividerBelow="false" />
+
 </PreferenceScreen>
diff --git a/src/com/android/settings/notification/AppBubbleListPreferenceController.java b/src/com/android/settings/notification/AppBubbleListPreferenceController.java
new file mode 100644
index 0000000..457a61c
--- /dev/null
+++ b/src/com/android/settings/notification/AppBubbleListPreferenceController.java
@@ -0,0 +1,154 @@
+/*
+ * 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;
+
+import static android.app.NotificationChannel.DEFAULT_ALLOW_BUBBLE;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
+
+import android.annotation.Nullable;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.content.Context;
+import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Drawable;
+import android.service.notification.ConversationChannelWrapper;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+import com.android.settings.notification.app.AppConversationListPreferenceController;
+import com.android.settingslib.RestrictedLockUtils;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Displays a list of conversations that have either been selected or excluded from bubbling.
+ */
+public class AppBubbleListPreferenceController extends AppConversationListPreferenceController {
+
+    private static final String KEY = "bubble_conversations";
+
+    public AppBubbleListPreferenceController(Context context, NotificationBackend backend) {
+        super(context, backend);
+    }
+
+    @Override
+    public void onResume(NotificationBackend.AppRow appRow,
+            @Nullable NotificationChannel channel, @Nullable NotificationChannelGroup group,
+            Drawable conversationDrawable,
+            ShortcutInfo conversationInfo,
+            RestrictedLockUtils.EnforcedAdmin admin) {
+        super.onResume(appRow, channel, group, conversationDrawable, conversationInfo, admin);
+        // In case something changed in the foreground (e.g. via bubble button on notification)
+        loadConversationsAndPopulate();
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        if (!super.isAvailable()) {
+            return false;
+        }
+        if (mAppRow.bubblePreference == BUBBLE_PREFERENCE_NONE) {
+            return false;
+        }
+        return true;
+    }
+
+    @VisibleForTesting
+    @Override
+    public List<ConversationChannelWrapper> filterAndSortConversations(
+            List<ConversationChannelWrapper> conversations) {
+        return conversations.stream()
+                .sorted(mConversationComparator)
+                .filter((c) -> {
+                    if (mAppRow.bubblePreference == BUBBLE_PREFERENCE_SELECTED) {
+                        return c.getNotificationChannel().canBubble();
+                    } else if (mAppRow.bubblePreference == BUBBLE_PREFERENCE_ALL) {
+                        return c.getNotificationChannel().getAllowBubbles() == 0;
+                    }
+                    return false;
+                })
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    protected int getTitleResId() {
+        // TODO: possible to left align like mocks?
+        return mAppRow.bubblePreference == BUBBLE_PREFERENCE_SELECTED
+                ? R.string.bubble_app_setting_selected_conversation_title
+                : R.string.bubble_app_setting_excluded_conversation_title;
+    }
+
+    @VisibleForTesting
+    @Override
+    public Preference createConversationPref(final ConversationChannelWrapper conversation) {
+        final ConversationPreference pref = new ConversationPreference(mContext);
+        populateConversationPreference(conversation, pref);
+        pref.setOnClickListener((v) -> {
+            conversation.getNotificationChannel().setAllowBubbles(DEFAULT_ALLOW_BUBBLE);
+            mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, conversation.getNotificationChannel());
+            mPreference.removePreference(pref);
+            if (mPreference.getPreferenceCount() == 0) {
+                mPreference.setVisible(false);
+            }
+        });
+        return pref;
+    }
+
+    /** Simple preference with a 'x' button at the end. */
+    @VisibleForTesting
+    public static class ConversationPreference extends Preference implements View.OnClickListener {
+
+        View.OnClickListener mOnClickListener;
+
+        ConversationPreference(Context context) {
+            super(context);
+            setWidgetLayoutResource(R.layout.bubble_conversation_remove_button);
+        }
+
+        @Override
+        public void onBindViewHolder(final PreferenceViewHolder holder) {
+            super.onBindViewHolder(holder);
+            ImageView view =  holder.itemView.findViewById(R.id.button);
+            view.setOnClickListener(mOnClickListener);
+        }
+
+        public void setOnClickListener(View.OnClickListener listener) {
+            mOnClickListener = listener;
+        }
+
+        @Override
+        public void onClick(View v) {
+            if (mOnClickListener != null) {
+                mOnClickListener.onClick(v);
+            }
+        }
+    }
+}
diff --git a/src/com/android/settings/notification/app/AppBubbleNotificationSettings.java b/src/com/android/settings/notification/app/AppBubbleNotificationSettings.java
index 5026a26..b1b126e 100644
--- a/src/com/android/settings/notification/app/AppBubbleNotificationSettings.java
+++ b/src/com/android/settings/notification/app/AppBubbleNotificationSettings.java
@@ -22,6 +22,7 @@
 import android.util.Log;
 
 import com.android.settings.R;
+import com.android.settings.notification.AppBubbleListPreferenceController;
 import com.android.settings.notification.NotificationBackend;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settingslib.core.AbstractPreferenceController;
@@ -56,18 +57,20 @@
 
     @Override
     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
-        mControllers = getPreferenceControllers(context, this);
+        mControllers = getPreferenceControllers(context, this, mDependentFieldListener);
         return new ArrayList<>(mControllers);
     }
 
     protected static List<NotificationPreferenceController> getPreferenceControllers(
-            Context context, AppBubbleNotificationSettings fragment) {
+            Context context, AppBubbleNotificationSettings fragment,
+            DependentFieldListener listener) {
         List<NotificationPreferenceController> controllers = new ArrayList<>();
         controllers.add(new HeaderPreferenceController(context, fragment));
         controllers.add(new BubblePreferenceController(context, fragment != null
                 ? fragment.getChildFragmentManager()
                 : null,
-                new NotificationBackend(), true /* isAppPage */));
+                new NotificationBackend(), true /* isAppPage */, listener));
+        controllers.add(new AppBubbleListPreferenceController(context, new NotificationBackend()));
         return controllers;
     }
 
@@ -114,7 +117,7 @@
                 public List<AbstractPreferenceController> createPreferenceControllers(Context
                         context) {
                     return new ArrayList<>(AppBubbleNotificationSettings.getPreferenceControllers(
-                            context, null));
+                            context, null, null));
                 }
             };
 }
diff --git a/src/com/android/settings/notification/app/AppConversationListPreferenceController.java b/src/com/android/settings/notification/app/AppConversationListPreferenceController.java
index bea0157..2fcd2b2 100644
--- a/src/com/android/settings/notification/app/AppConversationListPreferenceController.java
+++ b/src/com/android/settings/notification/app/AppConversationListPreferenceController.java
@@ -19,6 +19,7 @@
 import android.app.NotificationChannel;
 import android.app.settings.SettingsEnums;
 import android.content.Context;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.ShortcutInfo;
 import android.os.AsyncTask;
 import android.os.Bundle;
@@ -33,6 +34,7 @@
 import com.android.settings.core.SubSettingLauncher;
 import com.android.settings.notification.NotificationBackend;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
@@ -42,8 +44,8 @@
     private static final String KEY = "conversations";
     public static final String ARG_FROM_SETTINGS = "fromSettings";
 
-    private List<ConversationChannelWrapper> mConversations;
-    private PreferenceCategory mPreference;
+    protected List<ConversationChannelWrapper> mConversations = new ArrayList<>();
+    protected PreferenceCategory mPreference;
     private boolean mHasSentMsg;
 
     public AppConversationListPreferenceController(Context context, NotificationBackend backend) {
@@ -75,13 +77,23 @@
     @Override
     public void updateState(Preference preference) {
         mPreference = (PreferenceCategory) preference;
+        loadConversationsAndPopulate();
+    }
+
+    protected void loadConversationsAndPopulate() {
+        if (mAppRow == null) {
+            return;
+        }
         // Load channel settings
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... unused) {
                 mHasSentMsg = mBackend.hasSentMessage(mAppRow.pkg, mAppRow.uid);
-                mConversations = mBackend.getConversations(mAppRow.pkg, mAppRow.uid).getList();
-                Collections.sort(mConversations, mConversationComparator);
+                ParceledListSlice<ConversationChannelWrapper> list =
+                        mBackend.getConversations(mAppRow.pkg, mAppRow.uid);
+                if (list != null) {
+                    mConversations = filterAndSortConversations(list.getList());
+                }
                 return null;
             }
 
@@ -95,11 +107,22 @@
         }.execute();
     }
 
+    protected List<ConversationChannelWrapper> filterAndSortConversations(
+            List<ConversationChannelWrapper> conversations) {
+        Collections.sort(conversations, mConversationComparator);
+        return conversations;
+    }
+
+    protected int getTitleResId() {
+        return R.string.conversations_category_title;
+    }
+
     private void populateList() {
+        if (mPreference == null) {
+            return;
+        }
         // TODO: if preference has children, compare with newly loaded list
         mPreference.removeAll();
-        mPreference.setTitle(R.string.conversations_category_title);
-
         if (mConversations.isEmpty()) {
             if (mHasSentMsg) {
                 mPreference.setVisible(true);
@@ -112,6 +135,7 @@
             }
         } else {
             mPreference.setVisible(true);
+            mPreference.setTitle(getTitleResId());
             populateConversations();
         }
     }
@@ -127,6 +151,12 @@
 
     protected Preference createConversationPref(final ConversationChannelWrapper conversation) {
         Preference pref = new Preference(mContext);
+        populateConversationPreference(conversation, pref);
+        return pref;
+    }
+
+    protected void populateConversationPreference(final ConversationChannelWrapper conversation,
+            final Preference pref) {
         ShortcutInfo si = conversation.getShortcutInfo();
 
         pref.setTitle(si != null
@@ -157,7 +187,6 @@
                 .setTitleText(pref.getTitle())
                 .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_APP_NOTIFICATION)
                 .toIntent());
-        return pref;
     }
 
     protected Comparator<ConversationChannelWrapper> mConversationComparator =
diff --git a/src/com/android/settings/notification/app/BubblePreferenceController.java b/src/com/android/settings/notification/app/BubblePreferenceController.java
index 02d6d99..0ca2095 100644
--- a/src/com/android/settings/notification/app/BubblePreferenceController.java
+++ b/src/com/android/settings/notification/app/BubblePreferenceController.java
@@ -49,12 +49,15 @@
     private boolean mIsAppPage;
     private boolean mHasSentInvalidMsg;
     private int mNumConversations;
+    private NotificationSettings.DependentFieldListener mListener;
 
     public BubblePreferenceController(Context context, @Nullable FragmentManager fragmentManager,
-            NotificationBackend backend, boolean isAppPage) {
+            NotificationBackend backend, boolean isAppPage,
+            @Nullable NotificationSettings.DependentFieldListener listener) {
         super(context, backend);
         mFragmentManager = fragmentManager;
         mIsAppPage = isAppPage;
+        mListener = listener;
     }
 
     @Override
@@ -128,6 +131,9 @@
                     mBackend.setAllowBubbles(mAppRow.pkg, mAppRow.uid, value);
                 }
             }
+            if (mListener != null) {
+                mListener.onFieldValueChanged();
+            }
         }
         return true;
     }
diff --git a/src/com/android/settings/notification/app/ConversationNotificationSettings.java b/src/com/android/settings/notification/app/ConversationNotificationSettings.java
index 9ee4a2c..902c81c 100644
--- a/src/com/android/settings/notification/app/ConversationNotificationSettings.java
+++ b/src/com/android/settings/notification/app/ConversationNotificationSettings.java
@@ -92,7 +92,7 @@
         mControllers.add(new BadgePreferenceController(context, mBackend));
         mControllers.add(new NotificationsOffPreferenceController(context));
         mControllers.add(new BubblePreferenceController(context, getChildFragmentManager(),
-                mBackend, false /* isAppPage */));
+                mBackend, false /* isAppPage */, null /* dependentFieldListener */));
         mControllers.add(new ConversationDemotePreferenceController(context, this, mBackend));
         mControllers.add(new BubbleCategoryPreferenceController(context));
         mControllers.add(new BubbleLinkPreferenceController(context));
diff --git a/tests/robotests/src/com/android/settings/notification/app/AppBubbleListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/AppBubbleListPreferenceControllerTest.java
new file mode 100644
index 0000000..d176827
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/app/AppBubbleListPreferenceControllerTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.NotificationChannel.DEFAULT_ALLOW_BUBBLE;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
+
+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.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ShortcutInfo;
+import android.os.UserManager;
+import android.service.notification.ConversationChannelWrapper;
+
+import androidx.preference.PreferenceCategory;
+
+import com.android.settings.notification.AppBubbleListPreferenceController;
+import com.android.settings.notification.NotificationBackend;
+
+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 java.util.ArrayList;
+import java.util.List;
+
+
+@RunWith(RobolectricTestRunner.class)
+public class AppBubbleListPreferenceControllerTest {
+
+    private Context mContext;
+    @Mock
+    private NotificationBackend mBackend;
+    @Mock
+    private NotificationManager mNm;
+    @Mock
+    private UserManager mUm;
+
+    private AppBubbleListPreferenceController mController;
+    private ParceledListSlice<ConversationChannelWrapper> mConvoList;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowApplication shadowApplication = ShadowApplication.getInstance();
+        shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm);
+        shadowApplication.setSystemService(Context.USER_SERVICE, mUm);
+        mContext = RuntimeEnvironment.application;
+
+        List<ConversationChannelWrapper> convoList = new ArrayList<>();
+        convoList.add(getConvo(-1, "default"));
+        convoList.add(getConvo(1, "selected"));
+        convoList.add(getConvo(0, "excluded"));
+        mConvoList = new ParceledListSlice<>(convoList);
+        when(mBackend.getConversations(anyString(), anyInt())).thenReturn(mConvoList);
+        mController = new AppBubbleListPreferenceController(mContext, mBackend);
+    }
+
+    ConversationChannelWrapper getConvo(int bubbleChannelPref, String channelId) {
+        ConversationChannelWrapper ccw = new ConversationChannelWrapper();
+        NotificationChannel channel = mock(NotificationChannel.class);
+        when(channel.getId()).thenReturn(channelId);
+        when(channel.getAllowBubbles()).thenReturn(bubbleChannelPref);
+        when(channel.canBubble()).thenReturn(bubbleChannelPref == 1);
+        ccw.setNotificationChannel(channel);
+        ccw.setPkg("pkg");
+        ccw.setUid(1);
+        ccw.setShortcutInfo(mock(ShortcutInfo.class));
+        return ccw;
+    }
+
+    @Test
+    public void isAvailable_BUBBLE_PREFERENCE_NONE_false() {
+        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+        appRow.bubblePreference = BUBBLE_PREFERENCE_NONE;
+        mController.onResume(appRow, null, null, null, null, null);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void isAvailable_BUBBLE_PREFERENCE_SELECTED_true() {
+        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+        appRow.bubblePreference = BUBBLE_PREFERENCE_SELECTED;
+        mController.onResume(appRow, null, null, null, null, null);
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_BUBBLE_PREFERENCE_ALL_true() {
+        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+        appRow.bubblePreference = BUBBLE_PREFERENCE_ALL;
+        mController.onResume(appRow, null, null, null, null, null);
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void filterAndSortConversations_BUBBLE_PREFERENCE_SELECTED_filtersAllowedBubbles() {
+        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+        appRow.bubblePreference = BUBBLE_PREFERENCE_SELECTED;
+        mController.onResume(appRow, null, null, null, null, null);
+
+        List<ConversationChannelWrapper> result =
+                mController.filterAndSortConversations(mConvoList.getList());
+        assertThat(result.size()).isEqualTo(1);
+        assertThat(result.get(0).getNotificationChannel().getId())
+                .isEqualTo("selected");
+    }
+
+    @Test
+    public void filterAndSortConversations_BUBBLE_PREFERENCE_ALL_filtersExcludedBubbles() {
+        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+        appRow.bubblePreference = BUBBLE_PREFERENCE_ALL;
+        mController.onResume(appRow, null, null, null, null, null);
+
+        List<ConversationChannelWrapper> result =
+                mController.filterAndSortConversations(mConvoList.getList());
+        assertThat(result.size()).isEqualTo(1);
+        assertThat(result.get(0).getNotificationChannel().getId())
+                .isEqualTo("excluded");
+    }
+
+    @Test
+    public void clickConversationPref_updatesChannel() {
+        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+        appRow.bubblePreference = BUBBLE_PREFERENCE_ALL;
+        appRow.pkg = "PKG";
+        mController.onResume(appRow, null, null, null, null, null);
+        mController.mPreference = new PreferenceCategory(mContext);
+
+        ConversationChannelWrapper ccw = mConvoList.getList().get(0);
+        AppBubbleListPreferenceController.ConversationPreference pref =
+                (AppBubbleListPreferenceController.ConversationPreference)
+                mController.createConversationPref(ccw);
+        pref.onClick(null);
+
+        verify(ccw.getNotificationChannel()).setAllowBubbles(DEFAULT_ALLOW_BUBBLE);
+        verify(mBackend).updateChannel(anyString(), anyInt(), any(NotificationChannel.class));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/app/BubblePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/BubblePreferenceControllerTest.java
index 0cf6dc6..685bca9 100644
--- a/tests/robotests/src/com/android/settings/notification/app/BubblePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/app/BubblePreferenceControllerTest.java
@@ -80,6 +80,8 @@
     private PreferenceScreen mScreen;
     @Mock
     private FragmentManager mFragmentManager;
+    @Mock
+    private NotificationSettings.DependentFieldListener mListener;
 
     private BubblePreferenceController mController;
     private BubblePreferenceController mAppPageController;
@@ -93,9 +95,9 @@
         mContext = RuntimeEnvironment.application;
         when(mFragmentManager.beginTransaction()).thenReturn(mock(FragmentTransaction.class));
         mController = spy(new BubblePreferenceController(mContext, mFragmentManager, mBackend,
-                false /* isAppPage */));
+                false /* isAppPage */, mListener));
         mAppPageController = spy(new BubblePreferenceController(mContext, mFragmentManager,
-                mBackend, true /* isAppPage */));
+                mBackend, true /* isAppPage */, mListener));
     }
 
     @Test
@@ -106,7 +108,7 @@
     }
 
     @Test
-    public void testIsAvailable_notIfAppBlocked() {
+    public void isAvailable_notIfAppBlocked() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         appRow.banned = true;
@@ -115,7 +117,7 @@
     }
 
     @Test
-    public void testIsAvailable_notIfChannelBlocked() {
+    public void isAvailable_notIfChannelBlocked() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         NotificationChannel channel = mock(NotificationChannel.class);
@@ -125,7 +127,7 @@
     }
 
     @Test
-    public void testIsAvailable_channel_yesIfAppOff() {
+    public void isAvailable_channel_yesIfAppOff() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         appRow.bubblePreference = BUBBLE_PREFERENCE_NONE;
@@ -137,7 +139,7 @@
     }
 
     @Test
-    public void testIsNotAvailable_ifOffGlobally_app() {
+    public void isNotAvailable_ifOffGlobally_app() {
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         mController.onResume(appRow, null, null, null, null, null);
         Settings.Global.putInt(mContext.getContentResolver(),
@@ -147,7 +149,7 @@
     }
 
     @Test
-    public void testIsAvailable_notIfOffGlobally_channel() {
+    public void isAvailable_notIfOffGlobally_channel() {
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         NotificationChannel channel = mock(NotificationChannel.class);
         when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH);
@@ -159,7 +161,7 @@
     }
 
     @Test
-    public void testIsAvailable_app_evenIfOffGlobally() {
+    public void isAvailable_app_evenIfOffGlobally() {
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         mAppPageController.onResume(appRow, null, null, null, null, null);
         Settings.Global.putInt(mContext.getContentResolver(),
@@ -169,7 +171,7 @@
     }
 
     @Test
-    public void testIsAvailable_app() {
+    public void isAvailable_app() {
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         mController.onResume(appRow, null, null, null, null, null);
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
@@ -178,7 +180,7 @@
     }
 
     @Test
-    public void testIsAvailable_defaultChannel() {
+    public void isAvailable_defaultChannel() {
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         appRow.bubblePreference = BUBBLE_PREFERENCE_ALL;
         NotificationChannel channel = mock(NotificationChannel.class);
@@ -191,7 +193,7 @@
     }
 
     @Test
-    public void testIsAvailable_channel() {
+    public void isAvailable_channel() {
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         appRow.bubblePreference = BUBBLE_PREFERENCE_ALL;
         NotificationChannel channel = mock(NotificationChannel.class);
@@ -203,7 +205,7 @@
     }
 
     @Test
-    public void testUpdateState_disabledByAdmin() {
+    public void updateState_disabledByAdmin() {
         NotificationChannel channel = mock(NotificationChannel.class);
         when(channel.getId()).thenReturn("something");
         mController.onResume(new NotificationBackend.AppRow(), channel, null,
@@ -216,7 +218,7 @@
     }
 
     @Test
-    public void testUpdateState_app_disabledByAdmin() {
+    public void updateState_app_disabledByAdmin() {
         NotificationChannel channel = mock(NotificationChannel.class);
         when(channel.getId()).thenReturn("something");
         mAppPageController.onResume(new NotificationBackend.AppRow(), channel, null,
@@ -229,7 +231,7 @@
     }
 
     @Test
-    public void testUpdateState_channel_channelNotBlockable() {
+    public void updateState_channel_channelNotBlockable() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         NotificationChannel channel = mock(NotificationChannel.class);
@@ -243,7 +245,7 @@
     }
 
     @Test
-    public void testUpdateState_channel() {
+    public void updateState_channel() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         NotificationChannel channel = mock(NotificationChannel.class);
@@ -263,7 +265,7 @@
     }
 
     @Test
-    public void testUpdateState_app() {
+    public void updateState_app() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         appRow.label = "App!";
@@ -288,7 +290,7 @@
     }
 
     @Test
-    public void testUpdateState_app_offGlobally() {
+    public void updateState_app_offGlobally() {
         Settings.Global.putInt(mContext.getContentResolver(),
                 NOTIFICATION_BUBBLES, SYSTEM_WIDE_OFF);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
@@ -302,7 +304,7 @@
     }
 
     @Test
-    public void testOnPreferenceChange_on_channel() {
+    public void onPreferenceChange_on_channel() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         appRow.bubblePreference = BUBBLE_PREFERENCE_SELECTED;
@@ -321,7 +323,7 @@
     }
 
     @Test
-    public void testOnPreferenceChange_off_channel() {
+    public void onPreferenceChange_off_channel() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         appRow.bubblePreference = BUBBLE_PREFERENCE_SELECTED;
@@ -341,7 +343,7 @@
 
 
     @Test
-    public void testOnPreferenceChange_app_all() {
+    public void onPreferenceChange_app_all() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         appRow.bubblePreference = BUBBLE_PREFERENCE_NONE;
@@ -379,7 +381,7 @@
     }
 
     @Test
-    public void testOnPreferenceChange_app_selected() {
+    public void onPreferenceChange_app_selected() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         appRow.bubblePreference = BUBBLE_PREFERENCE_ALL;
@@ -397,7 +399,7 @@
     }
 
     @Test
-    public void testOnPreferenceChange_app_none() {
+    public void onPreferenceChange_app_none() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         appRow.bubblePreference = BUBBLE_PREFERENCE_ALL;
@@ -413,4 +415,17 @@
         assertEquals(BUBBLE_PREFERENCE_NONE, appRow.bubblePreference);
         verify(mBackend, times(1)).setAllowBubbles(any(), anyInt(), eq(BUBBLE_PREFERENCE_NONE));
     }
+
+    @Test
+    public void onPreferenceChange_dependentFieldListenerCalled() {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
+        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+        appRow.bubblePreference = BUBBLE_PREFERENCE_ALL;
+        mAppPageController.onResume(appRow, null, null, null, null, null);
+
+        BubblePreference pref = new BubblePreference(mContext);
+        mAppPageController.onPreferenceChange(pref, BUBBLE_PREFERENCE_NONE);
+
+        verify(mListener, times(1)).onFieldValueChanged();
+    }
 }