Bubble settings: feature, notification, app

* Removed setting from developer options
* Removed bubble settings from normal notifications /
  channels

* Feature available via notification setting
* Feature screen with educational gif

* App level is now a tri-state choice of all / selected /
  none
* App level bubble controls are accessible top-level in
  app notifications

Test: make -j40 RunSettingsRoboTests ROBOTEST_FILTER="Bubble"
Bug: 138116133
Change-Id: Id103e9d3717fdc9b86a916be40c43cda9c35ac34
diff --git a/res/drawable/button_border_selected.xml b/res/drawable/button_border_selected.xml
index 65dfe1b..29acbce 100644
--- a/res/drawable/button_border_selected.xml
+++ b/res/drawable/button_border_selected.xml
@@ -20,6 +20,6 @@
         android:color="@color/notification_importance_selection_bg" />
     <stroke
         android:width="2dp"
-        android:color="@color/notification_importance_button_selected"/>
+        android:color="?android:attr/colorAccent"/>
     <corners android:radius="@dimen/rect_button_radius" />
 </shape>
diff --git a/res/drawable/ic_bubble_all.xml b/res/drawable/ic_bubble_all.xml
new file mode 100644
index 0000000..fdcf6dc
--- /dev/null
+++ b/res/drawable/ic_bubble_all.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM8,11c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zM8,13c-2.33,0 -7,1.17 -7,3.5L1,19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zM16,13c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45L17,19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z"
+        android:fillColor="#000000"/>
+</vector>
diff --git a/res/drawable/ic_bubble_none.xml b/res/drawable/ic_bubble_none.xml
new file mode 100644
index 0000000..e8fd7df
--- /dev/null
+++ b/res/drawable/ic_bubble_none.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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:pathData="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10 10,-4.5 10,-10S17.5,2 12,2zM4,12c0,-4.4 3.6,-8 8,-8 1.8,0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4,13.8 4,12zM12,20c-1.8,0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20,10.2 20,12c0,4.4 -3.6,8 -8,8z"
+        android:fillColor="#000000"/>
+</vector>
diff --git a/res/drawable/ic_bubble_selected.xml b/res/drawable/ic_bubble_selected.xml
new file mode 100644
index 0000000..f953328
--- /dev/null
+++ b/res/drawable/ic_bubble_selected.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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:pathData="M15,8c0,-1.42 -0.5,-2.73 -1.33,-3.76 0.42,-0.14 0.86,-0.24 1.33,-0.24 2.21,0 4,1.79 4,4s-1.79,4 -4,4c-0.43,0 -0.84,-0.09 -1.23,-0.21 -0.03,-0.01 -0.06,-0.02 -0.1,-0.03C14.5,10.73 15,9.42 15,8zM16.66,13.13C18.03,14.06 19,15.32 19,17v3h4v-3c0,-2.18 -3.58,-3.47 -6.34,-3.87zM9,6c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2m0,9c-2.7,0 -5.8,1.29 -6,2.01L3,18h12v-1c-0.2,-0.71 -3.3,-2 -6,-2M9,4c2.21,0 4,1.79 4,4s-1.79,4 -4,4 -4,-1.79 -4,-4 1.79,-4 4,-4zM9,13c2.67,0 8,1.34 8,4v3L1,20v-3c0,-2.66 5.33,-4 8,-4z"
+        android:fillColor="#000000"/>
+</vector>
diff --git a/res/drawable/ic_create_bubble.xml b/res/drawable/ic_create_bubble.xml
index e943355..82d5db8 100644
--- a/res/drawable/ic_create_bubble.xml
+++ b/res/drawable/ic_create_bubble.xml
@@ -14,17 +14,13 @@
     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"
-        android:viewportHeight="24"
-        android:tint="?android:attr/colorControlNormal">
-    <path
-        android:pathData="M12,3c-4.97,0 -9,4.03 -9,9c0,1.39 0.32,2.69 0.88,3.86l1.53,-1.53C5.15,13.6 5,12.82 5,12c0,-3.86 3.14,-7 7,-7s7,3.14 7,7s-3.14,7 -7,7c-0.83,0 -1.62,-0.15 -2.35,-0.42l-1.53,1.53C9.3,20.67 10.61,21 12,21c4.97,0 9,-4.03 9,-9C21,7.03 16.97,3 12,3z"
-        android:fillColor="#000000"/>
-    <path
-        android:pathData="M12.99,15.99l2,0l0,-7l-7,0l0,2l3.59,0l-8.79,8.8l1.41,1.41l8.79,-8.79z"
-        android:fillColor="#000000"/>
-</vector>
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?android:attr/colorControlNormal">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M22,12C22,12 22,12 22,12C22,12 22,12 22,12c0,0.56 -0.06,1.1 -0.15,1.64l-1.97,-0.33c0.15,-0.91 0.15,-1.84 -0.02,-2.75c-0.01,-0.03 -0.01,-0.07 -0.02,-0.1c-0.03,-0.18 -0.08,-0.36 -0.13,-0.54c-0.02,-0.08 -0.04,-0.16 -0.06,-0.24c-0.04,-0.14 -0.09,-0.27 -0.14,-0.41c-0.04,-0.12 -0.08,-0.24 -0.13,-0.35c-0.04,-0.09 -0.08,-0.18 -0.13,-0.27c-0.07,-0.15 -0.14,-0.3 -0.22,-0.45c-0.03,-0.05 -0.06,-0.09 -0.08,-0.14c-0.72,-1.26 -1.77,-2.31 -3.03,-3.03c-0.05,-0.03 -0.09,-0.06 -0.14,-0.08c-0.15,-0.08 -0.3,-0.15 -0.45,-0.22c-0.09,-0.04 -0.18,-0.09 -0.27,-0.13c-0.11,-0.05 -0.23,-0.09 -0.35,-0.13c-0.14,-0.05 -0.27,-0.1 -0.41,-0.14c-0.08,-0.02 -0.16,-0.04 -0.23,-0.06c-0.18,-0.05 -0.36,-0.1 -0.54,-0.13c-0.03,-0.01 -0.07,-0.01 -0.1,-0.01c-0.95,-0.17 -1.93,-0.17 -2.88,0c-0.03,0.01 -0.07,0.01 -0.1,0.01c-0.18,0.04 -0.36,0.08 -0.54,0.13C9.85,4.3 9.77,4.32 9.69,4.34C9.55,4.38 9.42,4.44 9.28,4.49C9.17,4.53 9.05,4.57 8.93,4.61C8.84,4.65 8.75,4.7 8.66,4.74c-0.15,0.07 -0.3,0.14 -0.45,0.22C8.16,4.98 8.12,5.01 8.07,5.04C5.64,6.42 4,9.02 4,12c0,2.74 1.39,5.16 3.49,6.6c0.01,0.01 0.03,0.02 0.04,0.03c0.16,0.11 0.33,0.2 0.49,0.3c0.06,0.04 0.12,0.08 0.19,0.11c0.13,0.07 0.27,0.13 0.4,0.19c0.11,0.05 0.21,0.1 0.32,0.15c0.1,0.04 0.2,0.07 0.29,0.11c0.15,0.06 0.31,0.11 0.46,0.16c0.05,0.02 0.11,0.03 0.17,0.04c1.11,0.31 2.27,0.35 3.4,0.18l0.35,1.98c-0.54,0.09 -1.08,0.14 -1.62,0.14V22c-0.65,0 -1.28,-0.07 -1.9,-0.19c-0.01,0 -0.01,0 -0.02,0c-0.25,-0.05 -0.49,-0.11 -0.73,-0.18c-0.08,-0.02 -0.16,-0.04 -0.23,-0.06c-0.19,-0.06 -0.37,-0.13 -0.55,-0.19c-0.13,-0.05 -0.26,-0.09 -0.39,-0.14c-0.13,-0.05 -0.25,-0.12 -0.38,-0.18c-0.18,-0.08 -0.35,-0.16 -0.53,-0.25c-0.07,-0.04 -0.14,-0.08 -0.21,-0.13c-0.22,-0.12 -0.43,-0.25 -0.64,-0.39c-0.01,-0.01 -0.02,-0.02 -0.04,-0.03c-0.51,-0.35 -1,-0.74 -1.45,-1.2l0,0C3.12,17.26 2,14.76 2,12c0,-2.76 1.12,-5.26 2.93,-7.07l0,0c0.45,-0.45 0.93,-0.84 1.44,-1.19C6.39,3.73 6.4,3.72 6.42,3.71c0.2,-0.14 0.41,-0.26 0.62,-0.38c0.08,-0.05 0.15,-0.09 0.23,-0.14c0.17,-0.09 0.33,-0.16 0.5,-0.24c0.13,-0.06 0.27,-0.13 0.4,-0.19C8.3,2.71 8.42,2.67 8.55,2.63c0.19,-0.07 0.38,-0.14 0.58,-0.2c0.07,-0.02 0.14,-0.03 0.21,-0.05C10.18,2.14 11.07,2 12,2c0.65,0 1.29,0.07 1.91,0.19c0,0 0,0 0,0c0.25,0.05 0.5,0.11 0.75,0.18c0.07,0.02 0.14,0.03 0.22,0.06c0.19,0.06 0.38,0.13 0.57,0.2c0.12,0.05 0.25,0.09 0.37,0.14c0.14,0.06 0.27,0.12 0.4,0.18c0.17,0.08 0.34,0.16 0.51,0.25c0.08,0.04 0.15,0.09 0.23,0.14c0.21,0.12 0.42,0.24 0.62,0.38c0.01,0.01 0.03,0.02 0.04,0.03c0.51,0.35 0.99,0.74 1.45,1.19c0.24,0.24 0.47,0.49 0.68,0.75c0.04,0.04 0.06,0.09 0.1,0.13c0.17,0.22 0.34,0.45 0.5,0.68c0.01,0.01 0.02,0.03 0.03,0.04c0.69,1.05 1.17,2.21 1.42,3.44c0,0 0,0.01 0,0.01c0.06,0.29 0.1,0.58 0.13,0.87c0.01,0.04 0.01,0.09 0.02,0.13C21.98,11.32 22,11.66 22,12zM18.5,15c-1.93,0 -3.5,1.57 -3.5,3.5s1.57,3.5 3.5,3.5s3.5,-1.57 3.5,-3.5S20.43,15 18.5,15z"/>
+</vector>
\ No newline at end of file
diff --git a/res/layout/bubble_preference.xml b/res/layout/bubble_preference.xml
new file mode 100644
index 0000000..8a64716
--- /dev/null
+++ b/res/layout/bubble_preference.xml
@@ -0,0 +1,128 @@
+<?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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    android:padding="@dimen/notification_importance_toggle_marginTop"
+    android:orientation="vertical">
+
+    <!-- If bubbles is managed by the admin this is used to inform the user. -->
+    <TextView
+        android:id="@android:id/summary"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="@dimen/notification_importance_button_padding"
+        android:textAppearance="@style/TextAppearance.Small"
+        android:visibility="gone"
+        />
+
+    <com.android.settings.notification.NotificationButtonRelativeLayout
+        android:id="@+id/bubble_all"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="@dimen/notification_importance_button_padding"
+        android:clickable="true"
+        android:focusable="true">
+        <ImageView
+            android:id="@+id/bubble_all_icon"
+            android:src="@drawable/ic_bubble_all"
+            android:background="@android:color/transparent"
+            android:layout_gravity="center"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:clickable="false"
+            android:focusable="false"/>
+        <TextView
+            android:id="@+id/bubble_all_label"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:maxLines="2"
+            android:clickable="false"
+            android:focusable="false"
+            android:layout_toEndOf="@id/bubble_all_icon"
+            android:layout_marginStart="@dimen/notification_importance_drawable_padding"
+            android:textAppearance="@style/TextAppearance.NotificationImportanceButton.Unselected"
+            android:text="@string/bubble_app_setting_all"/>
+    </com.android.settings.notification.NotificationButtonRelativeLayout>
+
+    <com.android.settings.notification.NotificationButtonRelativeLayout
+        android:id="@+id/bubble_selected"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="@dimen/notification_importance_button_padding"
+        android:layout_marginTop="@dimen/notification_importance_button_separation"
+        android:clickable="true"
+        android:focusable="true">
+        <ImageView
+            android:id="@+id/bubble_selected_icon"
+            android:src="@drawable/ic_bubble_selected"
+            android:background="@android:color/transparent"
+            android:layout_gravity="center"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:clickable="false"
+            android:focusable="false"/>
+        <TextView
+            android:id="@+id/bubble_selected_label"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:maxLines="2"
+            android:clickable="false"
+            android:focusable="false"
+            android:layout_toEndOf="@id/bubble_selected_icon"
+            android:layout_marginStart="@dimen/notification_importance_drawable_padding"
+            android:textAppearance="@style/TextAppearance.NotificationImportanceButton.Unselected"
+            android:text="@string/bubble_app_setting_selected"/>
+    </com.android.settings.notification.NotificationButtonRelativeLayout>
+
+    <com.android.settings.notification.NotificationButtonRelativeLayout
+        android:id="@+id/bubble_none"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="@dimen/notification_importance_button_padding"
+        android:layout_marginTop="@dimen/notification_importance_button_separation"
+        android:clickable="true"
+        android:focusable="true">
+        <ImageView
+            android:id="@+id/bubble_none_icon"
+            android:src="@drawable/ic_bubble_none"
+            android:background="@android:color/transparent"
+            android:layout_gravity="center"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:clickable="false"
+            android:focusable="false"/>
+        <TextView
+            android:id="@+id/bubble_none_label"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:maxLines="2"
+            android:clickable="false"
+            android:focusable="false"
+            android:layout_toEndOf="@id/bubble_none_icon"
+            android:layout_marginStart="@dimen/notification_importance_drawable_padding"
+            android:textAppearance="@style/TextAppearance.NotificationImportanceButton.Unselected"
+            android:text="@string/bubble_app_setting_none"/>
+    </com.android.settings.notification.NotificationButtonRelativeLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/raw-night/bubble_notification_animation.mp4 b/res/raw-night/bubble_notification_animation.mp4
new file mode 100644
index 0000000..1a25e93
--- /dev/null
+++ b/res/raw-night/bubble_notification_animation.mp4
Binary files differ
diff --git a/res/raw/bubble_notification_animation.mp4 b/res/raw/bubble_notification_animation.mp4
new file mode 100644
index 0000000..2994548
--- /dev/null
+++ b/res/raw/bubble_notification_animation.mp4
Binary files differ
diff --git a/res/xml/app_bubble_notification_settings.xml b/res/xml/app_bubble_notification_settings.xml
index 8d97f8f..3f52ad3 100644
--- a/res/xml/app_bubble_notification_settings.xml
+++ b/res/xml/app_bubble_notification_settings.xml
@@ -13,22 +13,18 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
                   xmlns:settings="http://schemas.android.com/apk/res-auto"
                   android:title="@string/bubbles_app_toggle_title"
                   android:key="app_bubble_notification_settings">
+
         <com.android.settingslib.widget.LayoutPreference
             android:key="pref_app_header"
             android:layout="@layout/settings_entity_header"/>
 
-        <com.android.settingslib.RestrictedSwitchPreference
+        <com.android.settings.notification.app.BubblePreference
             android:key="bubble_pref"
-            android:title="@string/notification_bubbles_title"/>
-
-        <com.android.settingslib.widget.FooterPreference
-            android:key="notification_bubbles_footer"
-            android:title="@string/bubbles_feature_education"
-            android:selectable="false" />
+            android:title="@string/notification_bubbles_title"
+            settings:allowDividerBelow="false"/>
 
 </PreferenceScreen>
diff --git a/res/xml/app_notification_settings.xml b/res/xml/app_notification_settings.xml
index ceb08a2..f0200ce 100644
--- a/res/xml/app_notification_settings.xml
+++ b/res/xml/app_notification_settings.xml
@@ -29,6 +29,14 @@
     <com.android.settings.notification.app.NotificationFooterPreference
         android:key="block_desc" />
 
+    <!--Bubbles -->
+    <Preference
+        android:key="bubble_pref_link"
+        android:title="@string/notification_bubbles_title"
+        android:icon="@drawable/ic_create_bubble"
+        settings:controller="com.android.settings.notification.app.BubbleSummaryPreferenceController">
+    </Preference>
+
     <!-- Conversations added here -->
     <PreferenceCategory
         android:title="@string/conversations_category_title"
@@ -74,10 +82,6 @@
             android:order="1001"
             settings:restrictedSwitchSummary="@string/enabled_by_admin" />
         <Preference
-            android:key="bubble_link_pref"
-            android:title="@string/notification_bubbles_title"
-            android:order="1002" />
-        <Preference
             android:key="app_link"
             android:order="1003"
             android:title="@string/app_settings_link" />
diff --git a/res/xml/bubble_notification_settings.xml b/res/xml/bubble_notification_settings.xml
new file mode 100644
index 0000000..4dc3e24
--- /dev/null
+++ b/res/xml/bubble_notification_settings.xml
@@ -0,0 +1,38 @@
+<?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"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/bubbles_app_toggle_title"
+                  android:key="bubble_notification_settings">
+
+        <com.android.settings.widget.VideoPreference
+            android:key="bubbles_illustration"
+            android:title="@string/summary_placeholder"
+            settings:animation="@raw/bubble_notification_animation"
+            settings:controller="com.android.settings.widget.VideoPreferenceController"
+            android:persistent="false" />
+
+        <!-- Notification bubbles -->
+        <SwitchPreference
+            android:key="global_notification_bubbles"
+            android:icon="@drawable/ic_create_bubble"
+            settings:allowDividerAbove="true"
+            android:title="@string/notifications_bubble_setting_title"
+            android:summary="@string/notifications_bubble_setting_description"
+            settings:controller="com.android.settings.notification.BubbleNotificationPreferenceController"/>
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/configure_notification_settings.xml b/res/xml/configure_notification_settings.xml
index 3dcddc8..cb8357b 100644
--- a/res/xml/configure_notification_settings.xml
+++ b/res/xml/configure_notification_settings.xml
@@ -50,15 +50,23 @@
         </Preference>
     </PreferenceCategory>
 
-    <PreferenceCategory
+    <Preference
         android:key="conversations"
         android:title="@string/conversations_category_title"
-        android:order="5">
-        <Preference
-            android:key="manage_conversations"
-            android:title="@string/manage_conversations"
-            android:fragment="com.android.settings.notification.app.ConversationListSettings"/>
-    </PreferenceCategory>
+        settings:allowDividerAbove="true"
+        android:summary="@string/manage_conversations"
+        android:order="6"
+        android:fragment="com.android.settings.notification.app.ConversationListSettings"
+        />
+
+    <Preference
+        android:key="notification_bubbles"
+        android:title="@string/notification_bubbles_title"
+        android:summary="@string/notifications_bubble_setting_on_summary"
+        android:order="7"
+        settings:controller="com.android.settings.notification.BubbleSummaryNotificationPreferenceController"
+        android:fragment="com.android.settings.notification.BubbleNotificationSettings"
+        />
 
     <PreferenceCategory
         android:key="configure_notifications_lock"
diff --git a/src/com/android/settings/development/BubbleGlobalPreferenceController.java b/src/com/android/settings/development/BubbleGlobalPreferenceController.java
deleted file mode 100644
index 2f22d09..0000000
--- a/src/com/android/settings/development/BubbleGlobalPreferenceController.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2019 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.development;
-
-import android.content.Context;
-import android.provider.Settings;
-
-import androidx.annotation.VisibleForTesting;
-import androidx.preference.Preference;
-import androidx.preference.SwitchPreference;
-
-import com.android.settings.core.PreferenceControllerMixin;
-import com.android.settingslib.development.DeveloperOptionsPreferenceController;
-
-public class BubbleGlobalPreferenceController extends DeveloperOptionsPreferenceController
-        implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {
-
-    @VisibleForTesting
-    static final int ON = 1;
-    @VisibleForTesting
-    static final int OFF = 0;
-
-    public BubbleGlobalPreferenceController(Context context) {
-        super(context);
-    }
-
-    @Override
-    public String getPreferenceKey() {
-        return Settings.Global.NOTIFICATION_BUBBLES;
-    }
-
-    @Override
-    public boolean onPreferenceChange(Preference preference, Object newValue) {
-        writeSetting((boolean) newValue);
-        return true;
-    }
-
-    @Override
-    public void updateState(Preference preference) {
-        ((SwitchPreference) mPreference).setChecked(isEnabled());
-    }
-
-    @Override
-    protected void onDeveloperOptionsSwitchDisabled() {
-        super.onDeveloperOptionsSwitchDisabled();
-        writeSetting(false /* isEnabled */);
-        updateState(mPreference);
-    }
-
-    private boolean isEnabled() {
-        return Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.NOTIFICATION_BUBBLES, OFF) == ON;
-    }
-
-    private void writeSetting(boolean isEnabled) {
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.NOTIFICATION_BUBBLES, isEnabled ? ON : OFF);
-    }
-}
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 66b93e1..d2d7372 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -508,7 +508,6 @@
         controllers.add(new DesktopModePreferenceController(context));
         controllers.add(new SizeCompatFreeformPreferenceController(context));
         controllers.add(new ShortcutManagerThrottlingPreferenceController(context));
-        controllers.add(new BubbleGlobalPreferenceController(context));
         controllers.add(new EnableGnssRawMeasFullTrackingPreferenceController(context));
         controllers.add(new DefaultLaunchPreferenceController(context, "running_apps"));
         controllers.add(new DefaultLaunchPreferenceController(context, "demo_mode"));
diff --git a/src/com/android/settings/notification/BubbleNotificationPreferenceController.java b/src/com/android/settings/notification/BubbleNotificationPreferenceController.java
new file mode 100644
index 0000000..0fa480c
--- /dev/null
+++ b/src/com/android/settings/notification/BubbleNotificationPreferenceController.java
@@ -0,0 +1,132 @@
+/*
+ * 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.provider.Settings.Global.NOTIFICATION_BUBBLES;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.core.TogglePreferenceController;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+
+/**
+ * Feature level screen for bubbles, available through notification menu.
+ * Allows user to turn bubbles on or off for the device.
+ */
+public class BubbleNotificationPreferenceController extends TogglePreferenceController
+        implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener,
+        LifecycleObserver, OnResume, OnPause {
+
+    private static final String TAG = "BubbleNotifPrefContr";
+
+    @VisibleForTesting
+    static final int ON = 1;
+    @VisibleForTesting
+    static final int OFF = 0;
+
+    private SettingObserver mSettingObserver;
+
+    public BubbleNotificationPreferenceController(Context context, String preferenceKey) {
+        super(context, preferenceKey);
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        Preference preference = screen.findPreference(getPreferenceKey());
+        if (preference != null) {
+            mSettingObserver = new SettingObserver(preference);
+        }
+    }
+
+    @Override
+    public void onResume() {
+        if (mSettingObserver != null) {
+            mSettingObserver.register(mContext.getContentResolver(), true /* register */);
+        }
+    }
+
+    @Override
+    public void onPause() {
+        if (mSettingObserver != null) {
+            mSettingObserver.register(mContext.getContentResolver(), false /* register */);
+        }
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+
+    @Override
+    public boolean isChecked() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                NOTIFICATION_BUBBLES, ON) == ON;
+    }
+
+    @Override
+    public boolean setChecked(boolean isChecked) {
+        return Settings.Global.putInt(mContext.getContentResolver(),
+                NOTIFICATION_BUBBLES, isChecked ? ON : OFF);
+    }
+
+    @Override
+    public boolean isSliceable() {
+        return false;
+    }
+
+    class SettingObserver extends ContentObserver {
+
+        private final Uri NOTIFICATION_BUBBLES_URI =
+                Settings.Global.getUriFor(NOTIFICATION_BUBBLES);
+
+        private final Preference mPreference;
+
+        SettingObserver(Preference preference) {
+            super(new Handler());
+            mPreference = preference;
+        }
+
+        public void register(ContentResolver cr, boolean register) {
+            if (register) {
+                cr.registerContentObserver(NOTIFICATION_BUBBLES_URI, false, this);
+            } else {
+                cr.unregisterContentObserver(this);
+            }
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            super.onChange(selfChange, uri);
+            if (NOTIFICATION_BUBBLES_URI.equals(uri)) {
+                updateState(mPreference);
+            }
+        }
+    }
+}
diff --git a/src/com/android/settings/notification/BubbleNotificationSettings.java b/src/com/android/settings/notification/BubbleNotificationSettings.java
new file mode 100644
index 0000000..6e04683
--- /dev/null
+++ b/src/com/android/settings/notification/BubbleNotificationSettings.java
@@ -0,0 +1,52 @@
+/*
+ * 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 android.app.settings.SettingsEnums;
+
+import com.android.settings.R;
+import com.android.settings.core.OnActivityResultListener;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+@SearchIndexable
+public class BubbleNotificationSettings extends DashboardFragment implements
+        OnActivityResultListener {
+    private static final String TAG = "BubbleNotiSettings";
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.BUBBLE_SETTINGS;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.bubble_notification_settings;
+    }
+
+    /**
+     * For Search.
+     */
+    public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider(R.xml.bubble_notification_settings);
+}
diff --git a/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceController.java b/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceController.java
new file mode 100644
index 0000000..f123c51
--- /dev/null
+++ b/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceController.java
@@ -0,0 +1,58 @@
+/*
+ * 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.provider.Settings.Global.NOTIFICATION_BUBBLES;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+/**
+ * Summary of the feature setting for bubbles, available through notification menu.
+ */
+public class BubbleSummaryNotificationPreferenceController extends BasePreferenceController {
+
+    @VisibleForTesting
+    static final int ON = 1;
+
+    public BubbleSummaryNotificationPreferenceController(Context context, String preferenceKey) {
+        super(context, preferenceKey);
+    }
+
+    @Override
+    public CharSequence getSummary() {
+        return mContext.getString(
+                areBubblesEnabled()
+                        ? R.string.notifications_bubble_setting_on_summary
+                        : R.string.switch_off_text);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+
+    private boolean areBubblesEnabled() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                NOTIFICATION_BUBBLES, ON) == ON;
+    }
+}
diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java
index fb0a438..b172879 100644
--- a/src/com/android/settings/notification/NotificationBackend.java
+++ b/src/com/android/settings/notification/NotificationBackend.java
@@ -24,6 +24,7 @@
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
 import android.app.NotificationHistory;
+import android.app.NotificationManager;
 import android.app.role.RoleManager;
 import android.app.usage.IUsageStatsManager;
 import android.app.usage.UsageEvents;
@@ -81,7 +82,7 @@
         row.icon = IconDrawableFactory.newInstance(context).getBadgedIcon(app);
         row.banned = getNotificationsBanned(row.pkg, row.uid);
         row.showBadge = canShowBadge(row.pkg, row.uid);
-        row.allowBubbles = canBubble(row.pkg, row.uid);
+        row.bubblePreference = getBubblePreference(row.pkg, row.uid);
         row.userId = UserHandle.getUserId(row.uid);
         row.blockedChannelCount = getBlockedChannelCount(row.pkg, row.uid);
         row.channelCount = getChannelCount(row.pkg, row.uid);
@@ -192,18 +193,18 @@
         }
     }
 
-    public boolean canBubble(String pkg, int uid) {
+    public int getBubblePreference(String pkg, int uid) {
         try {
-            return sINM.areBubblesAllowedForPackage(pkg, uid);
+            return sINM.getBubblePreferenceForPackage(pkg, uid);
         } catch (Exception e) {
             Log.w(TAG, "Error calling NoMan", e);
-            return false;
+            return -1;
         }
     }
 
-    public boolean setAllowBubbles(String pkg, int uid, boolean allow) {
+    public boolean setAllowBubbles(String pkg, int uid, int preference) {
         try {
-            sINM.setBubblesAllowed(pkg, uid, allow);
+            sINM.setBubblesAllowed(pkg, uid, preference);
             return true;
         } catch (Exception e) {
             Log.w(TAG, "Error calling NoMan", e);
@@ -563,7 +564,7 @@
         public boolean systemApp;
         public boolean lockedImportance;
         public boolean showBadge;
-        public boolean allowBubbles;
+        public int bubblePreference = NotificationManager.BUBBLE_PREFERENCE_NONE;
         public int userId;
         public int blockedChannelCount;
         public int channelCount;
diff --git a/src/com/android/settings/notification/app/AppBubbleNotificationSettings.java b/src/com/android/settings/notification/app/AppBubbleNotificationSettings.java
index 0ed1b84..5026a26 100644
--- a/src/com/android/settings/notification/app/AppBubbleNotificationSettings.java
+++ b/src/com/android/settings/notification/app/AppBubbleNotificationSettings.java
@@ -30,6 +30,9 @@
 import java.util.ArrayList;
 import java.util.List;
 
+/**
+ * App level settings for bubbles.
+ */
 @SearchIndexable
 public class AppBubbleNotificationSettings extends NotificationSettings implements
         GlobalBubblePermissionObserverMixin.Listener {
diff --git a/src/com/android/settings/notification/app/AppNotificationSettings.java b/src/com/android/settings/notification/app/AppNotificationSettings.java
index d9f4239..a422841 100644
--- a/src/com/android/settings/notification/app/AppNotificationSettings.java
+++ b/src/com/android/settings/notification/app/AppNotificationSettings.java
@@ -22,6 +22,10 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceScreen;
+
 import com.android.internal.widget.LockPatternUtils;
 import com.android.settings.R;
 import com.android.settingslib.core.AbstractPreferenceController;
@@ -29,10 +33,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import androidx.preference.Preference;
-import androidx.preference.PreferenceGroup;
-import androidx.preference.PreferenceScreen;
-
 /** These settings are per app, so should not be returned in global search results. */
 public class AppNotificationSettings extends NotificationSettings {
     private static final String TAG = "AppNotificationSettings";
@@ -41,8 +41,7 @@
     private static String KEY_ADVANCED_CATEGORY = "app_advanced";
     private static String KEY_BADGE = "badge";
     private static String KEY_APP_LINK = "app_link";
-    private static String KEY_BUBBLE = "bubble_link_pref";
-    private static String[] LEGACY_NON_ADVANCED_KEYS = {KEY_BADGE, KEY_APP_LINK, KEY_BUBBLE};
+    private static String[] LEGACY_NON_ADVANCED_KEYS = {KEY_BADGE, KEY_APP_LINK};
 
     @Override
     public int getMetricsCategory() {
@@ -121,9 +120,9 @@
         mControllers.add(new DescriptionPreferenceController(context));
         mControllers.add(new NotificationsOffPreferenceController(context));
         mControllers.add(new DeletedChannelsPreferenceController(context, mBackend));
-        mControllers.add(new BubbleSummaryPreferenceController(context, mBackend));
         mControllers.add(new ChannelListPreferenceController(context, mBackend));
         mControllers.add(new AppConversationListPreferenceController(context, mBackend));
+        mControllers.add(new BubbleSummaryPreferenceController(context, mBackend));
         return new ArrayList<>(mControllers);
     }
 }
diff --git a/src/com/android/settings/notification/app/BubblePreference.java b/src/com/android/settings/notification/app/BubblePreference.java
new file mode 100644
index 0000000..679b663
--- /dev/null
+++ b/src/com/android/settings/notification/app/BubblePreference.java
@@ -0,0 +1,172 @@
+/*
+ * 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.BUBBLE_PREFERENCE_ALL;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.Utils;
+import com.android.settingslib.R;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedPreferenceHelper;
+
+/**
+ * A tri-state preference allowing a user to specify what gets to bubble.
+ */
+public class BubblePreference extends Preference implements View.OnClickListener {
+    RestrictedPreferenceHelper mHelper;
+
+    private int mSelectedPreference;
+
+    private Context mContext;
+    private Drawable mSelectedBackground;
+    private Drawable mUnselectedBackground;
+
+    private ButtonViewHolder mBubbleAllButton;
+    private ButtonViewHolder mBubbleSelectedButton;
+    private ButtonViewHolder mBubbleNoneButton;
+
+    public BubblePreference(Context context) {
+        this(context, null);
+    }
+
+    public BubblePreference(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public BubblePreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public BubblePreference(Context context, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mHelper = new RestrictedPreferenceHelper(context, this, attrs);
+        mHelper.useAdminDisabledSummary(true);
+        mContext = context;
+        mSelectedBackground = mContext.getDrawable(R.drawable.button_border_selected);
+        mUnselectedBackground = mContext.getDrawable(R.drawable.button_border_unselected);
+        setLayoutResource(R.layout.bubble_preference);
+    }
+
+    public void setSelectedPreference(int preference) {
+        mSelectedPreference = preference;
+    }
+
+    public int getSelectedPreference() {
+        return mSelectedPreference;
+    }
+
+    public void setDisabledByAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
+        if (mHelper.setDisabledByAdmin(admin)) {
+            notifyChanged();
+        }
+    }
+
+    @Override
+    public void onBindViewHolder(final PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+
+        final boolean disabledByAdmin = mHelper.isDisabledByAdmin();
+        View summary = holder.findViewById(android.R.id.summary);
+        if (disabledByAdmin) {
+            mHelper.onBindViewHolder(holder);
+            summary.setVisibility(View.VISIBLE);
+        } else {
+            summary.setVisibility(View.GONE);
+        }
+        holder.itemView.setClickable(false);
+
+        View bubbleAll = holder.findViewById(R.id.bubble_all);
+        ImageView bubbleAllImage = (ImageView) holder.findViewById(R.id.bubble_all_icon);
+        TextView bubbleAllText = (TextView) holder.findViewById(R.id.bubble_all_label);
+        mBubbleAllButton = new ButtonViewHolder(bubbleAll, bubbleAllImage, bubbleAllText,
+                BUBBLE_PREFERENCE_ALL);
+        mBubbleAllButton.setSelected(mContext, mSelectedPreference == BUBBLE_PREFERENCE_ALL);
+        bubbleAll.setTag(BUBBLE_PREFERENCE_ALL);
+        bubbleAll.setOnClickListener(this);
+        bubbleAll.setVisibility(disabledByAdmin ? View.GONE : View.VISIBLE);
+
+        View bubbleSelected = holder.findViewById(R.id.bubble_selected);
+        ImageView bubbleSelectedImage = (ImageView) holder.findViewById(R.id.bubble_selected_icon);
+        TextView bubbleSelectedText = (TextView) holder.findViewById(R.id.bubble_selected_label);
+        mBubbleSelectedButton = new ButtonViewHolder(bubbleSelected, bubbleSelectedImage,
+                bubbleSelectedText, BUBBLE_PREFERENCE_SELECTED);
+        mBubbleSelectedButton.setSelected(mContext,
+                mSelectedPreference == BUBBLE_PREFERENCE_SELECTED);
+        bubbleSelected.setTag(BUBBLE_PREFERENCE_SELECTED);
+        bubbleSelected.setOnClickListener(this);
+        bubbleSelected.setVisibility(disabledByAdmin ? View.GONE : View.VISIBLE);
+
+        View bubbleNone = holder.findViewById(R.id.bubble_none);
+        ImageView bubbleNoneImage = (ImageView) holder.findViewById(R.id.bubble_none_icon);
+        TextView bubbleNoneText = (TextView) holder.findViewById(R.id.bubble_none_label);
+        mBubbleNoneButton = new ButtonViewHolder(bubbleNone, bubbleNoneImage, bubbleNoneText,
+                BUBBLE_PREFERENCE_NONE);
+        mBubbleNoneButton.setSelected(mContext, mSelectedPreference == BUBBLE_PREFERENCE_NONE);
+        bubbleNone.setTag(BUBBLE_PREFERENCE_NONE);
+        bubbleNone.setOnClickListener(this);
+        bubbleNone.setVisibility(disabledByAdmin ? View.GONE : View.VISIBLE);
+    }
+
+    @Override
+    public void onClick(View v) {
+        final int selected = (int) v.getTag();
+        callChangeListener(selected);
+
+        mBubbleAllButton.setSelected(mContext, selected == BUBBLE_PREFERENCE_ALL);
+        mBubbleSelectedButton.setSelected(mContext, selected == BUBBLE_PREFERENCE_SELECTED);
+        mBubbleNoneButton.setSelected(mContext, selected == BUBBLE_PREFERENCE_NONE);
+    }
+
+    private class ButtonViewHolder {
+        private View mView;
+        private ImageView mImageView;
+        private TextView mTextView;
+        private int mId;
+
+        ButtonViewHolder(View v, ImageView iv, TextView tv, int identifier) {
+            mView = v;
+            mImageView = iv;
+            mTextView = tv;
+            mId = identifier;
+        }
+
+        void setSelected(Context context, boolean selected) {
+            mView.setBackground(selected ?  mSelectedBackground : mUnselectedBackground);
+            mView.setSelected(selected);
+
+            ColorStateList stateList = selected
+                    ? Utils.getColorAccent(context)
+                    : Utils.getColorAttr(context, android.R.attr.textColorPrimary);
+            mImageView.setImageTintList(stateList);
+            mTextView.setTextColor(stateList);
+        }
+    }
+}
diff --git a/src/com/android/settings/notification/app/BubblePreferenceController.java b/src/com/android/settings/notification/app/BubblePreferenceController.java
index d33ba7e..3255192 100644
--- a/src/com/android/settings/notification/app/BubblePreferenceController.java
+++ b/src/com/android/settings/notification/app/BubblePreferenceController.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.notification.app;
 
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
 import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
 
 import android.annotation.Nullable;
@@ -26,11 +27,14 @@
 import androidx.fragment.app.FragmentManager;
 import androidx.preference.Preference;
 
-import com.android.settings.R;
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settings.notification.NotificationBackend;
 import com.android.settingslib.RestrictedSwitchPreference;
 
+/**
+ * Preference controller for Bubbles. This is used as the app-specific page and conversation
+ * settings.
+ */
 public class BubblePreferenceController extends NotificationPreferenceController
         implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
 
@@ -74,40 +78,49 @@
         return true;
     }
 
+    @Override
     public void updateState(Preference preference) {
-        if (mAppRow != null) {
-            RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
+        if (mIsAppPage && mAppRow != null) {
+            // We're on the app specific bubble page which displays a tri-state
+            int backEndPref = mAppRow.bubblePreference;
+            BubblePreference pref = (BubblePreference) preference;
             pref.setDisabledByAdmin(mAdmin);
-            if (mChannel != null) {
-                pref.setChecked(mChannel.canBubble() && isGloballyEnabled());
-                pref.setEnabled(!pref.isDisabledByAdmin());
+            if (!isGloballyEnabled()) {
+                pref.setSelectedPreference(BUBBLE_PREFERENCE_NONE);
             } else {
-                pref.setChecked(mAppRow.allowBubbles && isGloballyEnabled());
-                pref.setSummary(mContext.getString(
-                        R.string.bubbles_app_toggle_summary, mAppRow.label));
+                pref.setSelectedPreference(backEndPref);
             }
+        } else if (mChannel != null) {
+            // We're on the channel specific notification page which displays a toggle.
+            RestrictedSwitchPreference switchpref = (RestrictedSwitchPreference) preference;
+            switchpref.setDisabledByAdmin(mAdmin);
+            switchpref.setChecked(mChannel.canBubble() && isGloballyEnabled());
         }
     }
 
     @Override
     public boolean onPreferenceChange(Preference preference, Object newValue) {
-        final boolean value = (Boolean) newValue && isGloballyEnabled();
         if (mChannel != null) {
-            mChannel.setAllowBubbles(value);
+            // Channel page is toggle
+            mChannel.setAllowBubbles((boolean) newValue);
             saveChannel();
-            return true;
-        } else if (mAppRow != null && mFragmentManager != null) {
-            RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
-            // if the global setting is off, toggling app level permission requires extra
-            // confirmation
-            if (!isGloballyEnabled() && !pref.isChecked()) {
-                new BubbleWarningDialogFragment()
-                        .setPkgInfo(mAppRow.pkg, mAppRow.uid)
-                        .show(mFragmentManager, "dialog");
-                return false;
-            } else {
-                mAppRow.allowBubbles = value;
-                mBackend.setAllowBubbles(mAppRow.pkg, mAppRow.uid, value);
+        } else if (mIsAppPage) {
+            // App page is bubble preference
+            BubblePreference pref = (BubblePreference) preference;
+            if (mAppRow != null && mFragmentManager != null) {
+                final int value = (int) newValue;
+                if (!isGloballyEnabled()
+                        && pref.getSelectedPreference() == BUBBLE_PREFERENCE_NONE) {
+                    // if the global setting is off, toggling app level permission requires extra
+                    // confirmation
+                    new BubbleWarningDialogFragment()
+                            .setPkgPrefInfo(mAppRow.pkg, mAppRow.uid, value)
+                            .show(mFragmentManager, "dialog");
+                    return false;
+                } else {
+                    mAppRow.bubblePreference = value;
+                    mBackend.setAllowBubbles(mAppRow.pkg, mAppRow.uid, value);
+                }
             }
         }
         return true;
@@ -118,21 +131,26 @@
                 NOTIFICATION_BUBBLES, SYSTEM_WIDE_OFF) == SYSTEM_WIDE_ON;
     }
 
-    // Used in app level prompt that confirms the user is ok with turning on bubbles
-    // globally. If they aren't, undo what
+    /**
+     * Used in app level prompt that confirms the user is ok with turning on bubbles
+     * globally. If they aren't, undo that.
+     */
     public static void revertBubblesApproval(Context mContext, String pkg, int uid) {
         NotificationBackend backend = new NotificationBackend();
-        backend.setAllowBubbles(pkg, uid, false);
+        backend.setAllowBubbles(pkg, uid, BUBBLE_PREFERENCE_NONE);
+
         // changing the global settings will cause the observer on the host page to reload
         // correct preference state
         Settings.Global.putInt(mContext.getContentResolver(),
                 NOTIFICATION_BUBBLES, SYSTEM_WIDE_OFF);
     }
 
-    // Apply global bubbles approval
-    public static void applyBubblesApproval(Context mContext, String pkg, int uid) {
+    /**
+     * Apply global bubbles approval
+     */
+    public static void applyBubblesApproval(Context mContext, String pkg, int uid, int pref) {
         NotificationBackend backend = new NotificationBackend();
-        backend.setAllowBubbles(pkg, uid, true);
+        backend.setAllowBubbles(pkg, uid, pref);
         // changing the global settings will cause the observer on the host page to reload
         // correct preference state
         Settings.Global.putInt(mContext.getContentResolver(),
diff --git a/src/com/android/settings/notification/app/BubbleSummaryPreferenceController.java b/src/com/android/settings/notification/app/BubbleSummaryPreferenceController.java
index f13613c..06c6f3a 100644
--- a/src/com/android/settings/notification/app/BubbleSummaryPreferenceController.java
+++ b/src/com/android/settings/notification/app/BubbleSummaryPreferenceController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -16,39 +16,35 @@
 
 package com.android.settings.notification.app;
 
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
 import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
 
-import android.app.settings.SettingsEnums;
 import android.content.Context;
-import android.os.Bundle;
+import android.content.Intent;
+import android.content.res.Resources;
 import android.provider.Settings;
 
-import com.android.settings.R;
-import com.android.settings.applications.AppInfoBase;
-import com.android.settings.core.SubSettingLauncher;
-import com.android.settings.notification.NotificationBackend;
-
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 
-public class BubbleSummaryPreferenceController extends NotificationPreferenceController {
+import com.android.settings.R;
+import com.android.settings.notification.NotificationBackend;
 
-    private static final String KEY = "bubble_link_pref";
+/**
+ * Summary of the app setting for bubbles, available through app notification settings.
+ */
+public class BubbleSummaryPreferenceController extends NotificationPreferenceController {
+    private static final String KEY = "bubble_pref_link";
+
     @VisibleForTesting
-    static final int SYSTEM_WIDE_ON = 1;
-    @VisibleForTesting
-    static final int SYSTEM_WIDE_OFF = 0;
+    static final int ON = 1;
 
     public BubbleSummaryPreferenceController(Context context, NotificationBackend backend) {
         super(context, backend);
     }
 
     @Override
-    public String getPreferenceKey() {
-        return KEY;
-    }
-
-    @Override
     public boolean isAvailable() {
         if (!super.isAvailable()) {
             return false;
@@ -63,45 +59,47 @@
             if (isDefaultChannel()) {
                 return true;
             } else {
-                return mAppRow != null && mAppRow.allowBubbles;
+                return mAppRow != null;
             }
         }
         return isGloballyEnabled();
     }
 
     @Override
+    public String getPreferenceKey() {
+        return KEY;
+    }
+
+    @Override
     public void updateState(Preference preference) {
         super.updateState(preference);
 
         if (mAppRow != null) {
-            Bundle args = new Bundle();
-            args.putString(AppInfoBase.ARG_PACKAGE_NAME, mAppRow.pkg);
-            args.putInt(AppInfoBase.ARG_PACKAGE_UID, mAppRow.uid);
-
-            preference.setIntent(new SubSettingLauncher(mContext)
-                    .setDestination(AppBubbleNotificationSettings.class.getName())
-                    .setArguments(args)
-                    .setSourceMetricsCategory(
-                            SettingsEnums.NOTIFICATION_APP_NOTIFICATION)
-                    .toIntent());
+            final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
+            intent.putExtra(Settings.EXTRA_APP_PACKAGE, mAppRow.pkg);
+            intent.putExtra(Settings.EXTRA_APP_UID, mAppRow.uid);
+            preference.setIntent(intent);
         }
     }
 
     @Override
     public CharSequence getSummary() {
-        boolean canBubble = false;
-        if (mAppRow != null) {
-            if (mChannel != null) {
-               canBubble |= mChannel.canBubble() && isGloballyEnabled();
-            } else {
-               canBubble |= mAppRow.allowBubbles && isGloballyEnabled();
-            }
+        if (mAppRow == null) {
+            return null;
         }
-        return mContext.getString(canBubble ? R.string.switch_on_text : R.string.switch_off_text);
+        int backEndPref = mAppRow.bubblePreference;
+        Resources res = mContext.getResources();
+        if (backEndPref == BUBBLE_PREFERENCE_NONE || !isGloballyEnabled()) {
+            return res.getString(R.string.bubble_app_setting_none);
+        } else if (backEndPref == BUBBLE_PREFERENCE_ALL) {
+            return res.getString(R.string.bubble_app_setting_all);
+        } else {
+            return res.getString(R.string.bubble_app_setting_selected);
+        }
     }
 
     private boolean isGloballyEnabled() {
         return Settings.Global.getInt(mContext.getContentResolver(),
-                NOTIFICATION_BUBBLES, SYSTEM_WIDE_OFF) == SYSTEM_WIDE_ON;
+                NOTIFICATION_BUBBLES, ON) == ON;
     }
 }
diff --git a/src/com/android/settings/notification/app/BubbleWarningDialogFragment.java b/src/com/android/settings/notification/app/BubbleWarningDialogFragment.java
index d3aa758..7d5b24a 100644
--- a/src/com/android/settings/notification/app/BubbleWarningDialogFragment.java
+++ b/src/com/android/settings/notification/app/BubbleWarningDialogFragment.java
@@ -27,6 +27,7 @@
 public class BubbleWarningDialogFragment extends InstrumentedDialogFragment {
     static final String KEY_PKG = "p";
     static final String KEY_UID = "u";
+    static final String KEY_SELECTED_PREFERENCE = "pref";
 
 
     @Override
@@ -34,10 +35,11 @@
         return SettingsEnums.DIALOG_APP_BUBBLE_SETTINGS;
     }
 
-    public BubbleWarningDialogFragment setPkgInfo(String pkg, int uid) {
+    public BubbleWarningDialogFragment setPkgPrefInfo(String pkg, int uid, int preference) {
         Bundle args = new Bundle();
         args.putString(KEY_PKG, pkg);
         args.putInt(KEY_UID, uid);
+        args.putInt(KEY_SELECTED_PREFERENCE, preference);
         setArguments(args);
         return this;
     }
@@ -48,6 +50,7 @@
         final Bundle args = getArguments();
         final String pkg = args.getString(KEY_PKG);
         final int uid = args.getInt(KEY_UID);
+        final int pref = args.getInt(KEY_SELECTED_PREFERENCE);
 
         final String title =
                 getResources().getString(R.string.bubbles_feature_disabled_dialog_title);
@@ -60,7 +63,7 @@
                 .setPositiveButton(R.string.bubbles_feature_disabled_button_approve,
                         (dialog, id) ->
                                 BubblePreferenceController.applyBubblesApproval(
-                                        getContext(), pkg, uid))
+                                        getContext(), pkg, uid, pref))
                 .setNegativeButton(R.string.bubbles_feature_disabled_button_cancel,
                         (dialog, id) ->
                                 BubblePreferenceController.revertBubblesApproval(
diff --git a/tests/robotests/src/com/android/settings/development/BubbleGlobalPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/BubbleGlobalPreferenceControllerTest.java
deleted file mode 100644
index 9e52a88..0000000
--- a/tests/robotests/src/com/android/settings/development/BubbleGlobalPreferenceControllerTest.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2019 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.development;
-
-import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
-
-import static com.android.settings.development.BubbleGlobalPreferenceController.OFF;
-import static com.android.settings.development.BubbleGlobalPreferenceController.ON;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.provider.Settings;
-
-import androidx.preference.PreferenceScreen;
-import androidx.preference.SwitchPreference;
-
-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;
-
-@RunWith(RobolectricTestRunner.class)
-public class BubbleGlobalPreferenceControllerTest {
-    private Context mContext;
-
-    @Mock
-    private SwitchPreference mPreference;
-    @Mock
-    private PreferenceScreen mPreferenceScreen;
-
-    private BubbleGlobalPreferenceController mController;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mContext = RuntimeEnvironment.application;
-        mController = new BubbleGlobalPreferenceController(mContext);
-        when(mPreferenceScreen.findPreference(mController.getPreferenceKey()))
-                .thenReturn(mPreference);
-        mController.displayPreference(mPreferenceScreen);
-    }
-
-    @Test
-    public void onPreferenceChange_settingEnabled_allowBubbles_shouldBeOn() {
-        mController.onPreferenceChange(mPreference, true /* new value */);
-
-        assertThat(isSettingEnabled()).isTrue();
-    }
-
-    @Test
-    public void onPreferenceChange_settingDisabled_allowBubbles_shouldBeOff() {
-        mController.onPreferenceChange(mPreference, false /* new value */);
-
-        assertThat(isSettingEnabled()).isFalse();
-    }
-
-    @Test
-    public void updateState_settingEnabled_preferenceShouldBeChecked() {
-        Settings.Global.putInt(mContext.getContentResolver(),
-                NOTIFICATION_BUBBLES, 1 /* enabled */);
-        mController.updateState(mPreference);
-
-        verify(mPreference).setChecked(true);
-    }
-
-    @Test
-    public void updateState_settingReset_defaultDisabled_preferenceShouldNotBeChecked() {
-        Settings.Global.putInt(mContext.getContentResolver(),
-                NOTIFICATION_BUBBLES, 0 /* enabled */);
-        mController.updateState(mPreference);
-
-        verify(mPreference).setChecked(false);
-    }
-
-    @Test
-    public void onDeveloperOptionsSwitchDisabled_shouldDisable() {
-        mController.onDeveloperOptionsSwitchDisabled();
-
-        verify(mPreference).setChecked(false);
-        verify(mPreference).setEnabled(false);
-
-        assertThat(isSettingEnabled()).isFalse();
-    }
-
-    private boolean isSettingEnabled() {
-        return Settings.Global.getInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES,
-                OFF /* default off */) == ON;
-    }
-
-}
diff --git a/tests/robotests/src/com/android/settings/notification/BubbleNotificationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BubbleNotificationPreferenceControllerTest.java
new file mode 100644
index 0000000..b2cf55b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/BubbleNotificationPreferenceControllerTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.provider.Settings.Global.NOTIFICATION_BUBBLES;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.notification.BadgingNotificationPreferenceController.OFF;
+import static com.android.settings.notification.BadgingNotificationPreferenceController.ON;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.TwoStatePreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class BubbleNotificationPreferenceControllerTest {
+
+    private Context mContext;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private PreferenceScreen mScreen;
+
+    private BubbleNotificationPreferenceController mController;
+    private Preference mPreference;
+
+    private static final String KEY_NOTIFICATION_BUBBLES = "notification_bubbles";
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mController = new BubbleNotificationPreferenceController(mContext,
+                KEY_NOTIFICATION_BUBBLES);
+        mPreference = new Preference(RuntimeEnvironment.application);
+        mPreference.setKey(mController.getPreferenceKey());
+        when(mScreen.findPreference(mPreference.getKey())).thenReturn(mPreference);
+    }
+
+    @Test
+    public void getAvilabilityStatus_returnsAvailable() {
+        assertEquals(AVAILABLE, mController.getAvailabilityStatus());
+    }
+
+    @Test
+    public void updateState_settingIsOn_preferenceSetChecked() {
+        final TwoStatePreference preference = mock(TwoStatePreference.class);
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, ON);
+
+        mController.updateState(preference);
+
+        verify(preference).setChecked(true);
+    }
+
+    @Test
+    public void updateState_settingIsOff_preferenceSetUnchecked() {
+        final TwoStatePreference preference = mock(TwoStatePreference.class);
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, OFF);
+        assertThat(Settings.Global.getInt(mContext.getContentResolver(),
+                NOTIFICATION_BUBBLES, ON)).isEqualTo(OFF);
+
+        mController.updateState(preference);
+
+        verify(preference).setChecked(false);
+    }
+
+    @Test
+    public void isChecked_settingIsOff_shouldReturnFalse() {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, OFF);
+
+        assertThat(mController.isChecked()).isFalse();
+    }
+
+    @Test
+    public void isChecked_settingIsOn_shouldReturnTrue() {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, ON);
+
+        assertThat(mController.isChecked()).isTrue();
+    }
+
+    @Test
+    public void setChecked_setFalse_disablesSetting() {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, ON);
+
+        mController.setChecked(false);
+        int updatedValue = Settings.Global.getInt(mContext.getContentResolver(),
+                NOTIFICATION_BUBBLES, -1);
+
+        assertThat(updatedValue).isEqualTo(OFF);
+    }
+
+    @Test
+    public void setChecked_setTrue_enablesSetting() {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, OFF);
+
+        mController.setChecked(true);
+        int updatedValue = Settings.Global.getInt(mContext.getContentResolver(),
+                NOTIFICATION_BUBBLES, -1);
+
+        assertThat(updatedValue).isEqualTo(ON);
+    }
+
+    @Test
+    public void isSliceable_returnsFalse() {
+        assertThat(mController.isSliceable()).isFalse();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceControllerTest.java
new file mode 100644
index 0000000..b5f505b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceControllerTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.provider.Settings.Global.NOTIFICATION_BUBBLES;
+
+import static com.android.settings.notification.BadgingNotificationPreferenceController.OFF;
+import static com.android.settings.notification.BadgingNotificationPreferenceController.ON;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class BubbleSummaryNotificationPreferenceControllerTest {
+
+    private Context mContext;
+
+    private BubbleSummaryNotificationPreferenceController mController;
+    private Preference mPreference;
+
+    private static final String KEY_NOTIFICATION_BUBBLES = "notification_bubbles";
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mController = new BubbleSummaryNotificationPreferenceController(mContext,
+                KEY_NOTIFICATION_BUBBLES);
+        mPreference = new Preference(RuntimeEnvironment.application);
+    }
+
+    @Test
+    public void getSummary_NOTIFICATION_BUBBLESIsOff_returnOffString() {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, OFF);
+
+        assertThat(mController.getSummary()).isEqualTo("Off");
+    }
+
+    @Test
+    public void getSummary_NOTIFICATION_BUBBLESIsOff_returnOnString() {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, ON);
+
+        String onString = mContext.getString(R.string.notifications_bubble_setting_on_summary);
+        assertThat(mController.getSummary()).isEqualTo(onString);
+    }
+}
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 c2c45cb..0cf6dc6 100644
--- a/tests/robotests/src/com/android/settings/notification/app/BubblePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/app/BubblePreferenceControllerTest.java
@@ -17,6 +17,9 @@
 package com.android.settings.notification.app;
 
 import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID;
+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 android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
@@ -25,8 +28,8 @@
 import static com.android.settings.notification.app.BubblePreferenceController.SYSTEM_WIDE_OFF;
 import static com.android.settings.notification.app.BubblePreferenceController.SYSTEM_WIDE_ON;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -44,6 +47,11 @@
 import android.os.UserManager;
 import android.provider.Settings;
 
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
 import com.android.settings.notification.NotificationBackend;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedSwitchPreference;
@@ -58,11 +66,6 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.shadows.ShadowApplication;
 
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentTransaction;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
 @RunWith(RobolectricTestRunner.class)
 public class BubblePreferenceControllerTest {
 
@@ -125,7 +128,7 @@
     public void testIsAvailable_channel_yesIfAppOff() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
-        appRow.allowBubbles = false;
+        appRow.bubblePreference = BUBBLE_PREFERENCE_NONE;
         NotificationChannel channel = mock(NotificationChannel.class);
         when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH);
         mController.onResume(appRow, channel, null, null, null, null);
@@ -177,7 +180,7 @@
     @Test
     public void testIsAvailable_defaultChannel() {
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
-        appRow.allowBubbles = true;
+        appRow.bubblePreference = BUBBLE_PREFERENCE_ALL;
         NotificationChannel channel = mock(NotificationChannel.class);
         when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH);
         when(channel.getId()).thenReturn(DEFAULT_CHANNEL_ID);
@@ -190,7 +193,7 @@
     @Test
     public void testIsAvailable_channel() {
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
-        appRow.allowBubbles = true;
+        appRow.bubblePreference = BUBBLE_PREFERENCE_ALL;
         NotificationChannel channel = mock(NotificationChannel.class);
         when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH);
         mController.onResume(appRow, channel, null, null, null, null);
@@ -213,7 +216,20 @@
     }
 
     @Test
-    public void testUpdateState_channelNotBlockable() {
+    public void testUpdateState_app_disabledByAdmin() {
+        NotificationChannel channel = mock(NotificationChannel.class);
+        when(channel.getId()).thenReturn("something");
+        mAppPageController.onResume(new NotificationBackend.AppRow(), channel, null,
+                null, null, mock(RestrictedLockUtils.EnforcedAdmin.class));
+
+        BubblePreference pref = new BubblePreference(mContext);
+        mAppPageController.updateState(pref);
+
+        assertFalse(pref.isEnabled());
+    }
+
+    @Test
+    public void testUpdateState_channel_channelNotBlockable() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         NotificationChannel channel = mock(NotificationChannel.class);
@@ -251,21 +267,24 @@
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         appRow.label = "App!";
-        appRow.allowBubbles = true;
-        mController.onResume(appRow, null, null, null, null, null);
+        appRow.bubblePreference = BUBBLE_PREFERENCE_ALL;
+        mAppPageController.onResume(appRow, null, null, null, null, null);
 
-        RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext);
-        mController.updateState(pref);
-        assertTrue(pref.isChecked());
+        BubblePreference pref = new BubblePreference(mContext);
+        mAppPageController.updateState(pref);
+        assertEquals(BUBBLE_PREFERENCE_ALL, pref.getSelectedPreference());
 
-        appRow.allowBubbles = false;
-        mController.onResume(appRow, null, null, null, null, null);
+        appRow.bubblePreference = BUBBLE_PREFERENCE_NONE;
+        mAppPageController.onResume(appRow, null, null, null, null, null);
 
-        mController.updateState(pref);
-        assertFalse(pref.isChecked());
+        mAppPageController.updateState(pref);
+        assertEquals(BUBBLE_PREFERENCE_NONE, pref.getSelectedPreference());
 
-        assertNotNull(pref.getSummary());
-        assertTrue(pref.getSummary().toString().contains(appRow.label));
+        appRow.bubblePreference = BUBBLE_PREFERENCE_SELECTED;
+        mAppPageController.onResume(appRow, null, null, null, null, null);
+
+        mAppPageController.updateState(pref);
+        assertEquals(BUBBLE_PREFERENCE_SELECTED, pref.getSelectedPreference());
     }
 
     @Test
@@ -274,22 +293,21 @@
                 NOTIFICATION_BUBBLES, SYSTEM_WIDE_OFF);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         appRow.label = "App!";
-        appRow.allowBubbles = true;
-        mController.onResume(appRow, null, null, null, null, null);
+        appRow.bubblePreference = BUBBLE_PREFERENCE_ALL;
+        mAppPageController.onResume(appRow, null, null, null, null, null);
 
-        RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext);
-        mController.updateState(pref);
-        assertFalse(pref.isChecked());
+        BubblePreference pref = new BubblePreference(mContext);
+        mAppPageController.updateState(pref);
+        assertEquals(BUBBLE_PREFERENCE_NONE, pref.getSelectedPreference());
     }
 
     @Test
     public void testOnPreferenceChange_on_channel() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
-        appRow.allowBubbles = true;
+        appRow.bubblePreference = BUBBLE_PREFERENCE_SELECTED;
         NotificationChannel channel =
                 new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_LOW);
-        channel.setAllowBubbles(false);
         mController.onResume(appRow, channel, null, null, null, null);
 
         RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext);
@@ -306,10 +324,9 @@
     public void testOnPreferenceChange_off_channel() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
-        appRow.allowBubbles = true;
+        appRow.bubblePreference = BUBBLE_PREFERENCE_SELECTED;
         NotificationChannel channel =
                 new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_HIGH);
-        channel.setAllowBubbles(true);
         mController.onResume(appRow, channel, null, null, null, null);
 
         RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext);
@@ -322,59 +339,78 @@
         assertFalse(channel.canBubble());
     }
 
+
     @Test
-    public void testOnPreferenceChange_on_app() {
+    public void testOnPreferenceChange_app_all() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
-        appRow.allowBubbles = false;
-        mController.onResume(appRow, null, null, null, null, null);
+        appRow.bubblePreference = BUBBLE_PREFERENCE_NONE;
+        mAppPageController.onResume(appRow, null, null, null, null, null);
 
-        RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext);
+        BubblePreference pref = new BubblePreference(mContext);
         when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref);
-        mController.displayPreference(mScreen);
-        mController.updateState(pref);
+        mAppPageController.displayPreference(mScreen);
+        mAppPageController.updateState(pref);
 
-        mController.onPreferenceChange(pref, true);
+        mAppPageController.onPreferenceChange(pref, BUBBLE_PREFERENCE_ALL);
 
-        assertTrue(appRow.allowBubbles);
-        verify(mBackend, times(1)).setAllowBubbles(any(), anyInt(), eq(true));
+        assertEquals(appRow.bubblePreference, BUBBLE_PREFERENCE_ALL);
+        verify(mBackend, times(1)).setAllowBubbles(any(), anyInt(), eq(BUBBLE_PREFERENCE_ALL));
     }
 
     @Test
-    public void testOnPreferenceChange_off_app() {
-        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
-        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
-        appRow.allowBubbles = true;
-        mController.onResume(appRow, null, null, null, null, null);
-
-        RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext);
-        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref);
-        mController.displayPreference(mScreen);
-        mController.updateState(pref);
-
-        mController.onPreferenceChange(pref, false);
-
-        assertFalse(appRow.allowBubbles);
-        verify(mBackend, times(1)).setAllowBubbles(any(), anyInt(), eq(false));
-    }
-
-    @Test
-    public void testOnPreferenceChange_on_app_offGlobally() {
+    public void testOnPreferenceChange_app_all_offGlobally() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES,
                 SYSTEM_WIDE_OFF);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
-        appRow.allowBubbles = false;
-        mController.onResume(appRow, null, null, null, null, null);
+        appRow.bubblePreference = BUBBLE_PREFERENCE_NONE;
+        mAppPageController.onResume(appRow, null, null, null, null, null);
 
-        RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext);
+        BubblePreference pref = new BubblePreference(mContext);
         when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref);
-        mController.displayPreference(mScreen);
-        mController.updateState(pref);
+        mAppPageController.displayPreference(mScreen);
+        mAppPageController.updateState(pref);
 
-        mController.onPreferenceChange(pref, true);
+        mAppPageController.onPreferenceChange(pref, BUBBLE_PREFERENCE_ALL);
 
-        assertFalse(appRow.allowBubbles);
-        verify(mBackend, never()).setAllowBubbles(any(), anyInt(), eq(true));
+        assertEquals(appRow.bubblePreference, BUBBLE_PREFERENCE_NONE);
+        verify(mBackend, never()).setAllowBubbles(any(), anyInt(), eq(BUBBLE_PREFERENCE_ALL));
         verify(mFragmentManager, times(1)).beginTransaction();
     }
+
+    @Test
+    public void testOnPreferenceChange_app_selected() {
+        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);
+        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref);
+        mAppPageController.displayPreference(mScreen);
+        mAppPageController.updateState(pref);
+
+        mAppPageController.onPreferenceChange(pref, BUBBLE_PREFERENCE_NONE);
+
+        assertEquals(BUBBLE_PREFERENCE_NONE, appRow.bubblePreference);
+        verify(mBackend, times(1)).setAllowBubbles(any(), anyInt(), eq(BUBBLE_PREFERENCE_NONE));
+    }
+
+    @Test
+    public void testOnPreferenceChange_app_none() {
+        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);
+        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref);
+        mAppPageController.displayPreference(mScreen);
+        mAppPageController.updateState(pref);
+
+        mAppPageController.onPreferenceChange(pref, BUBBLE_PREFERENCE_NONE);
+
+        assertEquals(BUBBLE_PREFERENCE_NONE, appRow.bubblePreference);
+        verify(mBackend, times(1)).setAllowBubbles(any(), anyInt(), eq(BUBBLE_PREFERENCE_NONE));
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/notification/app/BubbleSummaryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/BubbleSummaryPreferenceControllerTest.java
index 80abfbb..9d664ac 100644
--- a/tests/robotests/src/com/android/settings/notification/app/BubbleSummaryPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/app/BubbleSummaryPreferenceControllerTest.java
@@ -17,28 +17,31 @@
 package com.android.settings.notification.app;
 
 import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID;
+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 android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
 
-import static com.android.settings.notification.app.BubbleSummaryPreferenceController.SYSTEM_WIDE_OFF;
-import static com.android.settings.notification.app.BubbleSummaryPreferenceController.SYSTEM_WIDE_ON;
+import static com.android.settings.notification.app.BubblePreferenceController.SYSTEM_WIDE_OFF;
+import static com.android.settings.notification.app.BubblePreferenceController.SYSTEM_WIDE_ON;
 
 import static junit.framework.TestCase.assertEquals;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.NotificationChannel;
 import android.content.Context;
 import android.provider.Settings;
 
+import androidx.preference.Preference;
+
+import com.android.settings.R;
 import com.android.settings.notification.NotificationBackend;
 
 import org.junit.Before;
@@ -50,8 +53,6 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.shadows.ShadowApplication;
 
-import androidx.preference.Preference;
-
 @RunWith(RobolectricTestRunner.class)
 public class BubbleSummaryPreferenceControllerTest {
 
@@ -70,13 +71,13 @@
     }
 
     @Test
-    public void testNoCrashIfNoOnResume() {
+    public void isAvailable_noOnResume_shouldNotCrash() {
         mController.isAvailable();
         mController.updateState(mock(Preference.class));
     }
 
     @Test
-    public void testIsAvailable_notIfAppBlocked() {
+    public void isAvailable_appBlocked_shouldReturnFalse() {
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         appRow.banned = true;
         mController.onResume(appRow, mock(NotificationChannel.class), null, null, null, null);
@@ -84,53 +85,52 @@
     }
 
     @Test
-    public void testIsAvailable_notIfOffGlobally() {
-        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
-        NotificationChannel channel = mock(NotificationChannel.class);
-        when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH);
-        mController.onResume(appRow, channel, null, null, null, null);
-        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES,
-                SYSTEM_WIDE_OFF);
-
-        assertFalse(mController.isAvailable());
-    }
-
-    @Test
-    public void testIsAvailable_app() {
+    public void isAvailable_nullChannelNOTIFICATION_BUBBLESisOn_shouldReturnTrue() {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         mController.onResume(appRow, null, null, null, null, null);
-        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
 
         assertTrue(mController.isAvailable());
     }
 
     @Test
-    public void testIsNotAvailable_app_globalOff() {
-        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
-        mController.onResume(appRow, null, null, null, null, null);
+    public void isAvailable_nullChannelNOTIFICATION_BUBBLESisOff_shouldReturnFalse() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES,
                 SYSTEM_WIDE_OFF);
+        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+        mController.onResume(appRow, null, null, null, null, null);
 
         assertFalse(mController.isAvailable());
     }
 
     @Test
-    public void testIsAvailable_defaultChannel() {
+    public void isAvailable_nonNullChannelNOTIFICATION_BUBBLESisOff_shouldReturnFalse() {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES,
+                SYSTEM_WIDE_OFF);
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
-        appRow.allowBubbles = true;
+        NotificationChannel channel = mock(NotificationChannel.class);
+        when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH);
+        mController.onResume(appRow, channel, null, null, null, null);
+
+        assertFalse(mController.isAvailable());
+    }
+
+    @Test
+    public void isAvailable_defaultChannelNOTIFICATION_BUBBLESisOn_shouldReturnTrue() {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
+        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         NotificationChannel channel = mock(NotificationChannel.class);
         when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH);
         when(channel.getId()).thenReturn(DEFAULT_CHANNEL_ID);
         mController.onResume(appRow, channel, null, null, null, null);
-        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
 
         assertTrue(mController.isAvailable());
     }
 
     @Test
-    public void testUpdateState() {
+    public void updateState_setsIntent() {
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
-        appRow.allowBubbles = true;
+        appRow.bubblePreference = BUBBLE_PREFERENCE_ALL;
         mController.onResume(appRow, null, null, null, null, null);
 
         Preference pref = new Preference(mContext);
@@ -139,22 +139,53 @@
     }
 
     @Test
-    public void testGetSummary() {
-        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
-        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
-        appRow.allowBubbles = true;
-        mController.onResume(appRow, null, null, null, null, null);
-
-        assertEquals("On", mController.getSummary());
-
+    public void getSummary_NOTIFICATION_BUBBLESIsOff_returnsNoneString() {
         Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES,
                 SYSTEM_WIDE_OFF);
-        assertEquals("Off", mController.getSummary());
 
-        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
-        appRow.allowBubbles = false;
+        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         mController.onResume(appRow, null, null, null, null, null);
 
-        assertEquals("Off", mController.getSummary());
+        String noneString = mContext.getString(R.string.bubble_app_setting_none);
+        assertEquals(noneString, mController.getSummary());
+    }
+
+    @Test
+    public void getSummary_BUBBLE_PREFERENCE_NONEisSelected_returnsNoneString() {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES,
+                SYSTEM_WIDE_ON);
+
+        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+        appRow.bubblePreference = BUBBLE_PREFERENCE_NONE;
+        mController.onResume(appRow, null, null, null, null, null);
+
+        String noneString = mContext.getString(R.string.bubble_app_setting_none);
+        assertEquals(noneString, mController.getSummary());
+    }
+
+    @Test
+    public void getSummary_BUBBLE_PREFERENCE_ALLisSelected_returnsAllString() {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES,
+                SYSTEM_WIDE_ON);
+
+        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+        appRow.bubblePreference = BUBBLE_PREFERENCE_ALL;
+        mController.onResume(appRow, null, null, null, null, null);
+
+        String allString = mContext.getString(R.string.bubble_app_setting_all);
+        assertEquals(allString, mController.getSummary());
+    }
+
+    @Test
+    public void getSummary_BUBBLE_PREFERENCE_SELECTEDisSelected_returnsSelectedString() {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES,
+                SYSTEM_WIDE_ON);
+
+        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+        appRow.bubblePreference = BUBBLE_PREFERENCE_SELECTED;
+        mController.onResume(appRow, null, null, null, null, null);
+
+        String selectedString = mContext.getString(R.string.bubble_app_setting_selected);
+        assertEquals(selectedString, mController.getSummary());
     }
 }