Merge "Check config to Show/Hide Private space Hide Illustration" into main
diff --git a/aconfig/accessibility/accessibility_flags.aconfig b/aconfig/accessibility/accessibility_flags.aconfig
index 3ed618b..3092b8f 100644
--- a/aconfig/accessibility/accessibility_flags.aconfig
+++ b/aconfig/accessibility/accessibility_flags.aconfig
@@ -21,6 +21,16 @@
 }
 
 flag {
+  name: "check_prebundled_is_preinstalled"
+  namespace: "accessibility"
+  description: "Checks that all 'prebundled' components, used for grouping, are also preinstalled"
+  bug: "353888087"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "edit_shortcuts_in_full_screen"
   namespace: "accessibility"
   description: "Show the edit shorcuts screen in full screen, since the shortcut options are increasing."
diff --git a/res/drawable/ic_zen_mode_generic_contact.xml b/res/drawable/ic_zen_mode_generic_contact.xml
new file mode 100644
index 0000000..3721dc5
--- /dev/null
+++ b/res/drawable/ic_zen_mode_generic_contact.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 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:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M480,480Q414,480 367,433Q320,386 320,320Q320,254 367,207Q414,160 480,160Q546,160 593,207Q640,254 640,320Q640,386 593,433Q546,480 480,480ZM160,800L160,688Q160,654 177.5,625.5Q195,597 224,582Q286,551 350,535.5Q414,520 480,520Q546,520 610,535.5Q674,551 736,582Q765,597 782.5,625.5Q800,654 800,688L800,800L160,800ZM240,720L720,720L720,688Q720,677 714.5,668Q709,659 700,654Q646,627 591,613.5Q536,600 480,600Q424,600 369,613.5Q314,627 260,654Q251,659 245.5,668Q240,677 240,688L240,720ZM480,400Q513,400 536.5,376.5Q560,353 560,320Q560,287 536.5,263.5Q513,240 480,240Q447,240 423.5,263.5Q400,287 400,320Q400,353 423.5,376.5Q447,400 480,400ZM480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320ZM480,720L480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720L480,720Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_zen_mode_people_all.xml b/res/drawable/ic_zen_mode_people_all.xml
new file mode 100644
index 0000000..c6194d5
--- /dev/null
+++ b/res/drawable/ic_zen_mode_people_all.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 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:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M0,720L0,657Q0,614 44,587Q88,560 160,560Q173,560 185,560.5Q197,561 208,563Q194,584 187,607Q180,630 180,655L180,720L0,720ZM240,720L240,655Q240,623 257.5,596.5Q275,570 307,550Q339,530 383.5,520Q428,510 480,510Q533,510 577.5,520Q622,530 654,550Q686,570 703,596.5Q720,623 720,655L720,720L240,720ZM780,720L780,655Q780,629 773.5,606Q767,583 754,563Q765,561 776.5,560.5Q788,560 800,560Q872,560 916,586.5Q960,613 960,657L960,720L780,720ZM325,640L636,640L636,640Q626,620 580.5,605Q535,590 480,590Q425,590 379.5,605Q334,620 325,640ZM160,520Q127,520 103.5,496.5Q80,473 80,440Q80,406 103.5,383Q127,360 160,360Q194,360 217,383Q240,406 240,440Q240,473 217,496.5Q194,520 160,520ZM800,520Q767,520 743.5,496.5Q720,473 720,440Q720,406 743.5,383Q767,360 800,360Q834,360 857,383Q880,406 880,440Q880,473 857,496.5Q834,520 800,520ZM480,480Q430,480 395,445Q360,410 360,360Q360,309 395,274.5Q430,240 480,240Q531,240 565.5,274.5Q600,309 600,360Q600,410 565.5,445Q531,480 480,480ZM480,400Q497,400 508.5,388.5Q520,377 520,360Q520,343 508.5,331.5Q497,320 480,320Q463,320 451.5,331.5Q440,343 440,360Q440,377 451.5,388.5Q463,400 480,400ZM481,640L481,640Q481,640 481,640Q481,640 481,640Q481,640 481,640Q481,640 481,640L481,640ZM480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_zen_mode_sound_alarms.xml b/res/drawable/ic_zen_mode_sound_alarms.xml
new file mode 100644
index 0000000..b2761ca
--- /dev/null
+++ b/res/drawable/ic_zen_mode_sound_alarms.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 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:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M480,880Q405,880 339.5,851.5Q274,823 225.5,774.5Q177,726 148.5,660.5Q120,595 120,520Q120,445 148.5,379.5Q177,314 225.5,265.5Q274,217 339.5,188.5Q405,160 480,160Q555,160 620.5,188.5Q686,217 734.5,265.5Q783,314 811.5,379.5Q840,445 840,520Q840,595 811.5,660.5Q783,726 734.5,774.5Q686,823 620.5,851.5Q555,880 480,880ZM480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520ZM592,688L648,632L520,504L520,320L440,320L440,536L592,688ZM224,94L280,150L110,320L54,264L224,94ZM736,94L906,264L850,320L680,150L736,94ZM480,800Q597,800 678.5,718.5Q760,637 760,520Q760,403 678.5,321.5Q597,240 480,240Q363,240 281.5,321.5Q200,403 200,520Q200,637 281.5,718.5Q363,800 480,800Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_zen_mode_sound_events.xml b/res/drawable/ic_zen_mode_sound_events.xml
new file mode 100644
index 0000000..15035e4
--- /dev/null
+++ b/res/drawable/ic_zen_mode_sound_events.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 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:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M580,720Q538,720 509,691Q480,662 480,620Q480,578 509,549Q538,520 580,520Q622,520 651,549Q680,578 680,620Q680,662 651,691Q622,720 580,720ZM200,880Q167,880 143.5,856.5Q120,833 120,800L120,240Q120,207 143.5,183.5Q167,160 200,160L240,160L240,80L320,80L320,160L640,160L640,80L720,80L720,160L760,160Q793,160 816.5,183.5Q840,207 840,240L840,800Q840,833 816.5,856.5Q793,880 760,880L200,880ZM200,800L760,800Q760,800 760,800Q760,800 760,800L760,400L200,400L200,800Q200,800 200,800Q200,800 200,800ZM200,320L760,320L760,240Q760,240 760,240Q760,240 760,240L200,240Q200,240 200,240Q200,240 200,240L200,320ZM200,320L200,240Q200,240 200,240Q200,240 200,240L200,240Q200,240 200,240Q200,240 200,240L200,320Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_zen_mode_sound_media.xml b/res/drawable/ic_zen_mode_sound_media.xml
new file mode 100644
index 0000000..f2bedcb
--- /dev/null
+++ b/res/drawable/ic_zen_mode_sound_media.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 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:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M400,840Q334,840 287,793Q240,746 240,680Q240,614 287,567Q334,520 400,520Q423,520 442.5,525.5Q462,531 480,542L480,120L720,120L720,280L560,280L560,680Q560,746 513,793Q466,840 400,840Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_zen_mode_sound_reminders.xml b/res/drawable/ic_zen_mode_sound_reminders.xml
new file mode 100644
index 0000000..cd9490a
--- /dev/null
+++ b/res/drawable/ic_zen_mode_sound_reminders.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 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:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M260,320L300,320L300,280Q300,263 288.5,251.5Q277,240 260,240Q243,240 231.5,251.5Q220,263 220,280Q220,297 231.5,308.5Q243,320 260,320ZM440,320Q457,320 468.5,308.5Q480,297 480,280Q480,263 468.5,251.5Q457,240 440,240Q423,240 411.5,251.5Q400,263 400,280L400,320L440,320ZM502,620L502,620L502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620L502,620ZM419,880Q391,880 366.5,868Q342,856 325,834L107,557L126,537Q146,516 174,512Q202,508 226,523L300,568L300,400L260,400Q210,400 175,365Q140,330 140,280Q140,230 175,195Q210,160 260,160Q271,160 280.5,162Q290,164 300,167L300,120Q300,103 311.5,91.5Q323,80 340,80Q357,80 369,91.5Q381,103 381,120L381,176Q395,168 409.5,164Q424,160 440,160Q490,160 525,195Q560,230 560,280Q560,330 525,365Q490,400 440,400L381,400L381,712L284,652L388,785Q394,792 402,796Q410,800 419,800L640,800Q673,800 696.5,776.5Q720,753 720,720L720,560Q720,543 708.5,531.5Q697,520 680,520L461,520L461,440L680,440Q730,440 765,475Q800,510 800,560L800,720Q800,786 753,833Q706,880 640,880L419,880Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_zen_mode_sound_system.xml b/res/drawable/ic_zen_mode_sound_system.xml
new file mode 100644
index 0000000..2da05d8
--- /dev/null
+++ b/res/drawable/ic_zen_mode_sound_system.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 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:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M419,880Q391,880 366.5,868Q342,856 325,834L107,557L126,537Q146,516 174,512Q202,508 226,523L300,568L300,240Q300,223 311.5,211.5Q323,200 340,200Q357,200 369,211.5Q381,223 381,240L381,712L284,652L388,785Q394,792 402,796Q410,800 419,800L640,800Q673,800 696.5,776.5Q720,753 720,720L720,560Q720,543 708.5,531.5Q697,520 680,520L461,520L461,440L680,440Q730,440 765,475Q800,510 800,560L800,720Q800,786 753,833Q706,880 640,880L419,880ZM167,340Q154,318 147,292.5Q140,267 140,240Q140,157 198.5,98.5Q257,40 340,40Q423,40 481.5,98.5Q540,157 540,240Q540,267 533,292.5Q526,318 513,340L444,300Q452,286 456,271.5Q460,257 460,240Q460,190 425,155Q390,120 340,120Q290,120 255,155Q220,190 220,240Q220,257 224,271.5Q228,286 236,300L167,340ZM502,620L502,620L502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620L502,620Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_zen_mode_trigger_with_activity.xml b/res/drawable/ic_zen_mode_trigger_with_activity.xml
new file mode 100644
index 0000000..567f01a
--- /dev/null
+++ b/res/drawable/ic_zen_mode_trigger_with_activity.xml
@@ -0,0 +1,26 @@
+<!--
+Copyright (C) 2024 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:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960"
+    android:autoMirrored="true">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,600L200,600L200,760Q200,760 200,760Q200,760 200,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,360L120,360L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM420,680L364,622L466,520L120,520L120,440L466,440L364,338L420,280L620,480L420,680Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_zen_mode_trigger_without_activity.xml b/res/drawable/ic_zen_mode_trigger_without_activity.xml
new file mode 100644
index 0000000..11a97f1
--- /dev/null
+++ b/res/drawable/ic_zen_mode_trigger_without_activity.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 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:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M352,840L200,840Q167,840 143.5,816.5Q120,793 120,760L120,608Q168,608 204,577.5Q240,547 240,500Q240,453 204,422.5Q168,392 120,392L120,240Q120,207 143.5,183.5Q167,160 200,160L360,160Q360,118 389,89Q418,60 460,60Q502,60 531,89Q560,118 560,160L720,160Q753,160 776.5,183.5Q800,207 800,240L800,400Q842,400 871,429Q900,458 900,500Q900,542 871,571Q842,600 800,600L800,760Q800,793 776.5,816.5Q753,840 720,840L568,840Q568,790 536.5,755Q505,720 460,720Q415,720 383.5,755Q352,790 352,840ZM200,760L285,760Q309,694 362,667Q415,640 460,640Q505,640 558,667Q611,694 635,760L720,760L720,520L800,520Q808,520 814,514Q820,508 820,500Q820,492 814,486Q808,480 800,480L720,480L720,240L480,240L480,160Q480,152 474,146Q468,140 460,140Q452,140 446,146Q440,152 440,160L440,240L200,240L200,328Q254,348 287,395Q320,442 320,500Q320,557 287,604Q254,651 200,672L200,760ZM460,500L460,500Q460,500 460,500Q460,500 460,500Q460,500 460,500Q460,500 460,500L460,500L460,500L460,500Q460,500 460,500Q460,500 460,500Q460,500 460,500Q460,500 460,500L460,500L460,500L460,500L460,500Q460,500 460,500Q460,500 460,500Q460,500 460,500Q460,500 460,500L460,500L460,500L460,500Q460,500 460,500Q460,500 460,500Q460,500 460,500Q460,500 460,500Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/preference_circular_icons_plus_item_background.xml b/res/drawable/preference_circular_icons_plus_item_background.xml
new file mode 100644
index 0000000..faaa1fd
--- /dev/null
+++ b/res/drawable/preference_circular_icons_plus_item_background.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2024 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.
+  -->
+
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="oval">
+    <size
+        android:width="@dimen/zen_mode_circular_icon_diameter"
+        android:height="@dimen/zen_mode_circular_icon_diameter" />
+    <solid android:color="?androidprv:attr/materialColorSecondaryContainer" />
+</shape>
\ No newline at end of file
diff --git a/res/layout-land/bluetooth_audio_streams_qr_code.xml b/res/layout-land/bluetooth_audio_streams_qr_code.xml
new file mode 100644
index 0000000..b35bc65
--- /dev/null
+++ b/res/layout-land/bluetooth_audio_streams_qr_code.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 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="match_parent"
+    android:orientation="horizontal"
+    android:padding="25dp"
+    android:baselineAligned="false">
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="start"
+            android:textSize="15sp"
+            android:textColor="?android:attr/textColorPrimary" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:gravity="center"
+        android:orientation="vertical">
+
+        <ImageView
+            android:id="@+id/qrcode_view"
+            android:layout_width="@dimen/qrcode_size"
+            android:layout_height="@dimen/qrcode_size"
+            android:contentDescription="@string/audio_streams_qr_code_page_image_label"
+            android:focusable="true" />
+
+        <TextView
+            android:id="@+id/password"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="15sp"
+            android:textColor="?android:attr/textColorPrimary" />
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout-land/qrcode_scanner_fragment.xml b/res/layout-land/qrcode_scanner_fragment.xml
new file mode 100644
index 0000000..0e563e3
--- /dev/null
+++ b/res/layout-land/qrcode_scanner_fragment.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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="match_parent"
+    android:orientation="horizontal">
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:orientation="vertical">
+            <TextView
+                android:id="@android:id/summary"
+                style="@style/QrCodeScanner"
+                android:gravity="center"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"/>
+        </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:gravity="center"
+        android:orientation="vertical">
+
+        <FrameLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top"
+            android:gravity="center"
+            android:clipChildren="true"
+            android:contentDescription="@string/audio_streams_qr_code_scanner_label"
+            android:focusable="true">
+            <TextureView
+                android:id="@+id/preview_view"
+                android:layout_marginStart="@dimen/qrcode_preview_margin"
+                android:layout_marginEnd="@dimen/qrcode_preview_margin"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/qrcode_preview_size"/>
+        </FrameLayout>
+
+        <TextView
+            android:id="@+id/error_message"
+            style="@style/TextAppearance.ErrorText"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dp"
+            android:gravity="center"
+            android:layout_gravity="center"
+            android:visibility="invisible"/>
+
+    </LinearLayout>
+
+
+</LinearLayout>
+
diff --git a/res/layout/advanced_bt_entity_sub.xml b/res/layout/advanced_bt_entity_sub.xml
index 90ac456..dd8e43a 100644
--- a/res/layout/advanced_bt_entity_sub.xml
+++ b/res/layout/advanced_bt_entity_sub.xml
@@ -45,7 +45,7 @@
             android:layout_gravity="center"
             android:indeterminate="false"
             app:trackColor="@android:color/transparent"
-            app:indicatorColor="@color/bluetooth_battery_ring_indicator_color"
+            app:indicatorColor="@color/settingslib_materialColorPrimary"
             app:trackThickness="4dp"
             app:indicatorSize="76dp"
             app:indicatorInset="0dp"
diff --git a/res/xml/bluetooth_audio_streams_qr_code.xml b/res/layout/bluetooth_audio_streams_qr_code.xml
similarity index 91%
rename from res/xml/bluetooth_audio_streams_qr_code.xml
rename to res/layout/bluetooth_audio_streams_qr_code.xml
index 5ec5505..fd521fe 100644
--- a/res/xml/bluetooth_audio_streams_qr_code.xml
+++ b/res/layout/bluetooth_audio_streams_qr_code.xml
@@ -47,7 +47,9 @@
             <ImageView
                 android:id="@+id/qrcode_view"
                 android:layout_width="@dimen/qrcode_size"
-                android:layout_height="@dimen/qrcode_size"/>
+                android:layout_height="@dimen/qrcode_size"
+                android:contentDescription="@string/audio_streams_qr_code_page_image_label"
+                android:focusable="true"/>
 
             <TextView
                 android:id="@+id/password"
diff --git a/res/layout/dialog_custom_body_audio_sharing.xml b/res/layout/dialog_custom_body_audio_sharing.xml
index 388a4941..2e8c506 100644
--- a/res/layout/dialog_custom_body_audio_sharing.xml
+++ b/res/layout/dialog_custom_body_audio_sharing.xml
@@ -15,57 +15,62 @@
   ~ limitations under the License.
   -->
 
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="vertical"
-    android:paddingHorizontal="?android:dialogPreferredPadding"
-    android:paddingBottom="?android:dialogPreferredPadding">
+    android:scrollbars="none">
 
-    <TextView
-        android:id="@+id/description_text"
-        style="@style/DeviceAudioSharingText"
+    <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:paddingBottom="24dp"
-        android:visibility="gone" />
+        android:orientation="vertical"
+        android:paddingBottom="?android:dialogPreferredPadding"
+        android:paddingHorizontal="?android:dialogPreferredPadding">
 
-    <ImageView
-        android:id="@+id/description_image"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:contentDescription="@null"
-        android:visibility="gone" />
+        <TextView
+            android:id="@+id/description_text"
+            style="@style/DeviceAudioSharingText"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:paddingBottom="24dp"
+            android:visibility="gone" />
 
-    <androidx.recyclerview.widget.RecyclerView
-        android:id="@+id/device_btn_list"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:nestedScrollingEnabled="false"
-        android:overScrollMode="never"
-        android:visibility="gone" />
+        <ImageView
+            android:id="@+id/description_image"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:contentDescription="@null"
+            android:visibility="gone" />
 
-    <Button
-        android:id="@+id/positive_btn"
-        style="@style/SettingsLibActionButton"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:layout_marginTop="4dp"
-        android:background="@drawable/audio_sharing_rounded_bg_ripple"
-        android:visibility="gone" />
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/device_btn_list"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:nestedScrollingEnabled="false"
+            android:overScrollMode="never"
+            android:visibility="gone" />
 
-    <Button
-        android:id="@+id/negative_btn"
-        style="@style/SettingsLibActionButton"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:layout_marginTop="4dp"
-        android:background="@drawable/audio_sharing_rounded_bg_ripple"
-        android:visibility="gone" />
-</LinearLayout>
\ No newline at end of file
+        <Button
+            android:id="@+id/positive_btn"
+            style="@style/SettingsLibActionButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginTop="4dp"
+            android:background="@drawable/audio_sharing_rounded_bg_ripple"
+            android:visibility="gone" />
+
+        <Button
+            android:id="@+id/negative_btn"
+            style="@style/SettingsLibActionButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginTop="4dp"
+            android:background="@drawable/audio_sharing_rounded_bg_ripple"
+            android:visibility="gone" />
+    </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/res/layout/preference_circular_icons.xml b/res/layout/preference_circular_icons.xml
new file mode 100644
index 0000000..ae981b2
--- /dev/null
+++ b/res/layout/preference_circular_icons.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+  -->
+
+<!-- Based off preference_two_target.xml, with the added LinearLayout for the icons. -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:gravity="center_vertical"
+    android:background="?android:attr/selectableItemBackground"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:clipToPadding="false">
+
+    <include layout="@layout/settingslib_icon_frame"/>
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp">
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxLines="2"
+            android:hyphenationFrequency="normalFast"
+            android:lineBreakWordStyle="phrase"
+            android:textAppearance="?android:attr/textAppearanceListItem"
+            android:ellipsize="marquee"/>
+
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/title"
+            android:layout_alignStart="@android:id/title"
+            android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+            android:textColor="?android:attr/textColorSecondary"
+            android:hyphenationFrequency="normalFast"
+            android:lineBreakWordStyle="phrase"
+            android:maxLines="10"/>
+
+        <!-- Circular icons (32dp) will be ImageViews under this LinearLayout -->
+        <LinearLayout
+            android:id="@+id/circles_container"
+            android:orientation="horizontal"
+            android:gravity="center_vertical"
+            android:layout_width="match_parent"
+            android:layout_height="48dp"
+            android:layout_below="@android:id/summary"
+            android:layout_alignStart="@android:id/title" />
+
+    </RelativeLayout>
+
+    <include layout="@layout/preference_two_target_divider" />
+
+    <!-- Preference should place its actual preference widget here. -->
+    <LinearLayout
+        android:id="@android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:minWidth="@dimen/two_target_min_width"
+        android:gravity="center"
+        android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/res/layout/preference_circular_icons_item.xml b/res/layout/preference_circular_icons_item.xml
new file mode 100644
index 0000000..e5656ce
--- /dev/null
+++ b/res/layout/preference_circular_icons_item.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+  -->
+
+<ImageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/zen_mode_circular_icon_diameter"
+    android:layout_height="@dimen/zen_mode_circular_icon_diameter"
+    android:layout_marginTop="@dimen/zen_mode_circular_icon_margin_vertical"
+    android:layout_marginBottom="@dimen/zen_mode_circular_icon_margin_vertical"
+    android:layout_marginEnd="@dimen/zen_mode_circular_icon_margin_between" />
\ No newline at end of file
diff --git a/res/layout/preference_circular_icons_plus_item.xml b/res/layout/preference_circular_icons_plus_item.xml
new file mode 100644
index 0000000..9882086
--- /dev/null
+++ b/res/layout/preference_circular_icons_plus_item.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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.
+  -->
+
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:layout_width="@dimen/zen_mode_circular_icon_diameter"
+    android:layout_height="@dimen/zen_mode_circular_icon_diameter"
+    android:layout_marginTop="@dimen/zen_mode_circular_icon_margin_vertical"
+    android:layout_marginBottom="@dimen/zen_mode_circular_icon_margin_vertical"
+    android:gravity="center"
+    android:padding="4dp"
+    android:drawablePadding="0dp"
+    android:background="@drawable/preference_circular_icons_plus_item_background"
+    android:textColor="?androidprv:attr/materialColorOnSecondaryContainer"
+    android:maxLines="1"
+    android:autoSizeTextType="uniform"
+    android:autoSizeMinTextSize="6sp" />
\ No newline at end of file
diff --git a/res/layout/qrcode_scanner_fragment.xml b/res/layout/qrcode_scanner_fragment.xml
index 4f748c5..72049a4 100644
--- a/res/layout/qrcode_scanner_fragment.xml
+++ b/res/layout/qrcode_scanner_fragment.xml
@@ -55,7 +55,9 @@
             android:layout_height="wrap_content"
             android:layout_gravity="top"
             android:gravity="center"
-            android:clipChildren="true">
+            android:clipChildren="true"
+            android:contentDescription="@string/audio_streams_qr_code_scanner_label"
+            android:focusable="true">
             <TextureView
                 android:id="@+id/preview_view"
                 android:layout_marginStart="@dimen/qrcode_preview_margin"
diff --git a/res/values/colors.xml b/res/values/colors.xml
index b0de870..6d92526 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -218,7 +218,4 @@
 
     <!-- Switch bar disabled state color-->
     <color name="switch_bar_state_disabled_color">#1F1F1F1F</color>
-
-    <!-- Battery ring indicator color in bluetooth device details -->
-    <color name="bluetooth_battery_ring_indicator_color">#9ED582</color>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 8a96727..68eb99b 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -508,4 +508,10 @@
     <dimen name="zen_mode_icon_list_item_size">96dp</dimen>
     <dimen name="zen_mode_icon_list_item_circle_diameter">56dp</dimen>
     <dimen name="zen_mode_icon_list_item_icon_size">32dp</dimen>
+    <!-- For the items in the CircularIconsPreference (contacts, apps, sound channels). -->
+    <dimen name="zen_mode_circular_icon_diameter">32dp</dimen>
+    <dimen name="zen_mode_circular_icon_inner_icon_size">20dp</dimen>
+    <dimen name="zen_mode_circular_icon_margin_between">4dp</dimen>
+    <dimen name="zen_mode_circular_icon_margin_vertical">8dp</dimen>
+    <dimen name="zen_mode_circular_icon_text_size">18dp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7bfd12a..84eb74f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -5477,6 +5477,8 @@
     <string name="daltonizer_mode_grayscale_title">Grayscale</string>
     <!-- Title shown for settings that controls color correction saturation level [CHAR LIMIT=45] -->
     <string name="daltonizer_saturation_title">Intensity</string>
+    <!-- The summary shown for settings that controls color correction intensity/saturation level. It is shown when intensity slider is grayed out and is not usable and it explains why it's not usable to the user. [CHAR LIMIT=NONE] -->
+    <string name="daltonizer_saturation_unavailable_summary">Unavailable for grayscale mode or when color correction is disabled</string>
     <!-- Summary shown for deuteranomaly (red-green color blindness) [CHAR LIMIT=45] -->
     <string name="daltonizer_mode_deuteranomaly_summary">Green weak, deuteranomaly</string>
     <!-- Summary shown for protanomaly (red-green color blindness) [CHAR LIMIT=45] -->
@@ -8124,9 +8126,9 @@
     <!--  Do not disturb: Subtitle for the Visual signals option to toggle on/off visual signals/alerts when the screen is on/when screen is off. [CHAR LIMIT=30] -->
     <string name="zen_mode_visual_signals_settings_subtitle">Allow visual signals</string>
 
-    <!-- Do not disturb: mode page section title [CHAR LIMIT=80] -->
+    <!-- Priority Modes: mode page section title [CHAR LIMIT=80] -->
     <string name="mode_interruption_filter_title">Stay focused</string>
-    <!-- Do not disturb: mode page section title [CHAR LIMIT=80] -->
+    <!-- Priority Modes: mode page section title [CHAR LIMIT=80] -->
     <string name="mode_device_effects_title">Additional actions</string>
 
     <!-- Summary for the Sound Do not Disturb option when DND isn't currently on. [CHAR LIMIT=NONE]-->
@@ -8871,9 +8873,10 @@
     <string name="notif_listener_more_settings_desc">More settings are available inside this app</string>
 
     <!-- Title for Polite Notifications setting  [CHAR LIMIT=45]-->
-    <string name="notification_polite_title">Adaptive Notifications</string>
-    <string name="notification_polite_main_control_title">Use adaptive notifications</string>
-    <string name="notification_polite_description">When you get many notifications within a short time, your phone will lower volume and minimize pop-ups on screen for up to two minutes. Calls, alarms, and priority conversations still vibrate, make a sound, or show up on the screen, and all notifications are easy to find when you pull down from the top of the screen.</string>
+    <string name="notification_polite_title">Notification cooldown</string>
+    <string name="notification_polite_main_control_title">Use notification cooldown</string>
+    <string name="notification_polite_description">When you receive many notifications within a short time, your device will lower its volume and minimize alerts for up to 2 minutes. Calls, alarms, and priority conversations are not affected.
+        \n\nNotifications received during the cooldown can be found by pulling down from the top of the screen.</string>
     <string name="notification_polite_work">Apply to work profiles</string>
     <string name="notification_polite_work_summary">Apply to work profile apps</string>
 
@@ -9365,6 +9368,8 @@
     <string name="zen_mode_apps_work_app"><xliff:g id="app_label" example="Chrome">%s</xliff:g> (Work)</string>
     <!-- Text displayed (for a brief time) while the list of bypassing apps is being fetched. Will be replaced by a zen_mode_apps_subtext. [CHAR_LIMIT=60] -->
     <string name="zen_mode_apps_calculating">Calculating\u2026</string>
+    <!-- Priority Modes: Format for a string displayed when there are more items (e.g. apps, contacts) that can be shown. For example, we show (A)(B)(C)(+5), where this string represents the "+5" value. Needs to be as compact as possible, since it will be drawn in a really small area. [CHAR_LIMIT=4] -->
+    <string name="zen_mode_plus_n_items">+<xliff:g id="number" example="42">%d</xliff:g></string>
 
     <!-- [CHAR LIMIT=100] Zen mode settings: Allow apps to bypass DND -->
     <string name="zen_mode_bypassing_apps">Allow apps to override</string>
@@ -9421,6 +9426,8 @@
     <!-- [CHAR LIMIT=120] Zen mode settings: Summary for people category -->
     <string name="zen_mode_people_some">Some people can interrupt</string>
     <!-- [CHAR LIMIT=120] Zen mode settings: Summary for people category -->
+    <string name="zen_mode_people_repeat_callers">Repeat callers can interrupt</string>
+    <!-- [CHAR LIMIT=120] Zen mode settings: Summary for people category -->
     <string name="zen_mode_people_all">All people can interrupt</string>
 
     <!-- [CHAR LIMIT=50] Zen mode settings: Repeat callers option -->
@@ -9478,6 +9485,19 @@
     <!-- Priority Modes: Hint for the EditText for editing a mode's name [CHAR LIMIT=30] -->
     <string name="zen_mode_edit_name_hint">Mode name</string>
 
+    <!-- Priority Modes: Trigger title for modes of type SCHEDULE_CALENDAR. [CHAR LIMIT=30] -->
+    <string name="zen_mode_trigger_title_schedule_calendar">Calendar events</string>
+    <!-- Priority Modes: Trigger title for modes of type BEDTIME. [CHAR LIMIT=30] -->
+    <string name="zen_mode_trigger_title_bedtime">Sleep schedule</string>
+    <!-- Priority Modes: Trigger title for modes of type DRIVING. [CHAR LIMIT=30] -->
+    <string name="zen_mode_trigger_title_driving">While driving</string>
+    <!-- Priority Modes: Generic trigger title for modes of other types [CHAR LIMIT=30] -->
+    <string name="zen_mode_trigger_title_generic">Linked to app</string>
+    <!-- Priority Modes: Generic trigger summary for modes where the owner app did not provide a triggerDescription but did provide a configurationActivity to call [CHAR LIMIT=60] -->
+    <string name="zen_mode_trigger_summary_settings_in_app">Info and settings in <xliff:g id="app_name" example="The Awesome App">%1$s</xliff:g></string>
+    <!-- Priority Modes: Generic trigger summary for modes where the owner app did not provide neither a triggerDescription nor a configurationActivity to call [CHAR LIMIT=60] -->
+    <string name="zen_mode_trigger_summary_managed_by_app">Managed by <xliff:g id="app_name" example="The Awesome App">%1$s</xliff:g></string>
+
     <!-- Content description for help icon button [CHAR LIMIT=20] -->
     <string name="warning_button_text">Warning</string>
 
@@ -12235,16 +12255,11 @@
     <!-- Default title for the settings panel [CHAR LIMIT=NONE] -->
     <string name="settings_panel_title">Settings Panel</string>
 
-    <!-- Title for enabling freeform windows (desktop mode) developer option toggle [CHAR LIMIT=50] -->
-    <string name="enable_desktop_mode">Enable freeform windows</string>
-    <!-- Summary for enabling freeform windows (desktop mode) developer option toggle [CHAR LIMIT=NONE] -->
-    <string name="enable_desktop_mode_summary">Enable support for freeform windows.</string>
+    <!-- Title for a toggle that enables freeform windowing experiences. Freeform windowing experiences are features involving apps running in resizable windows. [CHAR LIMIT=50] -->
+    <string name="enable_desktop_mode">Enable freeform windowing experiences</string>
 
-    <!-- TODO(b/348193756): Rename resources for this toggle to indicate that it is for secondary display -->
-    <!-- Title for enabling freeform windows (desktop mode) on secondary display developer option toggle  [CHAR LIMIT=50] -->
-    <string name="force_desktop_mode">Enable freeform windowing on second display</string>
-    <!-- Summary for enabling freeform windows (desktop mode) on secondary display developer option toggle [CHAR LIMIT=NONE] -->
-    <string name="force_desktop_mode_summary">Enable freeform windows only on secondary display.</string>
+    <!-- Title for a toggle that enables desktop mode on secondary display. [CHAR LIMIT=50] -->
+    <string name="enable_desktop_mode_on_secondary_display">Enable desktop mode on secondary display</string>
 
     <!-- UI debug setting: enable non-resizables in multi window [CHAR LIMIT=60] -->
     <string name="enable_non_resizable_multi_window">Enable non-resizable in multi window</string>
@@ -13207,14 +13222,12 @@
     <!-- The content description for accessibility tools of the customize button. It specifies which screensaver the user is customizing [CHAR LIMIT=NONE] -->
     <string name="customize_button_description">Customize <xliff:g id="screensaver_name" example="Art Gallery">%1$s</xliff:g></string>
 
-    <!-- Dialog body text used to explain a reboot is required after enabling freeform support for
-    it to work [CHAR LIMIT=none] -->
-    <string name="reboot_dialog_enable_freeform_support">A reboot is required to enable legacy freeform windowing support.</string>
-    <!-- Dialog body text used to explain a reboot is required after overriding freeform windowing (desktop mode) support. [CHAR LIMIT=none] -->
-    <string name="reboot_dialog_override_desktop_mode">A reboot is required to change freeform windowing support.</string>
-    <!-- Dialog body text used to explain a reboot is required after forcing freeform windowing (desktop mode) on
-    secondary displays. [CHAR LIMIT=none] -->
-    <string name="reboot_dialog_force_desktop_mode">A reboot is required to force freeform windowing on secondary displays.</string>
+    <!-- Dialog body text used to explain a reboot is required after enabling freeform window support for it to work. Freeform window is when an app runs in a resizable window. [CHAR LIMIT=none] -->
+    <string name="reboot_dialog_enable_freeform_support">A reboot is required to enable freeform window support.</string>
+    <!-- Dialog body text used to explain a reboot is required after updating availability of freeform windowing experiences. Freeform windowing experiences are features involving apps running in resizable windows. [CHAR LIMIT=none] -->
+    <string name="reboot_dialog_override_desktop_mode">A reboot is required to update availability of freeform windowing experiences.</string>
+    <!-- Dialog body text used to explain a reboot is required after enabling desktop mode on secondary displays. [CHAR LIMIT=none] -->
+    <string name="reboot_dialog_enable_desktop_mode_on_secondary_display">A reboot is required to enable desktop mode on secondary displays.</string>
     <!-- Text on the dialog button to reboot the device now [CHAR LIMIT=50] -->
     <string name="reboot_dialog_reboot_now">Reboot now</string>
     <!-- Text on the dialog button to reboot the device later [CHAR LIMIT=50] -->
@@ -13739,6 +13752,10 @@
     <string name="audio_streams_main_page_qr_code_scanner_summary">Scan an audio stream QR code to listen with <xliff:g example="LE headset" id="device_name">%1$s</xliff:g></string>
     <!-- Le audio streams password dialog  [CHAR LIMIT=NONE] -->
     <string name="audio_streams_main_page_password_dialog_cannot_edit">Can\u0027t edit password while sharing. To change the password, first turn off audio sharing.</string>
+    <!-- Text for audio sharing qrcode image [CHAR LIMIT=none]-->
+    <string name="audio_streams_qr_code_page_image_label">QR code</string>
+    <!-- Text for audio sharing qrcode scanner [CHAR LIMIT=none]-->
+    <string name="audio_streams_qr_code_scanner_label">QR code scanner</string>
 
 
     <!-- url for learning more about bluetooth audio sharing -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 2d40af0..c02378e 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -955,11 +955,16 @@
         <item name="biometricsEnrollProgressHelpWithTalkback">@color/udfps_enroll_progress_help_with_talkback</item>
     </style>
 
-    <style name="ScreenLockPasswordHintTextFontStyle">
+    <style name="ScreenLockPasswordHintTextFontStyleError">
         <item name="android:textColor">?android:attr/colorError</item>
         <item name="android:fontFamily">google-sans-text</item>
     </style>
 
+    <style name="ScreenLockPasswordHintTextFontStyle">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:fontFamily">google-sans-text</item>
+    </style>
+
     <style name="PrivateSpaceSetupTextFontStyle" parent="@android:style/TextAppearance.DeviceDefault">
         <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:fontFamily">google-sans-text</item>
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index 9420f59..88abadb 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -739,18 +739,15 @@
 
         <SwitchPreferenceCompat
             android:key="override_desktop_mode_features"
-            android:title="@string/enable_desktop_mode"
-            android:summary="@string/enable_desktop_mode_summary" />
+            android:title="@string/enable_desktop_mode" />
 
         <SwitchPreferenceCompat
             android:key="enable_freeform_support"
-            android:title="@string/enable_freeform_support"
-            android:summary="@string/enable_freeform_support_summary" />
+            android:title="@string/enable_freeform_support" />
 
         <SwitchPreferenceCompat
             android:key="force_desktop_mode_on_external_displays"
-            android:title="@string/force_desktop_mode"
-            android:summary="@string/force_desktop_mode_summary" />
+            android:title="@string/enable_desktop_mode_on_secondary_display"/>
 
         <SwitchPreferenceCompat
             android:key="enable_non_resizable_multi_window"
diff --git a/res/xml/modes_rule_settings.xml b/res/xml/modes_rule_settings.xml
index 0c687b2..2464c25 100644
--- a/res/xml/modes_rule_settings.xml
+++ b/res/xml/modes_rule_settings.xml
@@ -28,6 +28,21 @@
             android:selectable="false"
             android:layout="@layout/modes_activation_button"/>
 
+    <!-- automatic trigger section; preference changes programmatically depending on type -->
+    <PreferenceCategory
+        android:key="zen_automatic_trigger_category"
+        android:title="@string/zen_mode_automatic_trigger_title">
+        <!-- For configuring the trigger on tap  and enabling/disabling the mode with the switch. -->
+        <com.android.settingslib.PrimarySwitchPreference
+            android:key="zen_automatic_trigger_settings" />
+        <!-- For adding a trigger for custom manual modes (no switch). -->
+        <Preference
+            android:key="zen_add_automatic_trigger"
+            android:title="@string/zen_mode_select_schedule"
+            android:icon="@drawable/ic_add_24dp"
+            settings:isPreferenceVisible="false" />
+    </PreferenceCategory>
+
     <PreferenceCategory
             android:title="@string/mode_interruption_filter_title"
             android:key="modes_filters">
@@ -36,25 +51,17 @@
             android:key="allow_filtering"
             android:title="@string/mode_notification_filter_title"/>
 
-        <Preference
-                android:key="zen_mode_people"
-                android:title="@string/zen_category_people"/>
+        <com.android.settings.notification.modes.CircularIconsPreference
+            android:key="zen_mode_people"
+            android:title="@string/zen_category_people" />
 
-        <Preference
+        <com.android.settings.notification.modes.CircularIconsPreference
             android:key="zen_mode_apps"
-            android:title="@string/zen_category_apps"/>
+            android:title="@string/zen_category_apps" />
 
-        <Preference
-                android:key="zen_other_settings"
-                android:title="@string/zen_category_exceptions" />
-    </PreferenceCategory>
-
-    <!-- automatic trigger section; preference changes programmatically depending on type -->
-    <PreferenceCategory
-        android:key="zen_automatic_trigger_category"
-        android:title="@string/zen_mode_automatic_trigger_title">
-        <com.android.settingslib.PrimarySwitchPreference
-            android:key="zen_automatic_trigger_settings" />
+        <com.android.settings.notification.modes.CircularIconsPreference
+            android:key="zen_other_settings"
+            android:title="@string/zen_category_exceptions" />
     </PreferenceCategory>
 
     <PreferenceCategory
diff --git a/src/com/android/settings/MainClear.java b/src/com/android/settings/MainClear.java
index 9d219d7..9dadcb9 100644
--- a/src/com/android/settings/MainClear.java
+++ b/src/com/android/settings/MainClear.java
@@ -182,10 +182,13 @@
         }
 
         if (requestCode == KEYGUARD_REQUEST) {
+            final int userId = getActivity().getUserId();
             if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
                     false /* biometricsSuccessfullyAuthenticated */,
-                    false /* biometricsAuthenticationRequested */)) {
-                Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRICS_REQUEST);
+                    false /* biometricsAuthenticationRequested */,
+                    userId)) {
+                Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRICS_REQUEST,
+                        userId);
                 return;
             }
         }
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index e94769a..add5604 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -1487,23 +1487,25 @@
 
     /**
      * Request biometric authentication if all requirements for mandatory biometrics is satisfied.
-     * @param context of the corresponding activity/fragment
+     *
+     * @param context                             of the corresponding activity/fragment
      * @param biometricsSuccessfullyAuthenticated if the user has already authenticated using
      *                                            biometrics
-     * @param biometricsAuthenticationRequested if the activity/fragment has already requested for
-     *                                          biometric prompt
+     * @param biometricsAuthenticationRequested   if the activity/fragment has already requested for
+     *                                            biometric prompt
+     * @param userId                              user id for the authentication request
      * @return true if all requirements for mandatory biometrics is satisfied
      */
     public static boolean requestBiometricAuthenticationForMandatoryBiometrics(
             @NonNull Context context,
             boolean biometricsSuccessfullyAuthenticated,
-            boolean biometricsAuthenticationRequested) {
+            boolean biometricsAuthenticationRequested, int userId) {
         final BiometricManager biometricManager = context.getSystemService(BiometricManager.class);
         if (biometricManager == null) {
             Log.e(TAG, "Biometric Manager is null.");
             return false;
         }
-        final int status = biometricManager.canAuthenticate(
+        final int status = biometricManager.canAuthenticate(userId,
                 BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
         return android.hardware.biometrics.Flags.mandatoryBiometrics()
                 && status == BiometricManager.BIOMETRIC_SUCCESS
@@ -1513,15 +1515,16 @@
 
     /**
      * Launch biometric prompt for mandatory biometrics. Call
-     * {@link #requestBiometricAuthenticationForMandatoryBiometrics(Context, boolean, boolean)}
+     * {@link #requestBiometricAuthenticationForMandatoryBiometrics(Context, boolean, boolean, int)}
      * to check if all requirements for mandatory biometrics is satisfied
      * before launching biometric prompt.
      *
-     * @param fragment corresponding fragment of the surface
+     * @param fragment    corresponding fragment of the surface
      * @param requestCode for starting the new activity
+     * @param userId      user id for the authentication request
      */
     public static void launchBiometricPromptForMandatoryBiometrics(@NonNull Fragment fragment,
-            int requestCode) {
+            int requestCode, int userId) {
         final Intent intent = new Intent();
         intent.putExtra(BIOMETRIC_PROMPT_AUTHENTICATORS,
                 BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
@@ -1529,8 +1532,10 @@
                 fragment.getString(R.string.cancel));
         intent.putExtra(KeyguardManager.EXTRA_DESCRIPTION,
                 fragment.getString(R.string.mandatory_biometrics_prompt_description));
+        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_ALLOW_ANY_USER, true);
+        intent.putExtra(EXTRA_USER_ID, userId);
         intent.setClassName(SETTINGS_PACKAGE_NAME,
-                ConfirmDeviceCredentialActivity.class.getName());
+                ConfirmDeviceCredentialActivity.InternalActivity.class.getName());
         fragment.startActivityForResult(intent, requestCode);
     }
 
diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java
index d746434..d01806a 100644
--- a/src/com/android/settings/accessibility/AccessibilitySettings.java
+++ b/src/com/android/settings/accessibility/AccessibilitySettings.java
@@ -75,7 +75,8 @@
     private static final String CATEGORY_AUDIO = "audio_category";
     private static final String CATEGORY_SPEECH = "speech_category";
     private static final String CATEGORY_DISPLAY = "display_category";
-    private static final String CATEGORY_DOWNLOADED_SERVICES = "user_installed_services_category";
+    @VisibleForTesting
+    static final String CATEGORY_DOWNLOADED_SERVICES = "user_installed_services_category";
     private static final String CATEGORY_KEYBOARD_OPTIONS = "physical_keyboard_options_category";
     @VisibleForTesting
     static final String CATEGORY_INTERACTION_CONTROL = "interaction_control_category";
@@ -154,7 +155,7 @@
     };
 
     @VisibleForTesting
-    final AccessibilitySettingsContentObserver mSettingsContentObserver;
+    AccessibilitySettingsContentObserver mSettingsContentObserver;
 
     private final Map<String, PreferenceCategory> mCategoryToPrefCategoryMap =
             new ArrayMap<>();
@@ -168,9 +169,14 @@
     private boolean mIsForeground = true;
 
     public AccessibilitySettings() {
+        mSettingsContentObserver = new AccessibilitySettingsContentObserver(mHandler);
+    }
+
+    private void initializeSettingsContentObserver() {
         // Observe changes to anything that the shortcut can toggle, so we can reflect updates
         final Collection<AccessibilityShortcutController.FrameworkFeatureInfo> features =
-                AccessibilityShortcutController.getFrameworkShortcutFeaturesMap().values();
+                AccessibilityShortcutController
+                        .getFrameworkShortcutFeaturesMap().values();
         final List<String> shortcutFeatureKeys = new ArrayList<>(features.size());
         for (AccessibilityShortcutController.FrameworkFeatureInfo feature : features) {
             final String key = feature.getSettingKey();
@@ -188,7 +194,6 @@
         shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_STICKY_KEYS);
         shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_SLOW_KEYS);
         shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS);
-        mSettingsContentObserver = new AccessibilitySettingsContentObserver(mHandler);
         mSettingsContentObserver.registerKeysToObserverCallback(shortcutFeatureKeys,
                 key -> onContentChanged());
     }
@@ -213,6 +218,7 @@
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
+        initializeSettingsContentObserver();
         initializeAllPreferences();
         updateAllPreferences();
         mNeedPreferencesUpdate = false;
@@ -375,6 +381,7 @@
     }
 
     protected void updateServicePreferences() {
+        final AccessibilityManager a11yManager = AccessibilityManager.getInstance(getPrefContext());
         // Since services category is auto generated we have to do a pass
         // to generate it since services can come and go and then based on
         // the global accessibility state to decided whether it is enabled.
@@ -405,8 +412,18 @@
                 AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM,
                 mCategoryToPrefCategoryMap.get(CATEGORY_INTERACTION_CONTROL));
 
-        final List<RestrictedPreference> preferenceList = getInstalledAccessibilityList(
-                getPrefContext());
+        final List<AccessibilityShortcutInfo> installedShortcutList =
+                a11yManager.getInstalledAccessibilityShortcutListAsUser(getPrefContext(),
+                        UserHandle.myUserId());
+        final List<AccessibilityServiceInfo> modifiableInstalledServiceList =
+                new ArrayList<>(a11yManager.getInstalledAccessibilityServiceList());
+        final List<RestrictedPreference> preferenceList = getInstalledAccessibilityPreferences(
+                getPrefContext(), installedShortcutList, modifiableInstalledServiceList);
+
+        if (Flags.checkPrebundledIsPreinstalled()) {
+            removeNonPreinstalledComponents(mPreBundledServiceComponentToCategoryMap,
+                    installedShortcutList, modifiableInstalledServiceList);
+        }
 
         final PreferenceCategory downloadedServicesCategory =
                 mCategoryToPrefCategoryMap.get(CATEGORY_DOWNLOADED_SERVICES);
@@ -451,13 +468,21 @@
         updatePreferenceCategoryVisibility(CATEGORY_KEYBOARD_OPTIONS);
     }
 
-    private List<RestrictedPreference> getInstalledAccessibilityList(Context context) {
-        final AccessibilityManager a11yManager = AccessibilityManager.getInstance(context);
+    /**
+     * Gets a list of {@link RestrictedPreference}s for the provided a11y shortcuts and services.
+     *
+     * <p>{@code modifiableInstalledServiceList} may be modified to remove any entries with
+     * matching package name and label as an entry in {@code installedShortcutList}.
+     *
+     * @param installedShortcutList          A list of installed {@link AccessibilityShortcutInfo}s.
+     * @param modifiableInstalledServiceList A modifiable list of installed
+     *                                       {@link AccessibilityServiceInfo}s.
+     */
+    private List<RestrictedPreference> getInstalledAccessibilityPreferences(Context context,
+            List<AccessibilityShortcutInfo> installedShortcutList,
+            List<AccessibilityServiceInfo> modifiableInstalledServiceList) {
         final RestrictedPreferenceHelper preferenceHelper = new RestrictedPreferenceHelper(context);
 
-        final List<AccessibilityShortcutInfo> installedShortcutList =
-                a11yManager.getInstalledAccessibilityShortcutListAsUser(context,
-                        UserHandle.myUserId());
         final List<AccessibilityActivityPreference> activityList =
                 preferenceHelper.createAccessibilityActivityPreferenceList(installedShortcutList);
         final Set<Pair<String, CharSequence>> packageLabelPairs =
@@ -466,16 +491,14 @@
                                 a11yActivityPref.getPackageName(), a11yActivityPref.getLabel())
                         ).collect(Collectors.toSet());
 
-        // Remove duplicate item here, new a ArrayList to copy unmodifiable list result
-        // (getInstalledAccessibilityServiceList).
-        final List<AccessibilityServiceInfo> installedServiceList = new ArrayList<>(
-                a11yManager.getInstalledAccessibilityServiceList());
+        // Remove duplicate A11yServices that are already shown as A11yActivities.
         if (!packageLabelPairs.isEmpty()) {
-            installedServiceList.removeIf(
+            modifiableInstalledServiceList.removeIf(
                     target -> containsPackageAndLabelInList(packageLabelPairs, target));
         }
         final List<RestrictedPreference> serviceList =
-                preferenceHelper.createAccessibilityServicePreferenceList(installedServiceList);
+                preferenceHelper.createAccessibilityServicePreferenceList(
+                        modifiableInstalledServiceList);
 
         final List<RestrictedPreference> preferenceList = new ArrayList<>();
         preferenceList.addAll(activityList);
@@ -484,6 +507,22 @@
         return preferenceList;
     }
 
+    private static void removeNonPreinstalledComponents(
+            Map<ComponentName, PreferenceCategory> componentToCategory,
+            List<AccessibilityShortcutInfo> shortcutInfos,
+            List<AccessibilityServiceInfo> serviceInfos) {
+        for (AccessibilityShortcutInfo info : shortcutInfos) {
+            if (!info.getActivityInfo().applicationInfo.isSystemApp()) {
+                componentToCategory.remove(info.getComponentName());
+            }
+        }
+        for (AccessibilityServiceInfo info : serviceInfos) {
+            if (!info.getResolveInfo().serviceInfo.applicationInfo.isSystemApp()) {
+                componentToCategory.remove(info.getComponentName());
+            }
+        }
+    }
+
     private boolean containsPackageAndLabelInList(
             Set<Pair<String, CharSequence>> packageLabelPairs,
             AccessibilityServiceInfo targetServiceInfo) {
diff --git a/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java b/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java
index 7dcd661..2997185 100644
--- a/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java
+++ b/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java
@@ -17,26 +17,50 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
 import android.provider.Settings;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
 import com.android.server.accessibility.Flags;
+import com.android.settings.R;
 import com.android.settings.core.SliderPreferenceController;
 import com.android.settings.widget.SeekBarPreference;
 
 /**
  * The controller of the seekbar preference for the saturation level of color correction.
  */
-public class DaltonizerSaturationSeekbarPreferenceController extends SliderPreferenceController {
+public class DaltonizerSaturationSeekbarPreferenceController
+        extends SliderPreferenceController
+        implements DefaultLifecycleObserver {
 
     private static final int DEFAULT_SATURATION_LEVEL = 7;
     private static final int SATURATION_MAX = 10;
-    private static final int SATURATION_MIN = 0;
+    private static final int SATURATION_MIN = 1;
 
     private int mSliderPosition;
     private final ContentResolver mContentResolver;
 
+    @Nullable
+    private SeekBarPreference mPreference;
+
+    public final ContentObserver mContentObserver = new ContentObserver(
+            new Handler(Looper.getMainLooper())) {
+        @Override
+        public void onChange(boolean selfChange) {
+            if (mPreference != null) {
+                updateState(mPreference);
+            }
+        }
+    };
+
     public DaltonizerSaturationSeekbarPreferenceController(Context context,
             String preferenceKey) {
         super(context, preferenceKey);
@@ -50,9 +74,32 @@
     }
 
     @Override
+    public void onStart(@NonNull LifecycleOwner owner) {
+        if (!isAvailable()) return;
+        mContentResolver.registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER),
+                true,
+                mContentObserver
+        );
+        mContentResolver.registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED),
+                true,
+                mContentObserver
+        );
+    }
+
+    @Override
+    public void onStop(@NonNull LifecycleOwner owner) {
+        if (!isAvailable()) return;
+        mContentResolver.unregisterContentObserver(mContentObserver);
+        mPreference = null;
+    }
+
+    @Override
     public void displayPreference(PreferenceScreen screen) {
         super.displayPreference(screen);
         SeekBarPreference preference = screen.findPreference(getPreferenceKey());
+        mPreference = preference;
         preference.setMax(getMax());
         preference.setMin(getMin());
         preference.setProgress(mSliderPosition);
@@ -62,7 +109,7 @@
     @Override
     public int getAvailabilityStatus() {
         if (Flags.enableColorCorrectionSaturation()) {
-            return AVAILABLE;
+            return shouldSeekBarEnabled() ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
         }
         return CONDITIONALLY_UNAVAILABLE;
     }
@@ -86,6 +133,21 @@
     }
 
     @Override
+    public void updateState(Preference preference) {
+        if (preference == null) {
+            return;
+        }
+
+        var shouldSeekbarEnabled = shouldSeekBarEnabled();
+        // setSummary not working yet on SeekBarPreference.
+        String summary = shouldSeekbarEnabled
+                ? ""
+                : mContext.getString(R.string.daltonizer_saturation_unavailable_summary);
+        preference.setSummary(summary);
+        preference.setEnabled(shouldSeekbarEnabled);
+    }
+
+    @Override
     public int getMax() {
         return SATURATION_MAX;
     }
@@ -94,4 +156,16 @@
     public int getMin() {
         return SATURATION_MIN;
     }
+
+    private boolean shouldSeekBarEnabled() {
+        int enabled = Settings.Secure.getInt(
+                mContentResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0);
+        int mode = Settings.Secure.getInt(
+                mContentResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, -1);
+
+        // enabled == 0 is disabled and also default.
+        // mode == 0 is gray scale where saturation level isn't applicable.
+        // mode == -1 is disabled and also default.
+        return enabled != 0 && mode != -1 && mode != 0;
+    }
 }
diff --git a/src/com/android/settings/applications/AppStorageSettings.java b/src/com/android/settings/applications/AppStorageSettings.java
index e45657f..0f52053 100644
--- a/src/com/android/settings/applications/AppStorageSettings.java
+++ b/src/com/android/settings/applications/AppStorageSettings.java
@@ -324,9 +324,11 @@
     private void initMoveDialog() {
         final Context context = getActivity();
         final StorageManager storage = context.getSystemService(StorageManager.class);
-
-        final List<VolumeInfo> candidates = context.getPackageManager()
-                .getPackageCandidateVolumes(mAppEntry.info);
+        final PackageManager pm = context.getPackageManager();
+        final List<VolumeInfo> candidates =
+            mAppEntry != null && pm != null
+                ? pm.getPackageCandidateVolumes(mAppEntry.info)
+                : Collections.emptyList();
         if (candidates.size() > 1) {
             Collections.sort(candidates, VolumeInfo.getDescriptionComparator());
 
diff --git a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
index b9a0b93..835f3a8 100644
--- a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
+++ b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
@@ -144,9 +144,10 @@
             launchChooseOrConfirmLock();
         } else if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(
                 getActivity(), mBiometricsSuccessfullyAuthenticated,
-                mBiometricsAuthenticationRequested)) {
+                mBiometricsAuthenticationRequested, mUserId)) {
             mBiometricsAuthenticationRequested = true;
-            Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST);
+            Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST,
+                    mUserId);
         }
 
         updateUnlockPhonePreferenceSummary();
@@ -161,10 +162,11 @@
     public void onResume() {
         super.onResume();
         if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
-                mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested)
+                mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested, mUserId)
                 && mGkPwHandle != 0L) {
             mBiometricsAuthenticationRequested = true;
-            Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST);
+            Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST,
+                    mUserId);
         }
         if (!mConfirmCredential) {
             mDoNotFinishActivity = false;
diff --git a/src/com/android/settings/biometrics/face/FaceSettings.java b/src/com/android/settings/biometrics/face/FaceSettings.java
index 2a0dd83..305d670 100644
--- a/src/com/android/settings/biometrics/face/FaceSettings.java
+++ b/src/com/android/settings/biometrics/face/FaceSettings.java
@@ -289,9 +289,11 @@
                 finish();
             }
         } else if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
-                mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested)) {
+                mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested,
+                mUserId)) {
             mBiometricsAuthenticationRequested = true;
-            Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST);
+            Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST,
+                    mUserId);
         } else {
             mAttentionController.setToken(mToken);
             mEnrollController.setToken(mToken);
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintRemoveSidecar.java b/src/com/android/settings/biometrics/fingerprint/FingerprintRemoveSidecar.java
index 73eccdc..2737d38 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintRemoveSidecar.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintRemoveSidecar.java
@@ -23,6 +23,7 @@
 import android.util.Log;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.settings.core.InstrumentedFragment;
 
@@ -51,7 +52,8 @@
         }
     }
 
-    private FingerprintManager.RemovalCallback
+    @VisibleForTesting
+    FingerprintManager.RemovalCallback
             mRemoveCallback = new FingerprintManager.RemovalCallback() {
         @Override
         public void onRemovalSucceeded(Fingerprint fingerprint, int remaining) {
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
index cb7d617..815c08e 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
@@ -226,6 +226,7 @@
         private static final int MSG_FINGER_AUTH_FAIL = 1002;
         private static final int MSG_FINGER_AUTH_ERROR = 1003;
         private static final int MSG_FINGER_AUTH_HELP = 1004;
+        private static final int MSG_RELOAD_FINGERPRINT_TEMPLATES = 1005;
 
         private static final int CONFIRM_REQUEST = 101;
         @VisibleForTesting
@@ -313,6 +314,8 @@
                         if (activity != null) {
                             Toast.makeText(activity, errString, Toast.LENGTH_SHORT);
                         }
+                        mHandler.obtainMessage(MSG_RELOAD_FINGERPRINT_TEMPLATES)
+                                .sendToTarget();
                         updateDialog();
                     }
 
@@ -331,11 +334,7 @@
                 switch (msg.what) {
                     case MSG_REFRESH_FINGERPRINT_TEMPLATES:
                         removeFingerprintPreference(msg.arg1);
-                        updateAddPreference();
-                        if (isSfps()) {
-                            updateFingerprintUnlockCategoryVisibility();
-                        }
-                        updatePreferences();
+                        updatePreferencesAfterFingerprintRemoved();
                         break;
                     case MSG_FINGER_AUTH_SUCCESS:
                         highlightFingerprintItem(msg.arg1);
@@ -347,6 +346,9 @@
                     case MSG_FINGER_AUTH_ERROR:
                         handleError(msg.arg1 /* errMsgId */, (CharSequence) msg.obj /* errStr */);
                         break;
+                    case MSG_RELOAD_FINGERPRINT_TEMPLATES:
+                        updatePreferencesAfterFingerprintRemoved();
+                        break;
                     case MSG_FINGER_AUTH_HELP: {
                         // Not used
                     }
@@ -483,9 +485,11 @@
                     mLaunchedConfirm = true;
                     launchChooseOrConfirmLock();
                 } else if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
-                        mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested)) {
+                        mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested,
+                        mUserId)) {
                     mBiometricsAuthenticationRequested = true;
-                    Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST);
+                    Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST,
+                            mUserId);
                 } else if (!mHasFirstEnrolled) {
                     mIsEnrolling = true;
                     addFirstFingerprint(null);
@@ -568,6 +572,7 @@
 
         protected void removeFingerprintPreference(int fingerprintId) {
             String name = genKey(fingerprintId);
+            Log.e(TAG, "removeFingerprintPreference : " + fingerprintId);
             Preference prefToRemove = findPreference(name);
             if (prefToRemove != null) {
                 if (!getPreferenceScreen().removePreference(prefToRemove)) {
@@ -692,6 +697,13 @@
                     });
         }
 
+        private void updatePreferencesAfterFingerprintRemoved() {
+            updateAddPreference();
+            if (isSfps()) {
+                updateFingerprintUnlockCategoryVisibility();
+            }
+        }
+
         private void updateAddPreference() {
             if (getActivity() == null) {
                 return; // Activity went away
@@ -767,9 +779,11 @@
                     .getUdfpsEnrollCalibrator(getActivity().getApplicationContext(), null, null);
 
             if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
-                    mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested)) {
+                    mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested,
+                    mUserId)) {
                 mBiometricsAuthenticationRequested = true;
-                Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST);
+                Utils.launchBiometricPromptForMandatoryBiometrics(this,
+                        BIOMETRIC_AUTH_REQUEST, mUserId);
             }
         }
 
@@ -1182,7 +1196,7 @@
 
             @Override
             public int getMetricsCategory() {
-                return SettingsEnums.DIALOG_FINGERPINT_EDIT;
+                return SettingsEnums.DIALOG_FINGERPRINT_DELETE;
             }
 
             @Override
@@ -1344,7 +1358,7 @@
 
             @Override
             public int getMetricsCategory() {
-                return SettingsEnums.DIALOG_FINGERPINT_EDIT;
+                return SettingsEnums.DIALOG_FINGERPRINT_RENAME;
             }
         }
 
diff --git a/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt b/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt
index 215692a..9bc920a 100644
--- a/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt
@@ -16,7 +16,9 @@
 
 package com.android.settings.biometrics.fingerprint2
 
+import android.content.pm.PackageManager
 import android.hardware.fingerprint.FingerprintManager
+import android.os.ServiceManager.ServiceNotFoundException
 import android.view.MotionEvent
 import android.view.accessibility.AccessibilityManager
 import androidx.fragment.app.FragmentActivity
@@ -74,8 +76,15 @@
   private val backgroundDispatcher = executorService.asCoroutineDispatcher()
   private val applicationScope = MainScope()
   private val gateKeeperPasswordProvider = GatekeeperPasswordProvider(LockPatternUtils(context))
-  private val fingerprintManager =
-    context.getSystemService(FragmentActivity.FINGERPRINT_SERVICE) as FingerprintManager?
+  private val fingerprintManager = try {
+    if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+      context.getSystemService(FragmentActivity.FINGERPRINT_SERVICE) as FingerprintManager?
+    } else {
+      null
+    }
+  } catch (exception: ServiceNotFoundException){
+    null
+  }
 
   private val fingerprintSensorRepository: FingerprintSensorRepository =
     FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, applicationScope)
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintDeletionDialog.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintDeletionDialog.kt
index 46f64de..cb36721 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintDeletionDialog.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintDeletionDialog.kt
@@ -42,7 +42,7 @@
   lateinit var onCancelListener: DialogInterface.OnCancelListener
 
   override fun getMetricsCategory(): Int {
-    return SettingsEnums.DIALOG_FINGERPINT_EDIT
+    return SettingsEnums.DIALOG_FINGERPRINT_DELETE
   }
 
   override fun onCancel(dialog: DialogInterface) {
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsRenameDialog.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsRenameDialog.kt
index 9fef0c5..2f251de 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsRenameDialog.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsRenameDialog.kt
@@ -95,7 +95,7 @@
   }
 
   override fun getMetricsCategory(): Int {
-    return SettingsEnums.DIALOG_FINGERPINT_EDIT
+    return SettingsEnums.DIALOG_FINGERPRINT_RENAME
   }
 
   companion object {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
index bfccdc4..0a90e7b 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
@@ -92,6 +92,8 @@
         shareButton.setVisibility(View.VISIBLE);
         shareButton.setImageDrawable(getContext().getDrawable(R.drawable.ic_qrcode_24dp));
         shareButton.setOnClickListener(unused -> launchAudioSharingQrCodeFragment());
+        shareButton.setContentDescription(
+                getContext().getString(R.string.audio_sharing_qrcode_button_label));
     }
 
     private void configureInvisibleStateForQrCodeIcon(ImageButton shareButton, View divider) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java
index e4c0794..47f9c75 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java
@@ -55,7 +55,7 @@
     @Override
     public final View onCreateView(
             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        return inflater.inflate(R.xml.bluetooth_audio_streams_qr_code, container, false);
+        return inflater.inflate(R.layout.bluetooth_audio_streams_qr_code, container, false);
     }
 
     @Override
diff --git a/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceController.java b/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceController.java
index ff513c2..0d3d835 100644
--- a/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceController.java
+++ b/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceController.java
@@ -69,7 +69,8 @@
                 isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF);
         if (isEnabled && mFragment != null) {
             RebootConfirmationDialogFragment.show(
-                    mFragment, R.string.reboot_dialog_force_desktop_mode, this);
+                    mFragment, R.string.reboot_dialog_enable_desktop_mode_on_secondary_display,
+                    this);
         }
         return true;
     }
diff --git a/src/com/android/settings/development/FreeformWindowsPreferenceController.java b/src/com/android/settings/development/FreeformWindowsPreferenceController.java
index 7cf7738..c02ffa7 100644
--- a/src/com/android/settings/development/FreeformWindowsPreferenceController.java
+++ b/src/com/android/settings/development/FreeformWindowsPreferenceController.java
@@ -16,8 +16,9 @@
 
 package com.android.settings.development;
 
+import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
+
 import android.content.Context;
-import android.os.Build;
 import android.provider.Settings;
 
 import androidx.annotation.Nullable;
@@ -40,7 +41,8 @@
     @VisibleForTesting
     static final int SETTING_VALUE_ON = 1;
 
-    @Nullable private final DevelopmentSettingsDashboardFragment mFragment;
+    @Nullable
+    private final DevelopmentSettingsDashboardFragment mFragment;
 
     public FreeformWindowsPreferenceController(
             Context context, @Nullable DevelopmentSettingsDashboardFragment fragment) {
@@ -49,6 +51,13 @@
     }
 
     @Override
+    public boolean isAvailable() {
+        // When devices have the system feature FEATURE_FREEFORM_WINDOW_MANAGEMENT, freeform
+        // mode is enabled automatically, and this toggle is not needed.
+        return !mContext.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT);
+    }
+
+    @Override
     public String getPreferenceKey() {
         return ENABLE_FREEFORM_SUPPORT_KEY;
     }
@@ -80,9 +89,4 @@
                 Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, SETTING_VALUE_OFF);
         ((TwoStatePreference) mPreference).setChecked(false);
     }
-
-    @VisibleForTesting
-    String getBuildType() {
-        return Build.TYPE;
-    }
 }
diff --git a/src/com/android/settings/development/bluetooth/BluetoothStackLogPreferenceController.java b/src/com/android/settings/development/bluetooth/BluetoothStackLogPreferenceController.java
index 23d4cc6..9f7512c 100644
--- a/src/com/android/settings/development/bluetooth/BluetoothStackLogPreferenceController.java
+++ b/src/com/android/settings/development/bluetooth/BluetoothStackLogPreferenceController.java
@@ -30,85 +30,184 @@
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settingslib.development.DeveloperOptionsPreferenceController;
 
+/**
+ * This preference represents the default log level for the Bluetooth stack
+ *
+ * The default log level is captured and held in an Android Log Framework log tag, using "bluetooth"
+ * as the tag name. The Log framework does not provide methods to directly write a log tag value,
+ * but instead leverages special system properties to hold the value of a log tag.
+ *
+ * This preferences aims to keep the selection in sync with the currently set log tag value. It
+ * writes directly to the system properties that hold the level associated with the bluetooth log
+ * tag. It leverages the Log.isLoggable("bluetooth", level) function to discern the current value.
+ * The default level is INFO.
+ *
+ * This value is read once at start of the Bluetooth stack. To use a new value once setting it, be
+ * sure to turn Bluetooth off and back on again.
+ */
 public class BluetoothStackLogPreferenceController extends DeveloperOptionsPreferenceController
         implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {
+    private static final String TAG = BluetoothStackLogPreferenceController.class.getSimpleName();
+
+    private static final String PREFERENCE_KEY = "bt_stack_log_level";
 
     /* Ensure that the indexes match with bt_stack_log_values and bt_stack_log_entries ordering */
-    private static final String PREFERENCE_KEY = "bt_stack_log_level";
-    @VisibleForTesting static final int BTSTACK_LOG_MODE_VERBOSE_INDEX = 0;
-    @VisibleForTesting static final int BTSTACK_LOG_MODE_DEBUG_INDEX = 1;
-    @VisibleForTesting static final int BTSTACK_LOG_MODE_INFO_INDEX = 2;
-    @VisibleForTesting static final int BTSTACK_LOG_MODE_WARN_INDEX = 3;
-    @VisibleForTesting static final int BTSTACK_LOG_MODE_ERROR_INDEX = 4;
+    private static final int BT_LOG_LEVEL_VERBOSE_INDEX = 0;
+    private static final int BT_LOG_LEVEL_DEBUG_INDEX = 1;
+    private static final int BT_LOG_LEVEL_INFO_INDEX = 2;
+    private static final int BT_LOG_LEVEL_WARN_INDEX = 3;
+    private static final int BT_LOG_LEVEL_ERROR_INDEX = 4;
+    @VisibleForTesting static final int BT_LOG_LEVEL_DEFAULT_INDEX = BT_LOG_LEVEL_INFO_INDEX;
 
-    @VisibleForTesting
-    static final String BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY_PERSIST = "persist.log.tag.bluetooth";
-    static final String BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY = "log.tag.bluetooth";
-    static final String BLUETOOTH_STRING_NAME = "bluetooth";
-    static final int DEFAULT_MODE = BTSTACK_LOG_MODE_INFO_INDEX;
+    private static final String BT_LOG_TAG = "bluetooth";
+    @VisibleForTesting static final String BT_LOG_LEVEL_PROP_PERSIST = "persist.log.tag.bluetooth";
+    @VisibleForTesting static final String BT_LOG_LEVEL_PROP = "log.tag.bluetooth";
 
-    private final String[] mListValues;
-    private final String[] mListEntries;
+    // Values represents the untranslatable log level strings that should be used for writing to
+    // system properties. Entries represents the translatable log level strings that should be used
+    // in the UI to communicate to the user their options for this preference.
+    private String[] mListValues;
+    private String[] mListEntries;
 
-
+    /**
+     * Create a BluetoothStackLogPreferenceController instance
+     */
     public BluetoothStackLogPreferenceController(@NonNull Context context) {
         super(context);
         mListValues = context.getResources().getStringArray(R.array.bt_stack_log_level_values);
         mListEntries = context.getResources().getStringArray(R.array.bt_stack_log_level_entries);
     }
 
-    /** returns default log level index of INFO */
-    public int getDefaultModeIndex() {
-        return DEFAULT_MODE;
-    }
-
+    /**
+     * Returns the preference key associated with this preference
+     *
+     * Note that this key is _usually_ a system property in and of itself, which is expected to hold
+     * the value of the preference. In this case though, this key *does not* hold the preference. It
+     * is only really used to tie this controller to the list preference defined in the XML file.
+     *
+     * @return the preference key associated with this preference
+     */
     @Override
     @Nullable
     public String getPreferenceKey() {
         return PREFERENCE_KEY;
     }
 
+    /**
+     * Update the state of the preference based on what the user has selected
+     *
+     * This function is invoked when the user has selected a new value for this preference. The new
+     * value is the entry value at the index of the list the user has selected. This value will be
+     * one of the values from the array returned in getEntryValues(). Specifically, this array is
+     * set using R.array.bt_stack_log_level_values
+     *
+     * @param preference - the preference object to set the value of
+     * @param newValue - the value the user has selected, as an Object
+     * @return True when updated successfully
+     */
     @Override
     public boolean onPreferenceChange(@NonNull Preference preference, @NonNull Object newValue) {
-        SystemProperties.set(BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY_PERSIST, newValue.toString());
-        SystemProperties.set(BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY, newValue.toString());
-        updateState(mPreference);
+        Log.v(TAG, "onPreferenceChange(pref=" + preference + "value=" + newValue.toString() + ")");
+        setBluetoothLogTag(newValue.toString());
+        setBluetoothLogLevelIndex(getBluetoothLogLevelIndex());
         return true;
     }
 
+    /**
+     * Refresh the state of this preference based on the state stored on the system
+     *
+     * Read the Bluetooth stack log level from the underlying system property/log tag, and map that
+     * level to the proper index in the values and entries array. Use those strings to set the value
+     * and summary of the preference.
+     *
+     * @param preference - the preference object to refresh the state of
+     */
     @Override
     public void updateState(@NonNull Preference preference) {
-        final ListPreference listPreference = (ListPreference) preference;
-        int index = getBluetoothLogLevelIndex();
-        listPreference.setValue(mListValues[index]);
-        listPreference.setSummary(mListEntries[index]);
+        Log.v(TAG, "updateState(pref=" + preference + "): refresh preference state");
+        setBluetoothLogLevelIndex(getBluetoothLogLevelIndex());
     }
 
     /**
-     *  Returns the current log level from Log.isLoggable().
+     * Notify this developer options preference of a change to developer options visibility
+     *
+     * We developer options are closed, we should clear out the value of this developer option
+     * preference and revert it back to the default state of INFO.
      */
-    @VisibleForTesting
-    public int getBluetoothLogLevelIndex() {
-        if (Log.isLoggable(BLUETOOTH_STRING_NAME, Log.VERBOSE)) {
-            return BTSTACK_LOG_MODE_VERBOSE_INDEX;
-        } else if (Log.isLoggable(BLUETOOTH_STRING_NAME, Log.DEBUG)) {
-            return BTSTACK_LOG_MODE_DEBUG_INDEX;
-        } else if (Log.isLoggable(BLUETOOTH_STRING_NAME, Log.INFO)) {
-            return BTSTACK_LOG_MODE_INFO_INDEX;
-        } else if (Log.isLoggable(BLUETOOTH_STRING_NAME, Log.WARN)) {
-            return BTSTACK_LOG_MODE_WARN_INDEX;
-        } else if (Log.isLoggable(BLUETOOTH_STRING_NAME, Log.ERROR)) {
-            return BTSTACK_LOG_MODE_ERROR_INDEX;
-        }
-        return BTSTACK_LOG_MODE_INFO_INDEX;
-    }
-
     @Override
     protected void onDeveloperOptionsSwitchDisabled() {
         super.onDeveloperOptionsSwitchDisabled();
-        SystemProperties.set(BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY_PERSIST, null);
-        SystemProperties.set(BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY, null);
-        ((ListPreference) mPreference).setValue(mListValues[getDefaultModeIndex()]);
-        ((ListPreference) mPreference).setSummary(mListEntries[getDefaultModeIndex()]);
+        Log.v(TAG, "onDeveloperOptionsSwitchDisabled(): Revert stack log to default");
+        setBluetoothLogTag(null);
+        setBluetoothLogLevelIndex(BT_LOG_LEVEL_DEFAULT_INDEX);
+    }
+
+    /**
+     * Set the system property values used by the Log framework to read the "bluetooth" log tag
+     *
+     * @param logLevel - the log level to set the Bluetooth stack minimum log level to
+     */
+    private void setBluetoothLogTag(@Nullable String logLevel) {
+        Log.i(TAG, "setBluetoothLogTag(logLevel=" + logLevel + "): Set properties for log tag");
+        SystemProperties.set(BT_LOG_LEVEL_PROP_PERSIST, logLevel);
+        SystemProperties.set(BT_LOG_LEVEL_PROP, logLevel);
+    }
+
+    /**
+     * Get the entry and value index corresponding to the current Bluetooth stack log level
+     *
+     * Since this preference uses an actual log tag and not a specific/private system property, we
+     * can read the value using the Log.isLoggable() function with our "bluetooth" log tag that
+     * represents the log level of the Bluetooth stack. This is safer than trying to replacate the
+     * logic used in the Log framework around the various persist, ro, and blank variants of the tag
+     *
+     * If no value is present, INFO is used.
+     *
+     * @return the entry/value index corresponding to the current log level of the tag "bluetooth"
+     */
+    @VisibleForTesting
+    public int getBluetoothLogLevelIndex() {
+        int level = BT_LOG_LEVEL_DEFAULT_INDEX;
+        if (Log.isLoggable(BT_LOG_TAG, Log.VERBOSE)) {
+            level = BT_LOG_LEVEL_VERBOSE_INDEX;
+        } else if (Log.isLoggable(BT_LOG_TAG, Log.DEBUG)) {
+            level = BT_LOG_LEVEL_DEBUG_INDEX;
+        } else if (Log.isLoggable(BT_LOG_TAG, Log.INFO)) {
+            level = BT_LOG_LEVEL_INFO_INDEX;
+        } else if (Log.isLoggable(BT_LOG_TAG, Log.WARN)) {
+            level = BT_LOG_LEVEL_WARN_INDEX;
+        } else if (Log.isLoggable(BT_LOG_TAG, Log.ERROR)) {
+            level = BT_LOG_LEVEL_ERROR_INDEX;
+        }
+        Log.v(TAG, "getBluetoothLogLevelIndex() -> " + level);
+        return level;
+    }
+
+    /**
+     * Set the current Bluetooth stack log level displayed in the list for this preference
+     *
+     * @param index - the index representing the log level choice of this preference
+     */
+    private void setBluetoothLogLevelIndex(int index) {
+        if (index < BT_LOG_LEVEL_VERBOSE_INDEX || index > BT_LOG_LEVEL_ERROR_INDEX) {
+            Log.e(TAG, "setBluetoothLogLevelIndex(index=" + index + "): Log level invalid");
+            return;
+        }
+
+        String value = mListValues[index];
+        String entryValue = mListEntries[index];
+
+        ListPreference preference = ((ListPreference) mPreference);
+        if (preference == null) {
+            Log.e(TAG, "setBluetoothLogLevelIndex(index=" + index + "): mPreference is null");
+            return;
+        }
+
+        preference.setValue(value);
+        preference.setSummary(entryValue);
+
+        Log.i(TAG, "setBluetoothLogLevelIndex(index=" + index
+                + "): Updated Bluetooth stack log level to value='" + value + "', entryValue='"
+                + entryValue + "'");
     }
 }
diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
index 7d15858..f121d0c 100644
--- a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
@@ -81,7 +81,10 @@
             return mContext.getString(
                     com.android.settingslib.R.string.battery_info_status_not_charging);
         }
-        if (BatteryUtils.isBatteryDefenderOn(info)) {
+        if (BatteryUtils.isBatteryDefenderOn(info)
+                || FeatureFactory.getFeatureFactory()
+                .getPowerUsageFeatureProvider()
+                .isExtraDefend()) {
             return mContext.getString(
                     com.android.settingslib.R.string.battery_info_status_charging_on_hold);
         }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java
index bfa501c..f710c71 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java
@@ -63,8 +63,8 @@
         }
         final String action = intent.getAction();
         Log.d(TAG, "onReceive:" + action);
-        if (com.android.settingslib.fuelgauge.BatteryUtils.isWorkProfile(context)) {
-            Log.w(TAG, "do nothing for work profile action=" + action);
+        if (com.android.settingslib.fuelgauge.BatteryUtils.isAdditionalProfile(context)) {
+            Log.w(TAG, "do nothing for an additional profile action=" + action);
             return;
         }
         DatabaseUtils.recordDateTime(context, action);
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java
index 095a65a..52010af 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java
@@ -110,8 +110,8 @@
 
     @Override
     public boolean onCreate() {
-        if (BatteryUtils.isWorkProfile(getContext())) {
-            Log.w(TAG, "do not create provider for work profile");
+        if (BatteryUtils.isAdditionalProfile(getContext())) {
+            Log.w(TAG, "do not create provider for an additional profile");
             return false;
         }
         mClock = Clock.systemUTC();
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
index b758df4..45d724f 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
@@ -54,8 +54,8 @@
     @Override
     public void onReceive(Context context, Intent intent) {
         final String action = intent == null ? "" : intent.getAction();
-        if (BatteryUtils.isWorkProfile(context)) {
-            Log.w(TAG, "do not start job for work profile action=" + action);
+        if (BatteryUtils.isAdditionalProfile(context)) {
+            Log.w(TAG, "do not start job for an additional profile action=" + action);
             return;
         }
 
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
index 7620323..6feb815 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
@@ -70,6 +70,7 @@
 
     /** Clear memory threshold for device booting phase. */
     private static final long CLEAR_MEMORY_THRESHOLD_MS = Duration.ofMinutes(5).toMillis();
+
     private static final long CLEAR_MEMORY_DELAYED_MS = Duration.ofSeconds(2).toMillis();
     private static final long INVALID_TIMESTAMP = 0L;
 
@@ -527,9 +528,11 @@
         return startCalendar.getTimeInMillis();
     }
 
-    /** Returns the context with profile parent identity when current user is work profile. */
+    /**
+     * Returns the context with profile parent identity when current user is an additional profile.
+     */
     public static Context getParentContext(Context context) {
-        if (com.android.settingslib.fuelgauge.BatteryUtils.isWorkProfile(context)) {
+        if (com.android.settingslib.fuelgauge.BatteryUtils.isAdditionalProfile(context)) {
             try {
                 return context.createPackageContextAsUser(
                         /* packageName= */ context.getPackageName(),
diff --git a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java
index 5c73adb..982cf40 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java
@@ -50,10 +50,10 @@
             Log.w(TAG, "receive unexpected action=" + action);
             return;
         }
-        if (BatteryUtils.isWorkProfile(context)) {
+        if (BatteryUtils.isAdditionalProfile(context)) {
             BatteryUsageLogUtils.writeLog(
-                    context, Action.SCHEDULE_JOB, "do not refresh job for work profile");
-            Log.w(TAG, "do not refresh job for work profile action=" + action);
+                    context, Action.SCHEDULE_JOB, "do not refresh job for an additional profile");
+            Log.w(TAG, "do not refresh job for an additional profile action=" + action);
             return;
         }
         BatteryUsageLogUtils.writeLog(context, Action.EXECUTE_JOB, "");
diff --git a/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProvider.java b/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProvider.java
index 7e759ee..e829a3c 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProvider.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProvider.java
@@ -49,8 +49,8 @@
             Log.w(TAG, "failed to dump BatteryUsage state: null application context");
             return;
         }
-        if (BatteryUtils.isWorkProfile(context)) {
-            Log.w(TAG, "ignore battery usage states dump in the work profile");
+        if (BatteryUtils.isAdditionalProfile(context)) {
+            Log.w(TAG, "ignore battery usage states dump in the additional profile");
             return;
         }
         writer.println("dump BatteryUsage and AppUsage states:");
diff --git a/src/com/android/settings/notification/modes/CircularIconSet.java b/src/com/android/settings/notification/modes/CircularIconSet.java
new file mode 100644
index 0000000..eb7289a
--- /dev/null
+++ b/src/com/android/settings/notification/modes/CircularIconSet.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 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.modes;
+
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.google.common.base.Equivalence;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Function;
+
+/**
+ * A set of icons to be displayed in a {@link CircularIconsPreference}
+ *
+ * @param <T> The type of the items in the set. Can be an arbitrary type, the only requirement
+ *           being that the {@code drawableLoader} supplied to the constructor is able to produce
+ *           a {@link Drawable} from it (for example a resource id, a Content Uri, etc).
+ */
+class CircularIconSet<T> {
+
+    @VisibleForTesting // Can be set by tests, before creating instances.
+    static ExecutorService sExecutorService = Executors.newCachedThreadPool();
+
+    static final CircularIconSet<?> EMPTY = new CircularIconSet<>(ImmutableList.of(),
+            unused -> new ColorDrawable(Color.BLACK));
+
+    private final ImmutableList<T> mItems;
+    private final Function<T, Drawable> mDrawableLoader;
+    private final ListeningExecutorService mBackgroundExecutor;
+
+    private final ConcurrentHashMap<T, Drawable> mCachedIcons;
+
+    CircularIconSet(List<T> items, Function<T, Drawable> drawableLoader) {
+        mItems = ImmutableList.copyOf(items);
+        mDrawableLoader = drawableLoader;
+        mBackgroundExecutor = MoreExecutors.listeningDecorator(sExecutorService);
+        mCachedIcons = new ConcurrentHashMap<>();
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this).add("items", mItems).toString();
+    }
+
+    @SuppressWarnings("unchecked")
+    <OtherT> boolean hasSameItemsAs(CircularIconSet<OtherT> other,
+            @Nullable Equivalence<OtherT> equivalence) {
+        if (other == null) {
+            return false;
+        }
+        if (other == this) {
+            return true;
+        }
+        if (equivalence == null) {
+            return mItems.equals(other.mItems);
+        }
+        // Check that types match before applying equivalence (statically unsafe). :(
+        Optional<Class<?>> thisItemClass = this.mItems.stream().findFirst().map(T::getClass);
+        Optional<Class<?>> otherItemClass = other.mItems.stream().findFirst().map(OtherT::getClass);
+        if (!thisItemClass.equals(otherItemClass)) {
+            return false;
+        }
+        return equivalence.pairwise().equivalent((Iterable<OtherT>) this.mItems, other.mItems);
+    }
+
+    int size() {
+        return mItems.size();
+    }
+
+    /**
+     * Loads all icons from the set, using the supplied {@code drawableLoader}, in a background
+     * thread.
+     */
+    List<ListenableFuture<Drawable>> getIcons() {
+        return getIcons(Integer.MAX_VALUE);
+    }
+
+    /**
+     * Loads up to {@code maxSize} icons from the set, using the supplied {@code drawableLoader}, in
+     * a background thread.
+     */
+    List<ListenableFuture<Drawable>> getIcons(int maxNumber) {
+        return mItems.stream().limit(maxNumber)
+                .map(this::loadIcon)
+                .toList();
+    }
+
+    private ListenableFuture<Drawable> loadIcon(T item) {
+        return mBackgroundExecutor.submit(() -> {
+            if (mCachedIcons.containsKey(item)) {
+                return mCachedIcons.get(item);
+            }
+            Drawable drawable = mDrawableLoader.apply(item);
+            if (drawable != null) {
+                mCachedIcons.put(item, drawable);
+            }
+            return drawable;
+        });
+    }
+}
diff --git a/src/com/android/settings/notification/modes/CircularIconsPreference.java b/src/com/android/settings/notification/modes/CircularIconsPreference.java
new file mode 100644
index 0000000..1ce3476
--- /dev/null
+++ b/src/com/android/settings/notification/modes/CircularIconsPreference.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2024 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.modes;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+import com.android.settingslib.RestrictedPreference;
+import com.android.settingslib.Utils;
+
+import com.google.common.base.Equivalence;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class CircularIconsPreference extends RestrictedPreference {
+
+    private Executor mUiExecutor;
+    @Nullable private LinearLayout mIconContainer;
+
+    @Nullable private CircularIconSet<?> mIconSet;
+    @Nullable private CircularIconSet<?> mPendingDisplayIconSet;
+    @Nullable private ListenableFuture<List<Drawable>> mPendingLoadIconsFuture;
+
+    public CircularIconsPreference(Context context) {
+        super(context);
+        init(context);
+    }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+    public CircularIconsPreference(Context context, Executor uiExecutor) {
+        this(context);
+        mUiExecutor = uiExecutor;
+    }
+
+    public CircularIconsPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context);
+    }
+
+    public CircularIconsPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(context);
+    }
+
+    public CircularIconsPreference(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init(context);
+    }
+
+    private void init(Context context) {
+        mUiExecutor = context.getMainExecutor();
+        setLayoutResource(R.layout.preference_circular_icons);
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+
+        mIconContainer = checkNotNull((LinearLayout) holder.findViewById(R.id.circles_container));
+        displayIconsIfPending();
+    }
+
+    private void displayIconsIfPending() {
+        CircularIconSet<?> pendingIconSet = mPendingDisplayIconSet;
+        if (pendingIconSet != null) {
+            mPendingDisplayIconSet = null;
+            displayIconsInternal(pendingIconSet);
+        }
+    }
+
+    <T> void displayIcons(CircularIconSet<T> iconSet) {
+        displayIcons(iconSet, null);
+    }
+
+    <T> void displayIcons(CircularIconSet<T> iconSet, @Nullable Equivalence<T> itemEquivalence) {
+        if (mIconSet != null && mIconSet.hasSameItemsAs(iconSet, itemEquivalence)) {
+            return;
+        }
+        mIconSet = iconSet;
+        displayIconsInternal(iconSet);
+    }
+
+    void displayIconsInternal(CircularIconSet<?> iconSet) {
+        if (mIconContainer == null) {
+            // Too soon, wait for bind.
+            mPendingDisplayIconSet = iconSet;
+            return;
+        }
+        mIconContainer.setVisibility(iconSet.size() != 0 ? View.VISIBLE : View.GONE);
+        if (iconSet.size() == 0) {
+            return;
+        }
+        if (mIconContainer.getMeasuredWidth() == 0) {
+            // Too soon, wait for first measure to know width.
+            mPendingDisplayIconSet = iconSet;
+            mIconContainer.getViewTreeObserver().addOnGlobalLayoutListener(
+                    new ViewTreeObserver.OnGlobalLayoutListener() {
+                        @Override
+                        public void onGlobalLayout() {
+                            checkNotNull(mIconContainer).getViewTreeObserver()
+                                    .removeOnGlobalLayoutListener(this);
+                            displayIconsIfPending();
+                        }
+                    }
+            );
+            return;
+        }
+
+        mIconContainer.setVisibility(View.VISIBLE);
+        Resources res = getContext().getResources();
+        int availableSpace = mIconContainer.getMeasuredWidth();
+        int iconHorizontalSpace = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter)
+                + res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_margin_between);
+        int numIconsThatFit = availableSpace / iconHorizontalSpace;
+
+        List<ListenableFuture<Drawable>> iconFutures;
+        int extraItems;
+        if (iconSet.size() > numIconsThatFit) {
+            // Reserve one space for the (+xx) textview.
+            int numIconsToShow = numIconsThatFit - 1;
+            if (numIconsToShow < 0) {
+                numIconsToShow = 0;
+            }
+            iconFutures = iconSet.getIcons(numIconsToShow);
+            extraItems = iconSet.size() - numIconsToShow;
+        } else {
+            // Fit exactly or with remaining space.
+            iconFutures = iconSet.getIcons();
+            extraItems = 0;
+        }
+
+        displayIconsWhenReady(iconFutures, extraItems);
+    }
+
+    private void displayIconsWhenReady(List<ListenableFuture<Drawable>> iconFutures,
+            int extraItems) {
+        checkState(mIconContainer != null);
+        if (mPendingLoadIconsFuture != null) {
+            mPendingLoadIconsFuture.cancel(true);
+        }
+
+        // Rearrange child views until we have <numImages> ImageViews...
+        LayoutInflater inflater = LayoutInflater.from(getContext());
+        int numImages = iconFutures.size();
+        int numImageViews = getChildCount(mIconContainer, ImageView.class);
+        if (numImages > numImageViews) {
+            for (int i = 0; i < numImages - numImageViews; i++) {
+                ImageView imageView = (ImageView) inflater.inflate(
+                        R.layout.preference_circular_icons_item, mIconContainer, false);
+                mIconContainer.addView(imageView, 0);
+            }
+        } else if (numImageViews > numImages) {
+            for (int i = 0; i < numImageViews - numImages; i++) {
+                mIconContainer.removeViewAt(0);
+            }
+        }
+        // ... plus 0/1 TextViews at the end.
+        if (extraItems > 0 && !(getLastChild(mIconContainer) instanceof TextView)) {
+            TextView plusView = (TextView) inflater.inflate(
+                    R.layout.preference_circular_icons_plus_item, mIconContainer, false);
+            mIconContainer.addView(plusView);
+        } else if (extraItems == 0 && (getLastChild(mIconContainer) instanceof TextView)) {
+            mIconContainer.removeViewAt(mIconContainer.getChildCount() - 1);
+        }
+
+        // Set up placeholders and extra items indicator.
+        for (int i = 0; i < numImages; i++) {
+            ImageView imageView = (ImageView) mIconContainer.getChildAt(i);
+            imageView.setImageDrawable(getPlaceholderImage(getContext()));
+        }
+        if (extraItems > 0) {
+            TextView textView = (TextView) checkNotNull(getLastChild(mIconContainer));
+            textView.setText(getContext().getString(R.string.zen_mode_plus_n_items, extraItems));
+        }
+
+        // Display icons when all are ready (more consistent than randomly loading).
+        mPendingLoadIconsFuture = Futures.allAsList(iconFutures);
+        FutureUtil.whenDone(
+                mPendingLoadIconsFuture,
+                icons -> {
+                    checkState(mIconContainer != null);
+                    for (int i = 0; i < icons.size(); i++) {
+                        ((ImageView) mIconContainer.getChildAt(i)).setImageDrawable(icons.get(i));
+                    }
+                },
+                mUiExecutor);
+    }
+
+    private static Drawable getPlaceholderImage(Context context) {
+        ShapeDrawable placeholder = new ShapeDrawable(new OvalShape());
+        placeholder.setTintList(Utils.getColorAttr(context,
+                com.android.internal.R.attr.materialColorSecondaryContainer));
+        return placeholder;
+    }
+
+    private static int getChildCount(ViewGroup parent, Class<? extends View> childClass) {
+        int count = 0;
+        for (int i = 0; i < parent.getChildCount(); i++) {
+            if (childClass.isInstance(parent.getChildAt(i))) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    @Nullable
+    private static View getLastChild(ViewGroup parent) {
+        if (parent.getChildCount() == 0) {
+            return null;
+        }
+        return parent.getChildAt(parent.getChildCount() - 1);
+    }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+    List<Drawable> getIcons() {
+        if (mIconContainer == null) {
+            return List.of();
+        }
+        ArrayList<Drawable> drawables = new ArrayList<>();
+        for (int i = 0; i < getChildCount(mIconContainer, ImageView.class); i++) {
+            drawables.add(((ImageView) mIconContainer.getChildAt(i)).getDrawable());
+        }
+        return drawables;
+    }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+    @Nullable
+    String getPlusText() {
+        if (mIconContainer == null) {
+            return null;
+        }
+        View lastChild = getLastChild(mIconContainer);
+        if (lastChild instanceof TextView tv) {
+            return tv.getText() != null ? tv.getText().toString() : null;
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/src/com/android/settings/notification/modes/FutureUtil.java b/src/com/android/settings/notification/modes/FutureUtil.java
index e7bf8b9..b9a4300 100644
--- a/src/com/android/settings/notification/modes/FutureUtil.java
+++ b/src/com/android/settings/notification/modes/FutureUtil.java
@@ -18,10 +18,13 @@
 
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 
+import java.util.concurrent.CancellationException;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
@@ -42,8 +45,10 @@
             }
 
             @Override
-            public void onFailure(Throwable throwable) {
-                Log.e(TAG, String.format(errorLogMessage, errorLogMessageArgs), throwable);
+            public void onFailure(@NonNull Throwable throwable) {
+                if (!(throwable instanceof CancellationException)) {
+                    Log.e(TAG, String.format(errorLogMessage, errorLogMessageArgs), throwable);
+                }
             }
         }, executor);
     }
diff --git a/src/com/android/settings/notification/modes/IconUtil.java b/src/com/android/settings/notification/modes/IconUtil.java
index d07abf3..e19da40 100644
--- a/src/com/android/settings/notification/modes/IconUtil.java
+++ b/src/com/android/settings/notification/modes/IconUtil.java
@@ -20,6 +20,13 @@
 
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.ShapeDrawable;
@@ -29,11 +36,16 @@
 import androidx.annotation.AttrRes;
 import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.Px;
 
 import com.android.settings.R;
 import com.android.settingslib.Utils;
 
+import com.google.common.base.Strings;
+
+import java.util.Locale;
+
 class IconUtil {
 
     static Drawable applyNormalTint(@NonNull Context context, @NonNull Drawable icon) {
@@ -55,7 +67,7 @@
      * Returns a variant of the supplied {@code icon} to be used as the header in the icon picker.
      * The inner icon is 48x48dp and it's contained into a circle of diameter 90dp.
      */
-    static Drawable makeBigIconCircle(@NonNull Context context, Drawable icon) {
+    static Drawable makeIconPickerHeader(@NonNull Context context, Drawable icon) {
         return composeIconCircle(
                 Utils.getColorAttr(context,
                         com.android.internal.R.attr.materialColorSecondaryContainer),
@@ -73,7 +85,7 @@
      * The inner icon is 36x36dp and it's contained into a circle of diameter 54dp. It's also set up
      * so that selection and pressed states are represented in the color.
      */
-    static Drawable makeSmallIconCircle(@NonNull Context context, @DrawableRes int iconResId) {
+    static Drawable makeIconPickerItem(@NonNull Context context, @DrawableRes int iconResId) {
         return composeIconCircle(
                 context.getColorStateList(R.color.modes_icon_picker_item_background),
                 context.getResources().getDimensionPixelSize(
@@ -84,6 +96,72 @@
                         R.dimen.zen_mode_icon_list_item_icon_size));
     }
 
+    /**
+     * Returns a variant of the supplied icon to be used in a {@link CircularIconsPreference}. The
+     * inner icon is 20x20 dp and it's contained in a circle of diameter 32dp, and is tinted
+     * with the "material secondary" color combination.
+     */
+    static Drawable makeCircularIconPreferenceItem(@NonNull Context context,
+            @DrawableRes int iconResId) {
+        return composeIconCircle(
+                Utils.getColorAttr(context,
+                        com.android.internal.R.attr.materialColorSecondaryContainer),
+                context.getResources().getDimensionPixelSize(
+                        R.dimen.zen_mode_circular_icon_diameter),
+                checkNotNull(context.getDrawable(iconResId)),
+                Utils.getColorAttr(context,
+                        com.android.internal.R.attr.materialColorOnSecondaryContainer),
+                context.getResources().getDimensionPixelSize(
+                        R.dimen.zen_mode_circular_icon_inner_icon_size));
+    }
+
+    /**
+     * Returns an icon representing a contact that doesn't have an associated photo, to be used in
+     * a {@link CircularIconsPreference}, tinted with the "material tertiary". If the contact's
+     * display name is not empty, it's the contact's monogram, otherwise it's a generic icon.
+     */
+    static Drawable makeContactMonogram(@NonNull Context context, @Nullable String displayName) {
+        Resources res = context.getResources();
+        if (Strings.isNullOrEmpty(displayName)) {
+            return composeIconCircle(
+                    Utils.getColorAttr(context,
+                            com.android.internal.R.attr.materialColorTertiaryContainer),
+                    res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter),
+                    checkNotNull(context.getDrawable(R.drawable.ic_zen_mode_generic_contact)),
+                    Utils.getColorAttr(context,
+                            com.android.internal.R.attr.materialColorOnTertiaryContainer),
+                    res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_inner_icon_size));
+        }
+
+        float diameter = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter);
+        Bitmap bitmap = Bitmap.createBitmap((int) diameter, (int) diameter,
+                Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+
+        Paint circlePaint = new Paint();
+        circlePaint.setColor(Utils.getColorAttrDefaultColor(context,
+                com.android.internal.R.attr.materialColorTertiaryContainer));
+        circlePaint.setFlags(Paint.ANTI_ALIAS_FLAG);
+        canvas.drawCircle(diameter / 2f, diameter / 2f, diameter / 2f, circlePaint);
+
+        Paint textPaint = new Paint();
+        textPaint.setColor(Utils.getColorAttrDefaultColor(context,
+                com.android.internal.R.attr.materialColorOnTertiaryContainer));
+        textPaint.setTypeface(Typeface.create("sans-serif", Typeface.NORMAL));
+        textPaint.setTextAlign(Paint.Align.LEFT);
+        textPaint.setTextSize(res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_text_size));
+
+        String text = displayName.substring(0, 1).toUpperCase(Locale.getDefault());
+        Rect textRect = new Rect();
+        textPaint.getTextBounds(text, 0, text.length(), textRect);
+
+        float textX = diameter / 2f - textRect.width() / 2f - textRect.left;
+        float textY = diameter / 2f + textRect.height() / 2f - textRect.bottom;
+        canvas.drawText(text, textX, textY, textPaint);
+
+        return new BitmapDrawable(context.getResources(), bitmap);
+    }
+
     private static Drawable composeIconCircle(ColorStateList circleColor, @Px int circleDiameterPx,
             Drawable icon, ColorStateList iconColor, @Px int iconSizePx) {
         ShapeDrawable background = new ShapeDrawable(new OvalShape());
@@ -93,11 +171,11 @@
 
         LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { background, foreground });
 
-        layerDrawable.setBounds(0, 0, circleDiameterPx, circleDiameterPx);
         layerDrawable.setLayerSize(0, circleDiameterPx, circleDiameterPx);
         layerDrawable.setLayerGravity(1, Gravity.CENTER);
         layerDrawable.setLayerSize(1, iconSizePx, iconSizePx);
 
+        layerDrawable.setBounds(0, 0, circleDiameterPx, circleDiameterPx);
         return layerDrawable;
     }
 }
diff --git a/src/com/android/settings/notification/modes/ZenHelperBackend.java b/src/com/android/settings/notification/modes/ZenHelperBackend.java
index 4136c22..a2c3578 100644
--- a/src/com/android/settings/notification/modes/ZenHelperBackend.java
+++ b/src/com/android/settings/notification/modes/ZenHelperBackend.java
@@ -21,15 +21,22 @@
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.ServiceManager;
 import android.provider.ContactsContract;
 import android.service.notification.ConversationChannelWrapper;
 import android.util.Log;
 
-import androidx.annotation.VisibleForTesting;
+import androidx.annotation.NonNull;
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
 
-import com.android.settings.R;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -75,45 +82,99 @@
     }
 
     @SuppressWarnings("unchecked")
-    ParceledListSlice<ConversationChannelWrapper> getConversations(boolean onlyImportant) {
+    ImmutableList<ConversationChannelWrapper> getImportantConversations() {
         try {
-            return mInm.getConversations(onlyImportant);
+            ImmutableList.Builder<ConversationChannelWrapper> list = new ImmutableList.Builder<>();
+            ParceledListSlice<ConversationChannelWrapper> parceledList = mInm.getConversations(
+                    /* onlyImportant= */ true);
+            if (parceledList != null) {
+                for (ConversationChannelWrapper conversation : parceledList.getList()) {
+                    if (!conversation.getNotificationChannel().isDemoted()) {
+                        list.add(conversation);
+                    }
+                }
+            }
+            return list.build();
         } catch (Exception e) {
             Log.w(TAG, "Error calling NoMan", e);
-            return ParceledListSlice.emptyList();
+            return ImmutableList.of();
         }
     }
 
-    List<String> getStarredContacts() {
+    record Contact(long id, @Nullable String displayName, @Nullable Uri photoUri) { }
+
+    ImmutableList<Contact> getAllContacts() {
+        try (Cursor cursor = queryAllContactsData()) {
+            return getContactsFromCursor(cursor);
+        }
+    }
+
+    ImmutableList<Contact> getStarredContacts() {
         try (Cursor cursor = queryStarredContactsData()) {
-            return getStarredContacts(cursor);
+            return getContactsFromCursor(cursor);
         }
     }
 
-    @VisibleForTesting
-    List<String> getStarredContacts(Cursor cursor) {
-        List<String> starredContacts = new ArrayList<>();
+    private ImmutableList<Contact> getContactsFromCursor(Cursor cursor) {
+        ImmutableList.Builder<Contact> list = new ImmutableList.Builder<>();
         if (cursor != null && cursor.moveToFirst()) {
             do {
-                String contact = cursor.getString(0);
-                starredContacts.add(contact != null ? contact :
-                        mContext.getString(R.string.zen_mode_starred_contacts_empty_name));
-
+                long id = cursor.getLong(0);
+                String name = Strings.emptyToNull(cursor.getString(1));
+                String photoUriStr = cursor.getString(2);
+                Uri photoUri = !Strings.isNullOrEmpty(photoUriStr) ? Uri.parse(photoUriStr) : null;
+                list.add(new Contact(id, name, photoUri));
             } while (cursor.moveToNext());
         }
-        return starredContacts;
+        return list.build();
     }
 
+    int getAllContactsCount() {
+        try (Cursor cursor = queryAllContactsData()) {
+            return cursor != null ? cursor.getCount() : 0;
+        }
+    }
+
+    private static final String[] CONTACTS_PROJECTION = new String[] {
+            ContactsContract.Contacts._ID,
+            ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
+            ContactsContract.Contacts.PHOTO_THUMBNAIL_URI
+    };
+
     private Cursor queryStarredContactsData() {
-        return mContext.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,
-                new String[]{ContactsContract.Contacts.DISPLAY_NAME_PRIMARY},
-                ContactsContract.Data.STARRED + "=1", null,
-                ContactsContract.Data.TIMES_CONTACTED);
+        return mContext.getContentResolver().query(
+                ContactsContract.Contacts.CONTENT_URI,
+                CONTACTS_PROJECTION,
+                /* selection= */ ContactsContract.Data.STARRED + "=1", /* selectionArgs= */ null,
+                /* sortOrder= */ ContactsContract.Contacts.DISPLAY_NAME_PRIMARY);
     }
 
-    Cursor queryAllContactsData() {
-        return mContext.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,
-                new String[]{ContactsContract.Contacts.DISPLAY_NAME_PRIMARY},
-                null, null, null);
+    private Cursor queryAllContactsData() {
+        return mContext.getContentResolver().query(
+                ContactsContract.Contacts.CONTENT_URI,
+                CONTACTS_PROJECTION,
+                /* selection= */ null, /* selectionArgs= */ null,
+                /* sortOrder= */ ContactsContract.Contacts.DISPLAY_NAME_PRIMARY);
+    }
+
+    @NonNull
+    Drawable getContactPhoto(Contact contact) {
+        if (contact.photoUri != null) {
+            try (InputStream is = mContext.getContentResolver().openInputStream(contact.photoUri)) {
+                if (is != null) {
+                    RoundedBitmapDrawable rbd = RoundedBitmapDrawableFactory.create(
+                            mContext.getResources(), is);
+                    if (rbd != null && rbd.getBitmap() != null) {
+                        rbd.setCircular(true);
+                        return rbd;
+                    }
+                }
+            } catch (IOException e) {
+                Log.w(TAG, "Couldn't load photo for " + contact, e);
+            }
+        }
+
+        // Fall back to a monogram if no picture.
+        return IconUtil.makeContactMonogram(mContext, contact.displayName);
     }
 }
diff --git a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
index 9c3f267..962e016 100644
--- a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
@@ -24,6 +24,7 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.service.notification.ZenPolicy;
 import android.text.TextUtils;
 
 import androidx.annotation.NonNull;
@@ -32,12 +33,14 @@
 import androidx.preference.Preference;
 
 import com.android.settings.R;
+import com.android.settings.Utils;
 import com.android.settings.core.SubSettingLauncher;
 import com.android.settingslib.applications.ApplicationsState;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
 import com.android.settingslib.notification.modes.ZenMode;
 import com.android.settingslib.notification.modes.ZenModesBackend;
 
+import com.google.common.base.Equivalence;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Multimap;
@@ -45,6 +48,7 @@
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Preference with a link and summary about what apps can break through the mode
@@ -59,7 +63,7 @@
     private ApplicationsState.Session mAppSession;
     private final ZenHelperBackend mHelperBackend;
     private ZenMode mZenMode;
-    private Preference mPreference;
+    private CircularIconsPreference mPreference;
     private final Fragment mHost;
 
     ZenModeAppsLinkPreferenceController(Context context, String key, Fragment host,
@@ -97,14 +101,21 @@
                 .setArguments(bundle)
                 .toIntent());
         mZenMode = zenMode;
-        mPreference = preference;
-        if (TextUtils.isEmpty(mPreference.getSummary())) {
-            mPreference.setSummary(R.string.zen_mode_apps_calculating);
+        mPreference = (CircularIconsPreference) preference;
+
+        if (zenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_NONE) {
+            mPreference.setSummary(R.string.zen_mode_apps_none_apps);
+            mPreference.displayIcons(CircularIconSet.EMPTY);
+        } else {
+            if (TextUtils.isEmpty(mPreference.getSummary())) {
+                mPreference.setSummary(R.string.zen_mode_apps_calculating);
+            }
+            if (mApplicationsState != null && mHost != null) {
+                mAppSession = mApplicationsState.newSession(mAppSessionCallbacks,
+                        mHost.getLifecycle());
+            }
+            triggerUpdateAppsBypassingDnd();
         }
-        if (mApplicationsState != null && mHost != null) {
-            mAppSession = mApplicationsState.newSession(mAppSessionCallbacks, mHost.getLifecycle());
-        }
-        triggerUpdateAppsBypassingDnd();
     }
 
     private void triggerUpdateAppsBypassingDnd() {
@@ -126,6 +137,10 @@
         ImmutableList<AppEntry> apps = getAppsBypassingDndSortedByName(allApps);
 
         mPreference.setSummary(mSummaryHelper.getAppsSummary(mZenMode, apps));
+
+        mPreference.displayIcons(new CircularIconSet<>(apps,
+                app -> Utils.getBadgedIcon(mContext, app.info)),
+                APP_ENTRY_EQUIVALENCE);
     }
 
     @VisibleForTesting
@@ -146,6 +161,19 @@
                         .toList());
     }
 
+    private static final Equivalence<AppEntry> APP_ENTRY_EQUIVALENCE = new Equivalence<>() {
+        @Override
+        protected boolean doEquivalent(@NonNull AppEntry a, @NonNull AppEntry b) {
+            return a.info.uid == b.info.uid
+                    && Objects.equals(a.info.packageName, b.info.packageName);
+        }
+
+        @Override
+        protected int doHash(@NonNull AppEntry entry) {
+            return Objects.hash(entry.info.uid, entry.info.packageName);
+        }
+    };
+
     @VisibleForTesting
     final ApplicationsState.Callbacks mAppSessionCallbacks =
             new ApplicationsState.Callbacks() {
diff --git a/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java
index 70df9b6..1b51cfa 100644
--- a/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java
@@ -64,7 +64,7 @@
 
         FutureUtil.whenDone(
                 zenMode.getIcon(mContext, ZenIconLoader.getInstance()),
-                icon -> mHeaderController.setIcon(IconUtil.makeBigIconCircle(mContext, icon))
+                icon -> mHeaderController.setIcon(IconUtil.makeIconPickerHeader(mContext, icon))
                         .done(/* rebindActions= */ false),
                 mContext.getMainExecutor());
     }
diff --git a/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java
index 512dabb..93df38b 100644
--- a/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java
@@ -156,7 +156,7 @@
         public void onBindViewHolder(@NonNull IconHolder holder, int position) {
             IconOptionsProvider.IconInfo iconInfo = mIconResources.get(position);
             Drawable iconDrawable = mIconCache.computeIfAbsent(iconInfo,
-                    info -> IconUtil.makeSmallIconCircle(mContext, info.resId()));
+                    info -> IconUtil.makeIconPickerItem(mContext, info.resId()));
             holder.bindIcon(iconInfo, iconDrawable);
         }
 
diff --git a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
index 248ef1d..d7bd517 100644
--- a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
@@ -17,22 +17,39 @@
 package com.android.settings.notification.modes;
 
 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
-import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
+import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_ALARMS;
+import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_EVENTS;
+import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MEDIA;
+import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
+import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
 
 import android.content.Context;
-import android.os.Bundle;
+import android.service.notification.ZenPolicy;
 
 import androidx.annotation.NonNull;
 import androidx.preference.Preference;
 
-import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.R;
 import com.android.settingslib.notification.modes.ZenMode;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.Map;
+
 /**
  * Preference with a link and summary about what other sounds can break through the mode
  */
 class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceController {
 
+    private static final ImmutableMap</* @PriorityCategory */ Integer, /* @DrawableRes */ Integer>
+            PRIORITIES_TO_ICONS = ImmutableMap.of(
+                    PRIORITY_CATEGORY_ALARMS, R.drawable.ic_zen_mode_sound_alarms,
+                    PRIORITY_CATEGORY_MEDIA, R.drawable.ic_zen_mode_sound_media,
+                    PRIORITY_CATEGORY_SYSTEM, R.drawable.ic_zen_mode_sound_system,
+                    PRIORITY_CATEGORY_REMINDERS, R.drawable.ic_zen_mode_sound_reminders,
+                    PRIORITY_CATEGORY_EVENTS, R.drawable.ic_zen_mode_sound_events);
+
     private final ZenModeSummaryHelper mSummaryHelper;
 
     public ZenModeOtherLinkPreferenceController(Context context, String key,
@@ -48,13 +65,23 @@
 
     @Override
     public void updateState(Preference preference, @NonNull ZenMode zenMode) {
-        Bundle bundle = new Bundle();
-        bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
-        preference.setIntent(new SubSettingLauncher(mContext)
-                .setDestination(ZenModeOtherFragment.class.getName())
-                .setSourceMetricsCategory(0)
-                .setArguments(bundle)
-                .toIntent());
+        // TODO: b/332937635 - Update metrics category
+        preference.setIntent(
+                ZenSubSettingLauncher.forModeFragment(mContext, ZenModeOtherFragment.class,
+                        zenMode.getId(), 0).toIntent());
+
         preference.setSummary(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode));
+        ((CircularIconsPreference) preference).displayIcons(getSoundIcons(zenMode.getPolicy()));
+    }
+
+    private CircularIconSet<Integer> getSoundIcons(ZenPolicy policy) {
+        ImmutableList.Builder<Integer> icons = new ImmutableList.Builder<>();
+        for (Map.Entry<Integer, Integer> entry : PRIORITIES_TO_ICONS.entrySet()) {
+            if (policy.isCategoryAllowed(entry.getKey(), false)) {
+                icons.add(entry.getValue());
+            }
+        }
+        return new CircularIconSet<>(icons.build(),
+                iconResId -> IconUtil.makeCircularIconPreferenceItem(mContext, iconResId));
     }
 }
diff --git a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
index 936cea6..762cdd5 100644
--- a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
@@ -17,28 +17,67 @@
 package com.android.settings.notification.modes;
 
 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
-import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
+import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
+import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_NONE;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
+import static android.service.notification.ZenPolicy.STATE_ALLOW;
 
 import android.content.Context;
-import android.os.Bundle;
+import android.content.pm.LauncherApps;
+import android.graphics.drawable.Drawable;
+import android.service.notification.ConversationChannelWrapper;
+import android.service.notification.ZenPolicy;
+import android.service.notification.ZenPolicy.ConversationSenders;
+import android.service.notification.ZenPolicy.PeopleType;
+import android.util.IconDrawableFactory;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
 import androidx.preference.Preference;
 
-import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.R;
+import com.android.settings.notification.modes.ZenHelperBackend.Contact;
+import com.android.settingslib.notification.ConversationIconFactory;
 import com.android.settingslib.notification.modes.ZenMode;
 
+import com.google.common.collect.ImmutableList;
+
+import java.util.function.Function;
+
 /**
- * Preference with a link and summary about what calls and messages can break through the mode
+ * Preference with a link and summary about what calls and messages can break through the mode,
+ * and icons representing those people.
  */
 class ZenModePeopleLinkPreferenceController extends AbstractZenModePreferenceController {
 
     private final ZenModeSummaryHelper mSummaryHelper;
+    private final ZenHelperBackend mHelperBackend;
+    private final ConversationIconFactory mConversationIconFactory;
 
-    public ZenModePeopleLinkPreferenceController(Context context, String key,
+    ZenModePeopleLinkPreferenceController(Context context, String key,
             ZenHelperBackend helperBackend) {
+        this(context, key, helperBackend,
+                new ConversationIconFactory(context,
+                        context.getSystemService(LauncherApps.class),
+                        context.getPackageManager(),
+                        IconDrawableFactory.newInstance(context, false),
+                        context.getResources().getDimensionPixelSize(
+                                R.dimen.zen_mode_circular_icon_diameter)));
+    }
+
+    @VisibleForTesting
+    ZenModePeopleLinkPreferenceController(Context context, String key,
+            ZenHelperBackend helperBackend, ConversationIconFactory conversationIconFactory) {
         super(context, key);
         mSummaryHelper = new ZenModeSummaryHelper(mContext, helperBackend);
+        mHelperBackend = helperBackend;
+        mConversationIconFactory = conversationIconFactory;
     }
 
     @Override
@@ -48,14 +87,109 @@
 
     @Override
     public void updateState(Preference preference, @NonNull ZenMode zenMode) {
-        Bundle bundle = new Bundle();
-        bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
         // TODO(b/332937635): Update metrics category
-        preference.setIntent(new SubSettingLauncher(mContext)
-                .setDestination(ZenModePeopleFragment.class.getName())
-                .setSourceMetricsCategory(0)
-                .setArguments(bundle)
-                .toIntent());
-        preference.setSummary(mSummaryHelper.getPeopleSummary(zenMode));
+        preference.setIntent(
+                ZenSubSettingLauncher.forModeFragment(mContext, ZenModePeopleFragment.class,
+                        zenMode.getId(), 0).toIntent());
+
+        preference.setSummary(mSummaryHelper.getPeopleSummary(zenMode.getPolicy()));
+        ((CircularIconsPreference) preference).displayIcons(getPeopleIcons(zenMode.getPolicy()));
+    }
+
+    // Represents "Either<Contact, ConversationChannelWrapper>".
+    record PeopleItem(@Nullable Contact contact,
+                      @Nullable ConversationChannelWrapper conversation) {
+
+        PeopleItem(@NonNull Contact contact) {
+            this(contact, null);
+        }
+
+        PeopleItem(@NonNull ConversationChannelWrapper conversation) {
+            this(null, conversation);
+        }
+
+    }
+
+    private CircularIconSet<?> getPeopleIcons(ZenPolicy policy) {
+        if (getCallersOrMessagesAllowed(policy) == PEOPLE_TYPE_ANYONE) {
+            return new CircularIconSet<>(
+                    ImmutableList.of(IconUtil.makeCircularIconPreferenceItem(mContext,
+                            R.drawable.ic_zen_mode_people_all)),
+                    Function.identity());
+        }
+
+        ImmutableList.Builder<PeopleItem> peopleItems = ImmutableList.builder();
+        fetchContactsAllowed(policy, peopleItems);
+        fetchConversationsAllowed(policy, peopleItems);
+        return new CircularIconSet<>(peopleItems.build(), this::loadPeopleIcon);
+    }
+
+    /**
+     * Adds {@link PeopleItem} entries corresponding to the set of people (contacts) who can
+     * break through via either call OR message.
+     */
+    private void fetchContactsAllowed(ZenPolicy policy,
+            ImmutableList.Builder<PeopleItem> peopleItems) {
+        @PeopleType int peopleAllowed = getCallersOrMessagesAllowed(policy);
+
+        ImmutableList<Contact> contactsAllowed = ImmutableList.of();
+        if (peopleAllowed == PEOPLE_TYPE_CONTACTS) {
+            contactsAllowed = mHelperBackend.getAllContacts();
+        } else if (peopleAllowed == PEOPLE_TYPE_STARRED) {
+            contactsAllowed = mHelperBackend.getStarredContacts();
+        }
+
+        for (Contact contact : contactsAllowed) {
+            peopleItems.add(new PeopleItem(contact));
+        }
+    }
+
+    /**
+     * Adds {@link PeopleItem} entries corresponding to the set of conversation channels that can
+     * break through.
+     */
+    private void fetchConversationsAllowed(ZenPolicy policy,
+            ImmutableList.Builder<PeopleItem> peopleItems) {
+        @ConversationSenders int conversationSendersAllowed =
+                policy.getPriorityCategoryConversations() == STATE_ALLOW
+                        ? policy.getPriorityConversationSenders()
+                        : CONVERSATION_SENDERS_NONE;
+        ImmutableList<ConversationChannelWrapper> conversationsAllowed = ImmutableList.of();
+        if (conversationSendersAllowed == CONVERSATION_SENDERS_ANYONE) {
+            // TODO: b/354658240 - Need to handle CONVERSATION_SENDERS_ANYONE?
+            return;
+        } else if (conversationSendersAllowed == CONVERSATION_SENDERS_IMPORTANT) {
+            conversationsAllowed = mHelperBackend.getImportantConversations();
+        }
+
+        for (ConversationChannelWrapper conversation : conversationsAllowed) {
+            peopleItems.add(new PeopleItem(conversation));
+        }
+    }
+
+    /** Returns the broadest set of people who can call OR message. */
+    private @PeopleType int getCallersOrMessagesAllowed(ZenPolicy policy) {
+        @PeopleType int callersAllowed = policy.getPriorityCategoryCalls() == STATE_ALLOW
+                ? policy.getPriorityCallSenders() : PEOPLE_TYPE_NONE;
+        @PeopleType int messagesAllowed = policy.getPriorityCategoryMessages() == STATE_ALLOW
+                ? policy.getPriorityMessageSenders() : PEOPLE_TYPE_NONE;
+
+        // Order is ANYONE -> CONTACTS -> STARRED -> NONE, so just taking the minimum works.
+        return Math.min(callersAllowed, messagesAllowed);
+    }
+
+    @WorkerThread
+    private Drawable loadPeopleIcon(PeopleItem peopleItem) {
+        if (peopleItem.contact != null) {
+            return mHelperBackend.getContactPhoto(peopleItem.contact);
+        } else if (peopleItem.conversation != null) {
+            return mConversationIconFactory.getConversationDrawable(
+                    peopleItem.conversation.getShortcutInfo(),
+                    peopleItem.conversation.getPkg(),
+                    peopleItem.conversation.getUid(),
+                    /* important= */ true);
+        } else {
+            throw new IllegalArgumentException("Neither contact nor conversation!");
+        }
     }
 }
diff --git a/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java
index 0f9323d..32c6a98 100644
--- a/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java
@@ -30,10 +30,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
 import android.icu.text.MessageFormat;
 import android.provider.Contacts;
-import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.ZenPolicy;
 import android.view.View;
 
@@ -167,17 +165,7 @@
     }
 
     private void updateChannelCounts() {
-        ParceledListSlice<ConversationChannelWrapper> impConversations =
-                mHelperBackend.getConversations(true);
-        int numImportantConversations = 0;
-        if (impConversations != null) {
-            for (ConversationChannelWrapper conversation : impConversations.getList()) {
-                if (!conversation.getNotificationChannel().isDemoted()) {
-                    numImportantConversations++;
-                }
-            }
-        }
-        mNumImportantConversations = numImportantConversations;
+        mNumImportantConversations = mHelperBackend.getImportantConversations().size();
     }
 
     private int getPrioritySenders(ZenPolicy policy) {
diff --git a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java
index 86135a9..1f97902 100644
--- a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java
@@ -16,14 +16,26 @@
 
 package com.android.settings.notification.modes;
 
+import static android.app.AutomaticZenRule.TYPE_BEDTIME;
+import static android.app.AutomaticZenRule.TYPE_DRIVING;
 import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
 import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
+import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.service.notification.SystemZenRules;
+import android.service.notification.ZenModeConfig;
 import android.util.Log;
 
+import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceCategory;
@@ -35,6 +47,8 @@
 import com.android.settingslib.notification.modes.ZenMode;
 import com.android.settingslib.notification.modes.ZenModesBackend;
 
+import com.google.common.base.Strings;
+
 /**
  * Preference controller for the link to an individual mode's configuration page.
  */
@@ -42,26 +56,29 @@
     private static final String TAG = "ZenModeSetTriggerLink";
 
     @VisibleForTesting
-    protected static final String AUTOMATIC_TRIGGER_PREF_KEY = "zen_automatic_trigger_settings";
+    static final String AUTOMATIC_TRIGGER_KEY = "zen_automatic_trigger_settings";
+    static final String ADD_TRIGGER_KEY = "zen_add_automatic_trigger";
 
+    private final DashboardFragment mFragment;
+    private final PackageManager mPackageManager;
     private final ConfigurationActivityHelper mConfigurationActivityHelper;
     private final ZenServiceListing mServiceListing;
-    private final DashboardFragment mFragment;
 
     ZenModeSetTriggerLinkPreferenceController(Context context, String key,
             DashboardFragment fragment, ZenModesBackend backend) {
-        this(context, key, fragment, backend,
+        this(context, key, fragment, backend, context.getPackageManager(),
                 new ConfigurationActivityHelper(context.getPackageManager()),
                 new ZenServiceListing(context));
     }
 
     @VisibleForTesting
     ZenModeSetTriggerLinkPreferenceController(Context context, String key,
-            DashboardFragment fragment, ZenModesBackend backend,
+            DashboardFragment fragment, ZenModesBackend backend, PackageManager packageManager,
             ConfigurationActivityHelper configurationActivityHelper,
             ZenServiceListing serviceListing) {
         super(context, key, backend);
         mFragment = fragment;
+        mPackageManager = packageManager;
         mConfigurationActivityHelper = configurationActivityHelper;
         mServiceListing = serviceListing;
     }
@@ -83,64 +100,137 @@
         // This controller is expected to govern a preference category so that it controls the
         // availability of the entire preference category if the mode doesn't have a way to
         // automatically trigger (such as manual DND).
-        PrimarySwitchPreference switchPref = ((PreferenceCategory) preference).findPreference(
-                AUTOMATIC_TRIGGER_PREF_KEY);
-        if (switchPref == null) {
+        if (zenMode.isManualDnd()) {
             return;
         }
-        switchPref.setChecked(zenMode.getRule().isEnabled());
-        switchPref.setOnPreferenceChangeListener(mSwitchChangeListener);
-        switchPref.setSummary(zenMode.getRule().getTriggerDescription());
-        switchPref.setIcon(null);
-        switchPref.setOnPreferenceClickListener(null);
-        switchPref.setIntent(null);
+        PrimarySwitchPreference triggerPref = checkNotNull(
+                ((PreferenceCategory) preference).findPreference(AUTOMATIC_TRIGGER_KEY));
+        Preference addTriggerPref = checkNotNull(
+                ((PreferenceCategory) preference).findPreference(ADD_TRIGGER_KEY));
 
-        if (zenMode.isSystemOwned()) {
-            if (zenMode.getType() == TYPE_SCHEDULE_TIME) {
-                switchPref.setTitle(R.string.zen_mode_set_schedule_link);
-                // TODO: b/332937635 - set correct metrics category
-                switchPref.setIntent(ZenSubSettingLauncher.forModeFragment(mContext,
-                        ZenModeSetScheduleFragment.class, zenMode.getId(), 0).toIntent());
-            } else if (zenMode.getType() == TYPE_SCHEDULE_CALENDAR) {
-                switchPref.setTitle(R.string.zen_mode_set_calendar_link);
-                switchPref.setIcon(null);
-                // TODO: b/332937635 - set correct metrics category
-                switchPref.setIntent(ZenSubSettingLauncher.forModeFragment(mContext,
-                        ZenModeSetCalendarFragment.class, zenMode.getId(), 0).toIntent());
-            } else {
-                switchPref.setTitle(R.string.zen_mode_select_schedule);
-                switchPref.setIcon(R.drawable.ic_add_24dp);
-                switchPref.setSummary("");
-                // TODO: b/342156843 - Hide the switch (needs support in SettingsLib).
-                switchPref.setOnPreferenceClickListener(clickedPreference -> {
-                    ZenModeScheduleChooserDialog.show(mFragment, mOnScheduleOptionListener);
-                    return true;
-                });
-            }
+        boolean isAddTrigger = zenMode.isSystemOwned() && zenMode.getType() != TYPE_SCHEDULE_TIME
+                && zenMode.getType() != TYPE_SCHEDULE_CALENDAR;
+
+        if (isAddTrigger) {
+            triggerPref.setVisible(false);
+            addTriggerPref.setVisible(true);
+            addTriggerPref.setOnPreferenceClickListener(unused -> {
+                ZenModeScheduleChooserDialog.show(mFragment, mOnScheduleOptionListener);
+                return true;
+            });
         } else {
-            Intent intent = mConfigurationActivityHelper.getConfigurationActivityIntentForMode(
-                    zenMode, mServiceListing::findService);
-            if (intent != null) {
-                preference.setVisible(true);
-                switchPref.setTitle(R.string.zen_mode_configuration_link_title);
-                switchPref.setSummary(zenMode.getRule().getTriggerDescription());
-                switchPref.setIntent(intent);
+            addTriggerPref.setVisible(false);
+            triggerPref.setVisible(true);
+            triggerPref.setChecked(zenMode.getRule().isEnabled());
+            triggerPref.setOnPreferenceChangeListener(mSwitchChangeListener);
+
+            if (zenMode.isSystemOwned()) {
+                setUpForSystemOwnedTrigger(triggerPref, zenMode);
             } else {
-                Log.i(TAG, "No intent found for " + zenMode.getRule().getName());
-                preference.setVisible(false);
+                setUpForAppTrigger(triggerPref, zenMode);
             }
         }
     }
 
+    private void setUpForSystemOwnedTrigger(Preference preference, ZenMode mode) {
+        if (mode.getType() == TYPE_SCHEDULE_TIME) {
+            // TODO: b/332937635 - set correct metrics category
+            preference.setIntent(ZenSubSettingLauncher.forModeFragment(mContext,
+                    ZenModeSetScheduleFragment.class, mode.getId(), 0).toIntent());
+
+            // [Clock Icon] 9:00 - 17:00 / Sun-Mon
+            preference.setIcon(com.android.internal.R.drawable.ic_zen_mode_type_schedule_time);
+            ZenModeConfig.ScheduleInfo schedule =
+                    tryParseScheduleConditionId(mode.getRule().getConditionId());
+            if (schedule != null) {
+                preference.setTitle(SystemZenRules.getTimeSummary(mContext, schedule));
+                preference.setSummary(SystemZenRules.getShortDaysSummary(mContext, schedule));
+            } else {
+                // Fallback, but shouldn't happen.
+                Log.wtf(TAG, "SCHEDULE_TIME mode without schedule: " + mode);
+                preference.setTitle(R.string.zen_mode_set_schedule_link);
+                preference.setSummary(null);
+            }
+        } else if (mode.getType() == TYPE_SCHEDULE_CALENDAR) {
+            // TODO: b/332937635 - set correct metrics category
+            preference.setIntent(ZenSubSettingLauncher.forModeFragment(mContext,
+                    ZenModeSetCalendarFragment.class, mode.getId(), 0).toIntent());
+
+            // [Event Icon] Calendar Events / <Calendar name>
+            preference.setIcon(
+                    com.android.internal.R.drawable.ic_zen_mode_type_schedule_calendar);
+            preference.setTitle(R.string.zen_mode_trigger_title_schedule_calendar);
+            preference.setSummary(mode.getTriggerDescription());
+        } else {
+            Log.wtf(TAG, "Unexpected type for system-owned mode: " + mode);
+        }
+    }
+
+    @SuppressLint("SwitchIntDef")
+    private void setUpForAppTrigger(Preference preference, ZenMode mode) {
+        // App-owned mode may have triggerDescription, configurationActivity, or both/neither.
+        Intent configurationIntent =
+                mConfigurationActivityHelper.getConfigurationActivityIntentForMode(
+                        mode, mServiceListing::findService);
+
+        @StringRes int title = switch (mode.getType()) {
+            case TYPE_BEDTIME -> R.string.zen_mode_trigger_title_bedtime;
+            case TYPE_DRIVING -> R.string.zen_mode_trigger_title_driving;
+            default -> R.string.zen_mode_trigger_title_generic;
+        };
+
+        String summary;
+        if (!Strings.isNullOrEmpty(mode.getTriggerDescription())) {
+            summary = mode.getTriggerDescription();
+        } else if (!Strings.isNullOrEmpty(mode.getRule().getPackageName())) {
+            String appName = null;
+            try {
+                ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
+                        mode.getRule().getPackageName(), 0);
+                appName = appInfo.loadLabel(mPackageManager).toString();
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.e(TAG, "Couldn't resolve owner for mode: " + mode);
+            }
+
+            if (appName != null) {
+                summary = mContext.getString(
+                        configurationIntent != null
+                                ? R.string.zen_mode_trigger_summary_settings_in_app
+                                : R.string.zen_mode_trigger_summary_managed_by_app,
+                        appName);
+            } else {
+                summary = null;
+            }
+        } else {
+            Log.e(TAG, "Mode without package! " + mode);
+            summary = null;
+        }
+
+        @DrawableRes int icon;
+        if (mode.getType() == TYPE_BEDTIME) {
+            icon = com.android.internal.R.drawable.ic_zen_mode_type_schedule_time; // Clock
+        } else if (mode.getType() == TYPE_DRIVING) {
+            icon = com.android.internal.R.drawable.ic_zen_mode_type_driving; // Car
+        } else {
+            icon = configurationIntent != null ? R.drawable.ic_zen_mode_trigger_with_activity
+                    : R.drawable.ic_zen_mode_trigger_without_activity;
+        }
+
+        preference.setTitle(title);
+        preference.setSummary(summary);
+        preference.setIcon(icon);
+        preference.setIntent(configurationIntent);
+    }
+
     @VisibleForTesting
     final ZenModeScheduleChooserDialog.OnScheduleOptionListener mOnScheduleOptionListener =
             conditionId -> saveMode(mode -> {
                 mode.setCustomModeConditionId(mContext, conditionId);
                 return mode;
+                // TODO: b/342156843 - Maybe jump to the corresponding schedule editing screen?
             });
 
-    @VisibleForTesting
-    protected Preference.OnPreferenceChangeListener mSwitchChangeListener = (p, newValue) -> {
+    private final Preference.OnPreferenceChangeListener mSwitchChangeListener = (p, newValue) -> {
         final boolean newEnabled = (Boolean) newValue;
         return saveMode((zenMode) -> {
             if (newEnabled != zenMode.getRule().isEnabled()) {
@@ -148,6 +238,5 @@
             }
             return zenMode;
         });
-        // TODO: b/342156843 - Do we want to jump to the corresponding schedule editing screen?
     };
 }
diff --git a/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java b/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
index 26de9ee..1acef20 100644
--- a/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
+++ b/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
@@ -31,6 +31,7 @@
 import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
 import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS;
 import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
+import static android.service.notification.ZenPolicy.STATE_ALLOW;
 import static android.service.notification.ZenPolicy.VISUAL_EFFECT_AMBIENT;
 import static android.service.notification.ZenPolicy.VISUAL_EFFECT_BADGE;
 import static android.service.notification.ZenPolicy.VISUAL_EFFECT_FULL_SCREEN_INTENT;
@@ -45,6 +46,8 @@
 import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenPolicy;
+import android.service.notification.ZenPolicy.ConversationSenders;
+import android.service.notification.ZenPolicy.PeopleType;
 import android.util.ArrayMap;
 
 import androidx.annotation.NonNull;
@@ -56,6 +59,9 @@
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
 import com.android.settingslib.notification.modes.ZenMode;
 
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -85,14 +91,18 @@
             PRIORITY_CATEGORY_REPEAT_CALLERS,
     };
 
+    static final ImmutableList</* @PriorityCategory */ Integer> OTHER_SOUND_CATEGORIES =
+            ImmutableList.of(
+                PRIORITY_CATEGORY_ALARMS,
+                PRIORITY_CATEGORY_MEDIA,
+                PRIORITY_CATEGORY_SYSTEM,
+                PRIORITY_CATEGORY_REMINDERS,
+                PRIORITY_CATEGORY_EVENTS);
+
     String getOtherSoundCategoriesSummary(ZenMode zenMode) {
         List<String> enabledCategories = getEnabledCategories(
                 zenMode.getPolicy(),
-                category -> PRIORITY_CATEGORY_ALARMS == category
-                        || PRIORITY_CATEGORY_MEDIA == category
-                        || PRIORITY_CATEGORY_SYSTEM == category
-                        || PRIORITY_CATEGORY_REMINDERS == category
-                        || PRIORITY_CATEGORY_EVENTS == category,
+                OTHER_SOUND_CATEGORIES::contains,
                 true);
         int numCategories = enabledCategories.size();
         MessageFormat msgFormat = new MessageFormat(
@@ -359,7 +369,12 @@
     }
 
     public String getStarredContactsSummary() {
-        List<String> starredContacts = mBackend.getStarredContacts();
+        List<String> starredContacts = mBackend.getStarredContacts().stream()
+                .map(ZenHelperBackend.Contact::displayName)
+                .map(name -> Strings.isNullOrEmpty(name)
+                        ? mContext.getString(R.string.zen_mode_starred_contacts_empty_name)
+                        : name)
+                .toList();
         int numStarredContacts = starredContacts.size();
         MessageFormat msgFormat = new MessageFormat(
                 mContext.getString(R.string.zen_mode_starred_contacts_summary_contacts),
@@ -383,26 +398,32 @@
                 mContext.getString(R.string.zen_mode_contacts_count),
                 Locale.getDefault());
         Map<String, Object> args = new HashMap<>();
-        args.put("count", mBackend.queryAllContactsData().getCount());
+        args.put("count", mBackend.getAllContactsCount());
         return msgFormat.format(args);
     }
 
-    public String getPeopleSummary(ZenMode zenMode) {
-        final int callersAllowed = zenMode.getPolicy().getPriorityCallSenders();
-        final int messagesAllowed = zenMode.getPolicy().getPriorityMessageSenders();
-        final int conversationsAllowed = zenMode.getPolicy().getPriorityConversationSenders();
+    public String getPeopleSummary(ZenPolicy policy) {
+        @PeopleType int callersAllowed = policy.getPriorityCategoryCalls() == STATE_ALLOW
+                ? policy.getPriorityCallSenders() : PEOPLE_TYPE_NONE;
+        @PeopleType int messagesAllowed = policy.getPriorityCategoryMessages() == STATE_ALLOW
+                ? policy.getPriorityMessageSenders() : PEOPLE_TYPE_NONE;
+        @ConversationSenders int conversationsAllowed =
+                policy.getPriorityCategoryConversations() == STATE_ALLOW
+                        ? policy.getPriorityConversationSenders()
+                        : CONVERSATION_SENDERS_NONE;
         final boolean areRepeatCallersAllowed =
-                zenMode.getPolicy().isCategoryAllowed(PRIORITY_CATEGORY_REPEAT_CALLERS, false);
+                policy.isCategoryAllowed(PRIORITY_CATEGORY_REPEAT_CALLERS, false);
 
         if (callersAllowed == PEOPLE_TYPE_ANYONE
                 && messagesAllowed == PEOPLE_TYPE_ANYONE
                 && conversationsAllowed == CONVERSATION_SENDERS_ANYONE) {
-            return mContext.getResources().getString(R.string.zen_mode_people_all);
+            return mContext.getString(R.string.zen_mode_people_all);
         } else if (callersAllowed == PEOPLE_TYPE_NONE
                 && messagesAllowed == PEOPLE_TYPE_NONE
-                && conversationsAllowed == CONVERSATION_SENDERS_NONE
-                && !areRepeatCallersAllowed) {
-            return mContext.getResources().getString(R.string.zen_mode_people_none);
+                && conversationsAllowed == CONVERSATION_SENDERS_NONE) {
+            return mContext.getString(
+                    areRepeatCallersAllowed ? R.string.zen_mode_people_repeat_callers
+                            : R.string.zen_mode_people_none);
         } else {
             return mContext.getResources().getString(R.string.zen_mode_people_some);
         }
diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java
index 915fe17..d5d079e 100644
--- a/src/com/android/settings/password/ChooseLockGeneric.java
+++ b/src/com/android/settings/password/ChooseLockGeneric.java
@@ -492,9 +492,10 @@
                     : null;
                 updatePreferencesOrFinish(false /* isRecreatingActivity */);
                 if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getContext(),
-                        mBiometricsAuthSuccessful, mWaitingForConfirmation)) {
+                        mBiometricsAuthSuccessful, mWaitingForConfirmation, mUserId)) {
                     mWaitingForConfirmation = true;
-                    Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST);
+                    Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST,
+                            mUserId);
                 }
             } else if (requestCode == BIOMETRIC_AUTH_REQUEST) {
                 if (resultCode == Activity.RESULT_OK) {
@@ -777,6 +778,9 @@
                         entries.removePreference(pref);
                     } else if (!enabled) {
                         pref.setEnabled(false);
+                        pref.setSummary(
+                                com.android.settingslib.widget
+                                        .restricted.R.string.disabled_by_admin);
                     }
                 }
             }
diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java
index a645300..bcf1795 100644
--- a/src/com/android/settings/password/ChooseLockPassword.java
+++ b/src/com/android/settings/password/ChooseLockPassword.java
@@ -271,6 +271,8 @@
 
         private static final int CONFIRM_EXISTING_REQUEST = 58;
         static final int RESULT_FINISHED = RESULT_FIRST_USER;
+        private boolean mIsErrorTooShort = true;
+
         /** Used to store the profile type for which pin/password is being set */
         protected enum ProfileType {
             None,
@@ -672,6 +674,11 @@
             view.addView(mPasswordRestrictionView);
         }
 
+        @VisibleForTesting
+        View getPasswordRequirementsView() {
+            return mPasswordRestrictionView;
+        }
+
         private void createHintMessageView(ViewGroup view) {
             if (mPasswordRestrictionView != null) {
                 return;
@@ -855,6 +862,7 @@
          */
         String[] convertErrorCodeToMessages() {
             List<String> messages = new ArrayList<>();
+            mIsErrorTooShort = false;
             for (PasswordValidationError error : mValidationErrors) {
                 switch (error.errorCode) {
                     case CONTAINS_INVALID_CHARACTERS:
@@ -889,6 +897,7 @@
                                 R.string.lockpassword_password_requires_nonnumerical));
                         break;
                     case TOO_SHORT:
+                        mIsErrorTooShort = true;
                         String message = StringUtil.getIcuPluralsString(getContext(),
                                 error.requirement,
                                 mIsAlphaMode
@@ -951,12 +960,13 @@
                     ? LockscreenCredential.createPassword(mPasswordEntry.getText())
                     : LockscreenCredential.createPin(mPasswordEntry.getText());
             final int length = password.size();
+
             if (mUiStage == Stage.Introduction) {
                 mPasswordRestrictionView.setVisibility(View.VISIBLE);
                 final boolean passwordCompliant = validatePassword(password);
                 String[] messages = convertErrorCodeToMessages();
                 // Update the fulfillment of requirements.
-                mPasswordRequirementAdapter.setRequirements(messages);
+                mPasswordRequirementAdapter.setRequirements(messages, mIsErrorTooShort);
                 // set the visibility of pin_auto_confirm option accordingly
                 setAutoPinConfirmOption(passwordCompliant, length);
                 // Enable/Disable the next button accordingly.
diff --git a/src/com/android/settings/password/PasswordRequirementAdapter.java b/src/com/android/settings/password/PasswordRequirementAdapter.java
index 7cf6a00..b17f864 100644
--- a/src/com/android/settings/password/PasswordRequirementAdapter.java
+++ b/src/com/android/settings/password/PasswordRequirementAdapter.java
@@ -36,6 +36,7 @@
 
     private String[] mRequirements;
     private Context mContext;
+    private boolean mIsTooShortError = true;
 
     public PasswordRequirementAdapter(Context context) {
         mContext = context;
@@ -54,8 +55,9 @@
         return  mRequirements.length;
     }
 
-    public void setRequirements(String[] requirements) {
+    public void setRequirements(String[] requirements, boolean isPasswordShort) {
         mRequirements = requirements;
+        mIsTooShortError = isPasswordShort;
         notifyDataSetChanged();
     }
 
@@ -74,7 +76,12 @@
         final int fontSize = mContext.getResources().getDimensionPixelSize(
                 R.dimen.password_requirement_font_size);
         holder.mDescriptionText.setText(mRequirements[position]);
-        holder.mDescriptionText.setTextAppearance(R.style.ScreenLockPasswordHintTextFontStyle);
+        if (mIsTooShortError) {
+            holder.mDescriptionText.setTextAppearance(R.style.ScreenLockPasswordHintTextFontStyle);
+        } else {
+            holder.mDescriptionText.
+                    setTextAppearance(R.style.ScreenLockPasswordHintTextFontStyleError);
+        }
         holder.mDescriptionText.setTextSize(fontSize / mContext.getResources()
                 .getDisplayMetrics().scaledDensity);
     }
diff --git a/src/com/android/settings/privatespace/onelock/UseOneLockControllerSwitch.java b/src/com/android/settings/privatespace/onelock/UseOneLockControllerSwitch.java
index dfac100..1e503ec 100644
--- a/src/com/android/settings/privatespace/onelock/UseOneLockControllerSwitch.java
+++ b/src/com/android/settings/privatespace/onelock/UseOneLockControllerSwitch.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.privatespace.onelock;
 
+import static com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS;
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CHOOSE_LOCK_SCREEN_TITLE;
 import static com.android.settings.privatespace.PrivateSpaceSetupActivity.EXTRA_ACTION_TYPE;
 import static com.android.settings.privatespace.PrivateSpaceSetupActivity.SET_LOCK_ACTION;
 import static com.android.settings.privatespace.onelock.UseOneLockSettingsFragment.UNIFY_PRIVATE_LOCK_WITH_DEVICE_REQUEST;
@@ -197,12 +199,7 @@
                   .setPositiveButton(
                             R.string.private_space_set_lock_label,
                             (dialog, which) -> {
-                                Intent intent = new Intent(mContext,
-                                          PrivateProfileContextHelperActivity.class);
-                                intent.putExtra(EXTRA_ACTION_TYPE, SET_LOCK_ACTION);
-                                ((Activity) mContext).startActivityForResultAsUser(intent,
-                                          UNUNIFY_PRIVATE_LOCK_FROM_DEVICE_REQUEST,
-                                          /*Options*/ null, mUserHandle);
+                                startSeparateLockSetup();
                             })
                   .setNegativeButton(R.string.private_space_cancel_label,
                             (DialogInterface dialog, int which) -> {
@@ -216,4 +213,26 @@
                             })
                   .show();
     }
+
+    private void startSeparateLockSetup() {
+        if (android.multiuser.Flags.modifyPrivateSpaceSecondaryUnlockSetupFlow()) {
+            final Bundle extras = new Bundle();
+            extras.putInt(Intent.EXTRA_USER_ID, mProfileUserId);
+            extras.putBoolean(HIDE_INSECURE_OPTIONS, true);
+            extras.putInt(EXTRA_KEY_CHOOSE_LOCK_SCREEN_TITLE,
+                    R.string.private_space_lock_setup_title);
+            new SubSettingLauncher(mContext).setDestination(ChooseLockGeneric
+                            .ChooseLockGenericFragment.class.getName())
+                    .setSourceMetricsCategory(mHost.getMetricsCategory())
+                    .setArguments(extras)
+                    .setExtras(extras)
+                    .setTransitionType(SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE)
+                    .launch();
+        } else {
+            Intent intent = new Intent(mContext, PrivateProfileContextHelperActivity.class);
+            intent.putExtra(EXTRA_ACTION_TYPE, SET_LOCK_ACTION);
+            ((Activity) mContext).startActivityForResultAsUser(intent,
+                    UNUNIFY_PRIVATE_LOCK_FROM_DEVICE_REQUEST, /*Options*/ null, mUserHandle);
+        }
+    }
 }
diff --git a/src/com/android/settings/sim/smartForwarding/SmartForwardingFragment.java b/src/com/android/settings/sim/smartForwarding/SmartForwardingFragment.java
index a95eb38..f847147 100644
--- a/src/com/android/settings/sim/smartForwarding/SmartForwardingFragment.java
+++ b/src/com/android/settings/sim/smartForwarding/SmartForwardingFragment.java
@@ -48,8 +48,7 @@
     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         setPreferencesFromResource(R.xml.smart_forwarding_switch, rootKey);
 
-        String title = getResources().getString(R.string.smart_forwarding_title);
-        getActivity().getActionBar().setTitle(title);
+        getActivity().setTitle(R.string.smart_forwarding_title);
 
         TwoStatePreference smartForwardingSwitch = findPreference(KEY_SMART_FORWARDING_SWITCH);
         if (turnOffSwitch) {
diff --git a/tests/robotests/src/com/android/settings/MainClearTest.java b/tests/robotests/src/com/android/settings/MainClearTest.java
index 187fce1..26a430b 100644
--- a/tests/robotests/src/com/android/settings/MainClearTest.java
+++ b/tests/robotests/src/com/android/settings/MainClearTest.java
@@ -140,8 +140,8 @@
         when(mScrollView.getChildCount()).thenReturn(1);
         doReturn(mMockActivity).when(mMainClear).getActivity();
         when(mMockActivity.getSystemService(BiometricManager.class)).thenReturn(mBiometricManager);
-        when(mBiometricManager.canAuthenticate(
-                BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+        when(mBiometricManager.canAuthenticate(anyInt(),
+                eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)))
                 .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
     }
 
@@ -370,8 +370,8 @@
         when(mContext.getResources()).thenReturn(mResources);
         when(mMockActivity.getSystemService(BiometricManager.class)).thenReturn(mBiometricManager);
         when(mResources.getString(anyInt())).thenReturn(TEST_ACCOUNT_NAME);
-        when(mBiometricManager.canAuthenticate(
-                BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+        when(mBiometricManager.canAuthenticate(anyInt(),
+                eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)))
                 .thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
         doReturn(true).when(mMainClear).isValidRequestCode(eq(MainClear.KEYGUARD_REQUEST));
         doNothing().when(mMainClear).startActivityForResult(any(), anyInt());
diff --git a/tests/robotests/src/com/android/settings/UtilsTest.java b/tests/robotests/src/com/android/settings/UtilsTest.java
index fd97b78..b36e9d6 100644
--- a/tests/robotests/src/com/android/settings/UtilsTest.java
+++ b/tests/robotests/src/com/android/settings/UtilsTest.java
@@ -81,6 +81,7 @@
 import androidx.fragment.app.FragmentActivity;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.password.ChooseLockSettingsHelper;
 import com.android.settings.password.ConfirmDeviceCredentialActivity;
 import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
 
@@ -532,31 +533,45 @@
         when(mContext.getSystemService(BiometricManager.class)).thenReturn(null);
         assertThat(Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
                 false /* biometricsSuccessfullyAuthenticated */,
-                false /* biometricsAuthenticationRequested */)).isFalse();
+                false /* biometricsAuthenticationRequested */, USER_ID)).isFalse();
     }
 
     @Test
     @EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS)
     public void testRequestBiometricAuthentication_biometricManagerReturnsSuccess_shouldReturnTrue() {
-        when(mBiometricManager.canAuthenticate(
+        when(mBiometricManager.canAuthenticate(USER_ID,
                 BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
                 .thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
-        boolean requestBiometricAuthenticationForMandatoryBiometrics =
+        final boolean requestBiometricAuthenticationForMandatoryBiometrics =
                 Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
-                true /* biometricsSuccessfullyAuthenticated */,
-                false /* biometricsAuthenticationRequested */);
-        assertThat(requestBiometricAuthenticationForMandatoryBiometrics).isFalse();
+                false /* biometricsSuccessfullyAuthenticated */,
+                false /* biometricsAuthenticationRequested */, USER_ID);
+        assertThat(requestBiometricAuthenticationForMandatoryBiometrics).isTrue();
     }
 
     @Test
     @EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS)
     public void testRequestBiometricAuthentication_biometricManagerReturnsError_shouldReturnFalse() {
-        when(mBiometricManager.canAuthenticate(
-                BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+        when(mBiometricManager.canAuthenticate(anyInt(),
+                eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)))
                 .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
         assertThat(Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
                 false /* biometricsSuccessfullyAuthenticated */,
-                false /* biometricsAuthenticationRequested */)).isFalse();
+                false /* biometricsAuthenticationRequested */, USER_ID)).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS)
+    public void testRequestBiometricAuthentication_biometricManagerReturnsSuccessForDifferentUser_shouldReturnFalse() {
+        when(mBiometricManager.canAuthenticate(anyInt(),
+                eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)))
+                .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
+        when(mBiometricManager.canAuthenticate(0 /* userId */,
+                BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+                .thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
+        assertThat(Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
+                false /* biometricsSuccessfullyAuthenticated */,
+                false /* biometricsAuthenticationRequested */, USER_ID)).isFalse();
     }
 
     @Test
@@ -566,7 +581,7 @@
 
         final int requestCode = 1;
         final ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
-        Utils.launchBiometricPromptForMandatoryBiometrics(mFragment, requestCode);
+        Utils.launchBiometricPromptForMandatoryBiometrics(mFragment, requestCode, USER_ID);
 
         verify(mFragment).startActivityForResult(intentArgumentCaptor.capture(), eq(requestCode));
 
@@ -576,9 +591,12 @@
                 BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
         assertThat(intent.getExtra(BIOMETRIC_PROMPT_NEGATIVE_BUTTON_TEXT)).isNotNull();
         assertThat(intent.getExtra(KeyguardManager.EXTRA_DESCRIPTION)).isNotNull();
+        assertThat(intent.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_ALLOW_ANY_USER, false))
+                .isTrue();
+        assertThat(intent.getIntExtra(Intent.EXTRA_USER_ID, 0)).isEqualTo(USER_ID);
         assertThat(intent.getComponent().getPackageName()).isEqualTo(SETTINGS_PACKAGE_NAME);
         assertThat(intent.getComponent().getClassName()).isEqualTo(
-                ConfirmDeviceCredentialActivity.class.getName());
+                ConfirmDeviceCredentialActivity.InternalActivity.class.getName());
     }
 
     private void setUpForConfirmCredentialString(boolean isEffectiveUserManagedProfile) {
diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java
index 1463cd0..cb2429c 100644
--- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java
@@ -26,11 +26,9 @@
 import static java.util.Collections.singletonList;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.AccessibilityShortcutInfo;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
@@ -110,9 +108,7 @@
     private final Context mContext = ApplicationProvider.getApplicationContext();
     @Spy
     private final AccessibilityServiceInfo mServiceInfo = getMockAccessibilityServiceInfo(
-            PACKAGE_NAME, CLASS_NAME);
-    @Mock
-    private AccessibilityShortcutInfo mShortcutInfo;
+            new ComponentName(PACKAGE_NAME, CLASS_NAME));
     private ShadowAccessibilityManager mShadowAccessibilityManager;
     @Mock
     private LocalBluetoothManager mLocalBluetoothManager;
@@ -125,7 +121,6 @@
         mShadowAccessibilityManager.setInstalledAccessibilityServiceList(new ArrayList<>());
         mContext.setTheme(androidx.appcompat.R.style.Theme_AppCompat);
         ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
-        setMockAccessibilityShortcutInfo(mShortcutInfo);
 
         Intent intent = new Intent();
         intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT,
@@ -398,14 +393,25 @@
     public void testAccessibilityMenuInSystem_IncludedInInteractionControl() {
         mShadowAccessibilityManager.setInstalledAccessibilityServiceList(
                 List.of(getMockAccessibilityServiceInfo(
-                        AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM)));
+                        AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM,
+                        /*isSystemApp=*/true)));
         setupFragment();
 
-        final RestrictedPreference pref = mFragment.getPreferenceScreen().findPreference(
-                AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM.flattenToString());
-        final String prefCategory = mFragment.mServicePreferenceToPreferenceCategoryMap.get(
-                pref).getKey();
-        assertThat(prefCategory).isEqualTo(AccessibilitySettings.CATEGORY_INTERACTION_CONTROL);
+        assertThat(getPreferenceCategory(AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM))
+                .isEqualTo(AccessibilitySettings.CATEGORY_INTERACTION_CONTROL);
+    }
+
+    @Test
+    @EnableFlags(com.android.settings.accessibility.Flags.FLAG_CHECK_PREBUNDLED_IS_PREINSTALLED)
+    public void testNonPreinstalledApp_IncludedInDownloadedCategory() {
+        mShadowAccessibilityManager.setInstalledAccessibilityServiceList(
+                List.of(getMockAccessibilityServiceInfo(
+                        AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM,
+                        /*isSystemApp=*/false)));
+        setupFragment();
+
+        assertThat(getPreferenceCategory(AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM))
+                .isEqualTo(AccessibilitySettings.CATEGORY_DOWNLOADED_SERVICES);
     }
 
     @Test
@@ -418,13 +424,20 @@
         assertThat(pref).isNull();
     }
 
-    private AccessibilityServiceInfo getMockAccessibilityServiceInfo(String packageName,
-            String className) {
-        return getMockAccessibilityServiceInfo(new ComponentName(packageName, className));
+    private String getPreferenceCategory(ComponentName componentName) {
+        return mFragment.mServicePreferenceToPreferenceCategoryMap.get(
+                        mFragment.getPreferenceScreen().findPreference(
+                                componentName.flattenToString())).getKey();
     }
 
     private AccessibilityServiceInfo getMockAccessibilityServiceInfo(ComponentName componentName) {
-        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        return getMockAccessibilityServiceInfo(componentName, true);
+    }
+
+    private AccessibilityServiceInfo getMockAccessibilityServiceInfo(ComponentName componentName,
+            boolean isSystemApp) {
+        final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class);
+        when(applicationInfo.isSystemApp()).thenReturn(isSystemApp);
         final ServiceInfo serviceInfo = new ServiceInfo();
         applicationInfo.packageName = componentName.getPackageName();
         serviceInfo.packageName = componentName.getPackageName();
@@ -445,16 +458,6 @@
         return null;
     }
 
-    private void setMockAccessibilityShortcutInfo(AccessibilityShortcutInfo mockInfo) {
-        final ActivityInfo activityInfo = Mockito.mock(ActivityInfo.class);
-        activityInfo.applicationInfo = new ApplicationInfo();
-        when(mockInfo.getActivityInfo()).thenReturn(activityInfo);
-        when(activityInfo.loadLabel(any())).thenReturn(DEFAULT_LABEL);
-        when(mockInfo.loadSummary(any())).thenReturn(DEFAULT_SUMMARY);
-        when(mockInfo.loadDescription(any())).thenReturn(DEFAULT_DESCRIPTION);
-        when(mockInfo.getComponentName()).thenReturn(COMPONENT_NAME);
-    }
-
     private void setInvisibleToggleFragmentType(AccessibilityServiceInfo info) {
         info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.R;
         info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
diff --git a/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java
index 98ed442..5fd11f9 100644
--- a/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java
@@ -16,38 +16,39 @@
 
 package com.android.settings.accessibility;
 
+import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+
 import static com.android.settings.core.BasePreferenceController.AVAILABLE;
 import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
+import static com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.os.Looper;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.PreferenceManager;
 import androidx.preference.PreferenceScreen;
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.server.accessibility.Flags;
 import com.android.settings.widget.SeekBarPreference;
+import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 
 /** Tests for {@link DaltonizerSaturationSeekbarPreferenceController}. */
@@ -60,8 +61,9 @@
     private int mOriginalSaturationLevel = -1;
 
     private PreferenceScreen mScreen;
+    private LifecycleOwner mLifecycleOwner;
+    private Lifecycle mLifecycle;
 
-    @Mock
     private SeekBarPreference mPreference;
 
     @Rule
@@ -69,7 +71,6 @@
 
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
         Context context = ApplicationProvider.getApplicationContext();
         mContentResolver = context.getContentResolver();
         mOriginalSaturationLevel = Settings.Secure.getInt(
@@ -77,10 +78,13 @@
                 Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
                 7);
 
-        mScreen = spy(new PreferenceScreen(context, /* attrs= */ null));
-        when(mScreen.findPreference(ToggleDaltonizerPreferenceFragment.KEY_SATURATION))
-                .thenReturn(mPreference);
+        mPreference = new SeekBarPreference(context);
+        mPreference.setKey(ToggleDaltonizerPreferenceFragment.KEY_SATURATION);
+        mScreen = new PreferenceManager(context).createPreferenceScreen(context);
+        mScreen.addPreference(mPreference);
 
+        mLifecycleOwner = () -> mLifecycle;
+        mLifecycle = new Lifecycle(mLifecycleOwner);
         mController = new DaltonizerSaturationSeekbarPreferenceController(
                 context,
                 ToggleDaltonizerPreferenceFragment.KEY_SATURATION);
@@ -94,6 +98,12 @@
                 mOriginalSaturationLevel);
     }
 
+    @Test
+    public void constructor_defaultValuesMatch() {
+        assertThat(mController.getSliderPosition()).isEqualTo(7);
+        assertThat(mController.getMax()).isEqualTo(10);
+        assertThat(mController.getMin()).isEqualTo(1);
+    }
 
     @Test
     @DisableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION)
@@ -103,28 +113,72 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION)
-    public void getAvailabilityStatus_flagEnabled_available() {
+    public void getAvailabilityStatus_flagEnabledProtanEnabled_available() {
+        setDaltonizerMode(/* enabled= */ 1, /* mode= */ 11);
         assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
     }
 
     @Test
-    public void constructor_defaultValuesMatch() {
-        assertThat(mController.getSliderPosition()).isEqualTo(7);
-        assertThat(mController.getMax()).isEqualTo(10);
-        assertThat(mController.getMin()).isEqualTo(0);
+    @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION)
+    public void getAvailabilityStatus_flagEnabledDeutranEnabled_available() {
+        setDaltonizerMode(/* enabled= */ 1, /* mode= */ 12);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
     }
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION)
-    public void displayPreference_enabled_visible() {
+    public void getAvailabilityStatus_flagEnabledTritanEnabled_available() {
+        setDaltonizerMode(/* enabled= */ 1, /* mode= */ 13);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION)
+    public void getAvailabilityStatus_flagEnabledGrayScale_disabled() {
+        setDaltonizerMode(/* enabled= */ 1, /* mode= */ 0);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION)
+    public void getAvailabilityStatus_flagEnabledColorCorrectionDisabled_disabled() {
+        setDaltonizerMode(/* enabled= */ 0, /* mode= */ 11);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION)
+    public void getAvailabilityStatus_flagEnabledColorCorrectionDisabledGrayScale_disabled() {
+        setDaltonizerMode(/* enabled= */ 0, /* mode= */ 0);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION)
+    public void displayPreference_flagEnabledColorCorrectionEnabled_enabledWithDefaultValues() {
+        setDaltonizerMode(/* enabled= */ 1, /* mode= */ 11);
         mController.displayPreference(mScreen);
 
-        verify(mPreference).setMax(eq(10));
-        verify(mPreference).setMin(eq(0));
-        verify(mPreference).setProgress(eq(7));
-        verify(mPreference).setContinuousUpdates(eq(true));
-        verify(mPreference).setOnPreferenceChangeListener(eq(mController));
-        verify(mPreference).setVisible(eq(true));
+        assertThat(mPreference.isEnabled()).isTrue();
+        assertThat(mPreference.getMax()).isEqualTo(10);
+        assertThat(mPreference.getMin()).isEqualTo(1);
+        assertThat(mPreference.getProgress()).isEqualTo(7);
+        assertThat(mPreference.isVisible()).isTrue();
+        assertThat(mPreference.getOnPreferenceChangeListener()).isEqualTo(mController);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION)
+    public void displayPreference_flagEnabledColorCorrectionDisabled_disabledWithDefaultValues() {
+        setDaltonizerMode(/* enabled= */ 0, /* mode= */ 11);
+        mController.displayPreference(mScreen);
+
+        assertThat(mPreference.isEnabled()).isFalse();
+        assertThat(mPreference.getMax()).isEqualTo(10);
+        assertThat(mPreference.getMin()).isEqualTo(1);
+        assertThat(mPreference.getProgress()).isEqualTo(7);
+        assertThat(mPreference.isVisible()).isTrue();
+        assertThat(mPreference.getOnPreferenceChangeListener()).isEqualTo(mController);
     }
 
     @Test
@@ -132,12 +186,8 @@
     public void displayPreference_disabled_notVisible() {
         mController.displayPreference(mScreen);
 
-        verify(mPreference).setMax(eq(10));
-        verify(mPreference).setMin(eq(0));
-        verify(mPreference).setProgress(eq(7));
-        verify(mPreference).setContinuousUpdates(eq(true));
-        verify(mPreference, never()).setOnPreferenceChangeListener(any());
-        verify(mPreference).setVisible(eq(false));
+        assertThat(mPreference.isVisible()).isFalse();
+        assertThat(mPreference.getOnPreferenceChangeListener()).isNull();
     }
 
     @Test
@@ -153,13 +203,13 @@
 
     @Test
     public void setSliderPosition_min_secureSettingsUpdated() {
-        var isSliderSet = mController.setSliderPosition(0);
+        var isSliderSet = mController.setSliderPosition(1);
 
         assertThat(isSliderSet).isTrue();
         assertThat(Settings.Secure.getInt(
                 mContentResolver,
                 Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
-                7)).isEqualTo(0);
+                7)).isEqualTo(1);
     }
 
     @Test
@@ -194,4 +244,140 @@
                 Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
                 7)).isEqualTo(7);
     }
+
+    @Test
+    public void updateState_enabledProtan_preferenceEnabled() {
+        setDaltonizerMode(/* enabled= */ 1, /* mode= */ 11);
+
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void updateState_enabledDeuteran_preferenceEnabled() {
+        setDaltonizerMode(/* enabled= */ 1, /* mode= */ 12);
+
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void updateState_enabledTritan_preferenceEnabled() {
+        setDaltonizerMode(/* enabled= */ 1, /* mode= */ 13);
+
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void updateState_disabledGrayScale_preferenceDisabled() {
+        setDaltonizerMode(/* enabled= */ 0, /* mode= */ 0);
+
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void updateState_nullPreference_noError() {
+        setDaltonizerMode(/* enabled= */ 0, /* mode= */ 0);
+
+        mController.updateState(null);
+    }
+
+    @Test
+    public void updateState_enabledGrayScale_preferenceDisabled() {
+        setDaltonizerMode(/* enabled= */ 1, /* mode= */ 0);
+
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onResume_daltonizerEnabledAfterResumed_preferenceEnabled() {
+        setDaltonizerMode(/* enabled= */ 0, /* mode= */ 11);
+        mController.displayPreference(mScreen);
+        assertThat(mPreference.isEnabled()).isFalse();
+
+        mLifecycle.addObserver(mController);
+        mLifecycle.handleLifecycleEvent(ON_RESUME);
+
+        Settings.Secure.putInt(
+                mContentResolver,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+                1);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void onResume_daltonizerDisabledAfterResumed_preferenceDisabled() {
+        setDaltonizerMode(/* enabled= */ 1, /* mode= */ 11);
+        mController.displayPreference(mScreen);
+        assertThat(mPreference.isEnabled()).isTrue();
+
+        mLifecycle.addObserver(mController);
+        mLifecycle.handleLifecycleEvent(ON_RESUME);
+
+        Settings.Secure.putInt(
+                mContentResolver,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+                0);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onResume_daltonizerGrayScaledAfterResumed_preferenceDisabled() {
+        setDaltonizerMode(/* enabled= */ 1, /* mode= */ 11);
+        mController.displayPreference(mScreen);
+        assertThat(mPreference.isEnabled()).isTrue();
+
+        mLifecycle.addObserver(mController);
+        mLifecycle.handleLifecycleEvent(ON_RESUME);
+
+        Settings.Secure.putInt(
+                mContentResolver,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
+                0);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void onStop_daltonizerEnabledAfterOnStop_preferenceNotChanged() {
+        setDaltonizerMode(/* enabled= */ 0, /* mode= */ 11);
+        mController.displayPreference(mScreen);
+        assertThat(mPreference.isEnabled()).isFalse();
+
+        mLifecycle.addObserver(mController);
+        mLifecycle.handleLifecycleEvent(ON_STOP);
+
+        // enabled.
+        Settings.Secure.putInt(
+                mContentResolver,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+                1);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    private void setDaltonizerMode(int enabled, int mode) {
+        Settings.Secure.putInt(
+                mContentResolver,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+                enabled);
+        Settings.Secure.putInt(
+                mContentResolver,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
+                mode);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java b/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java
index a775731..4f8860e 100644
--- a/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java
@@ -127,8 +127,8 @@
         mFragment = spy(new TestCombinedBiometricProfileSettings(mContext));
         doReturn(mActivity).when(mFragment).getActivity();
         doReturn(mBiometricManager).when(mActivity).getSystemService(BiometricManager.class);
-        when(mBiometricManager.canAuthenticate(
-                BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+        when(mBiometricManager.canAuthenticate(anyInt(),
+                eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)))
                 .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
 
         ReflectionHelpers.setField(mFragment, "mDashboardFeatureProvider",
@@ -181,8 +181,8 @@
     public void testLaunchBiometricPrompt_onCreateFragment() {
         ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
         doNothing().when(mFragment).startActivityForResult(any(), anyInt());
-        when(mBiometricManager.canAuthenticate(
-                BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+        when(mBiometricManager.canAuthenticate(anyInt(),
+                eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)))
                 .thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
 
         mFragment.onAttach(mContext);
@@ -193,7 +193,7 @@
 
         Intent intent = intentArgumentCaptor.getValue();
         assertThat(intent.getComponent().getClassName()).isEqualTo(
-                ConfirmDeviceCredentialActivity.class.getName());
+                ConfirmDeviceCredentialActivity.InternalActivity.class.getName());
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java
index 4ef3223..29b2961 100644
--- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java
@@ -36,6 +36,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
 
 import android.content.Context;
 import android.content.Intent;
@@ -44,11 +45,13 @@
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.Flags;
 import android.hardware.biometrics.SensorProperties;
+import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
@@ -59,6 +62,7 @@
 import androidx.fragment.app.FragmentActivity;
 import androidx.fragment.app.FragmentManager;
 import androidx.fragment.app.FragmentTransaction;
+import androidx.preference.Preference;
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.settings.password.ChooseLockSettingsHelper;
@@ -87,6 +91,7 @@
 import org.robolectric.annotation.Config;
 
 import java.util.ArrayList;
+import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowSettingsPreferenceFragment.class, ShadowUtils.class, ShadowFragment.class,
@@ -120,6 +125,7 @@
             FingerprintManager.AuthenticationCallback.class);
 
     private FingerprintAuthenticateSidecar mFingerprintAuthenticateSidecar;
+    private FingerprintRemoveSidecar mFingerprintRemoveSidecar;
 
     @Before
     public void setUp() {
@@ -170,7 +176,7 @@
 
         Intent intent = intentArgumentCaptor.getValue();
         assertThat(intent.getComponent().getClassName()).isEqualTo(
-                ConfirmDeviceCredentialActivity.class.getName());
+                ConfirmDeviceCredentialActivity.InternalActivity.class.getName());
     }
 
     // Test the case when FingerprintAuthenticateSidecar receives an error callback from the
@@ -216,7 +222,7 @@
                 1,
                 UserHandle.of(GUEST_USER_ID).getIdentifier());
 
-        setUpFragment(false, GUEST_USER_ID, TYPE_POWER_BUTTON);
+        setUpFragment(false, GUEST_USER_ID, TYPE_POWER_BUTTON, 1);
 
         final RestrictedSwitchPreference requireScreenOnToAuthPreference = mFragment.findPreference(
                 KEY_REQUIRE_SCREEN_ON_TO_AUTH);
@@ -224,11 +230,15 @@
     }
 
     private void setUpFragment(boolean showChooseLock) {
-        setUpFragment(showChooseLock, PRIMARY_USER_ID, TYPE_UDFPS_OPTICAL);
+        setUpFragment(showChooseLock, PRIMARY_USER_ID, TYPE_UDFPS_OPTICAL, 1);
+    }
+
+    private void setUpFragment(boolean showChooseLock, int maxFingerprints) {
+        setUpFragment(showChooseLock, PRIMARY_USER_ID, TYPE_UDFPS_OPTICAL, maxFingerprints);
     }
 
     private void setUpFragment(boolean showChooseLock, int userId,
-            @FingerprintSensorProperties.SensorType int sensorType) {
+            @FingerprintSensorProperties.SensorType int sensorType, int maxFingerprints) {
         ShadowUserManager.getShadow().addProfile(new UserInfo(userId, "", 0));
 
         Intent intent = new Intent();
@@ -250,9 +260,13 @@
         doReturn(mFingerprintAuthenticateSidecar).when(fragmentManager).findFragmentByTag(
                 "authenticate_sidecar");
 
+        mFingerprintRemoveSidecar = new FingerprintRemoveSidecar();
+        doReturn(mFingerprintRemoveSidecar).when(fragmentManager).findFragmentByTag(
+                "removal_sidecar");
+
         doNothing().when(mFragment).startActivityForResult(any(Intent.class), anyInt());
 
-        setSensor(sensorType);
+        setSensor(sensorType, maxFingerprints);
 
         // Start fragment
         mFragment.onAttach(mContext);
@@ -269,12 +283,38 @@
         assertThat(mFragment.isVisible()).isTrue();
     }
 
-    private void setSensor(@FingerprintSensorProperties.SensorType int sensorType) {
+    @Ignore("b/353726774")
+    @Test
+    public void testAddButtonWorksAfterRemovalError() {
+        final Fingerprint fingerprint = new Fingerprint("Test", 0, 0);
+        doReturn(List.of(fingerprint)).when(mFingerprintManager).getEnrolledFingerprints(anyInt());
+        setUpFragment(false, 5);
+        shadowOf(Looper.getMainLooper()).idle();
+        final Preference addPref = mFragment.findPreference("key_fingerprint_add");
+        final FingerprintSettings.FingerprintPreference fpPref =
+                mFragment.findPreference("key_fingerprint_item_0");
+        assertThat(fpPref).isNotNull();
+        assertThat(addPref).isNotNull();
+        assertThat(addPref.isEnabled()).isTrue();
+
+        mFingerprintRemoveSidecar.setListener(mFragment.mRemovalListener);
+        mFragment.deleteFingerPrint(fingerprint);
+        verify(mFingerprintManager).remove(any(), anyInt(), any());
+        assertThat(addPref.isEnabled()).isFalse();
+
+        mFingerprintRemoveSidecar.mRemoveCallback.onRemovalError(fingerprint, 0, "failure");
+
+        shadowOf(Looper.getMainLooper()).idle();
+        assertThat(addPref.isEnabled()).isTrue();
+    }
+
+    private void setSensor(@FingerprintSensorProperties.SensorType int sensorType,
+            int maxFingerprints) {
         final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
         props.add(new FingerprintSensorPropertiesInternal(
                 0 /* sensorId */,
                 SensorProperties.STRENGTH_STRONG,
-                1 /* maxEnrollmentsPerUser */,
+                maxFingerprints /* maxEnrollmentsPerUser */,
                 new ArrayList<ComponentInfoInternal>(),
                 sensorType,
                 true /* resetLockoutRequiresHardwareAuthToken */));
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceTest.java
index 13e2a9d..be62414 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceTest.java
@@ -117,6 +117,7 @@
         assertThat(shareButton.getVisibility()).isEqualTo(View.VISIBLE);
         assertThat(shareButton.getDrawable()).isNotNull();
         assertThat(shareButton.hasOnClickListeners()).isTrue();
+        assertThat(shareButton.getContentDescription()).isNotNull();
         assertThat(divider).isNotNull();
         assertThat(divider.getVisibility()).isEqualTo(View.VISIBLE);
 
diff --git a/tests/robotests/src/com/android/settings/development/FreeformWindowsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/FreeformWindowsPreferenceControllerTest.java
index bd005b3..978380e 100644
--- a/tests/robotests/src/com/android/settings/development/FreeformWindowsPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/development/FreeformWindowsPreferenceControllerTest.java
@@ -16,8 +16,9 @@
 
 package com.android.settings.development;
 
-import static com.android.settings.development.FreeformWindowsPreferenceController
-        .SETTING_VALUE_OFF;
+import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
+
+import static com.android.settings.development.FreeformWindowsPreferenceController.SETTING_VALUE_OFF;
 import static com.android.settings.development.FreeformWindowsPreferenceController.SETTING_VALUE_ON;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -29,6 +30,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.provider.Settings;
 
 import androidx.fragment.app.FragmentActivity;
@@ -43,7 +45,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
 @RunWith(RobolectricTestRunner.class)
@@ -52,9 +53,10 @@
 })
 public class FreeformWindowsPreferenceControllerTest {
 
-    private static final String ENG_BUILD_TYPE = "eng";
-    private static final String USER_BUILD_TYPE = "user";
-
+    @Mock
+    Context mContext;
+    @Mock
+    private PackageManager mPackageManager;
     @Mock
     private SwitchPreference mPreference;
     @Mock
@@ -68,33 +70,33 @@
     @Mock
     private FragmentTransaction mTransaction;
 
-    private Context mContext;
     private FreeformWindowsPreferenceController mController;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mContext = RuntimeEnvironment.application;
         doReturn(mTransaction).when(mFragmentManager).beginTransaction();
         doReturn(mFragmentManager).when(mActivity).getSupportFragmentManager();
         doReturn(mActivity).when(mFragment).getActivity();
         mController = new FreeformWindowsPreferenceController(mContext, mFragment);
         when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
         mController.displayPreference(mScreen);
     }
 
     @Test
-    public void isAvailable_engBuild_shouldBeTrue() {
+    public void isAvailable_deviceHasFreeformWindowSystemFeature_returnsFalse() {
         mController = spy(mController);
-        doReturn(ENG_BUILD_TYPE).when(mController).getBuildType();
+        when(mPackageManager.hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)).thenReturn(true);
 
-        assertThat(mController.isAvailable()).isTrue();
+        assertThat(mController.isAvailable()).isFalse();
     }
 
     @Test
-    public void isAvailable_userBuild_shouldBeTrue() {
+    public void isAvailable_deviceDoesNotHaveFreeformWindowSystemFeature_returnsTrue() {
         mController = spy(mController);
-        doReturn(USER_BUILD_TYPE).when(mController).getBuildType();
+        when(mPackageManager.hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)).thenReturn(
+                false);
 
         assertThat(mController.isAvailable()).isTrue();
     }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java
index b949a3e..fdb075d 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java
@@ -389,6 +389,28 @@
         verify(mBatteryUsageProgressBarPref).setBottomSummary(expectedChargingString);
     }
 
+    @Test
+    public void updateBatteryStatus_dockDefend_chargingOnHold() {
+        var expected = "Charging on hold";
+        mBatteryInfo.isBatteryDefender = false;
+        when(mFactory.powerUsageFeatureProvider.isExtraDefend()).thenReturn(true);
+
+        mController.updateBatteryStatus(/* label= */ null, mBatteryInfo);
+
+        verify(mBatteryUsageProgressBarPref).setBottomSummary(expected);
+    }
+
+    @Test
+    public void updateBatteryStatus_batteryDefender_chargingOnHold() {
+        var expected = "Charging on hold";
+        mBatteryInfo.isBatteryDefender = true;
+        when(mFactory.powerUsageFeatureProvider.isExtraDefend()).thenReturn(false);
+
+        mController.updateBatteryStatus(/* label= */ null, mBatteryInfo);
+
+        verify(mBatteryUsageProgressBarPref).setBottomSummary(expected);
+    }
+
     private BatteryInfo arrangeUpdateBatteryStatusTestWithRemainingLabel(
             String remainingLabel,
             String statusLabel,
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiverTest.java
index af0cb91..63d44d0 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiverTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiverTest.java
@@ -28,9 +28,9 @@
 import android.content.pm.PackageManager;
 import android.os.BatteryManager;
 import android.os.SystemClock;
+import android.os.UserManager;
 import android.text.format.DateUtils;
 
-import com.android.settings.testutils.BatteryTestUtils;
 import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
@@ -49,6 +49,7 @@
     private FakeFeatureFactory mFakeFeatureFactory;
 
     @Mock private PackageManager mPackageManager;
+    @Mock private UserManager mUserManager;
 
     @Before
     public void setUp() {
@@ -57,6 +58,7 @@
         mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
         mBatteryUsageBroadcastReceiver = new BatteryUsageBroadcastReceiver();
         doReturn(mPackageManager).when(mContext).getPackageManager();
+        doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
         DatabaseUtils.getSharedPreferences(mContext).edit().clear().apply();
     }
 
@@ -69,7 +71,17 @@
 
     @Test
     public void onReceive_workProfile_doNothing() {
-        BatteryTestUtils.setWorkProfile(mContext);
+        doReturn(true).when(mUserManager).isManagedProfile();
+
+        mBatteryUsageBroadcastReceiver.onReceive(
+                mContext, new Intent(BatteryUsageBroadcastReceiver.ACTION_BATTERY_UNPLUGGING));
+
+        assertThat(mBatteryUsageBroadcastReceiver.mFetchBatteryUsageData).isFalse();
+    }
+
+    @Test
+    public void onReceive_privateProfile_doNothing() {
+        doReturn(true).when(mUserManager).isPrivateProfile();
 
         mBatteryUsageBroadcastReceiver.onReceive(
                 mContext, new Intent(BatteryUsageBroadcastReceiver.ACTION_BATTERY_UNPLUGGING));
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java
index 950f828..ac711a4 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java
@@ -19,12 +19,16 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.UserManager;
 
 import androidx.test.core.app.ApplicationProvider;
 
@@ -39,6 +43,8 @@
 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 java.time.Duration;
@@ -62,9 +68,14 @@
     private Context mContext;
     private BatteryUsageContentProvider mProvider;
 
+    @Mock
+    private UserManager mUserManager;
+
     @Before
     public void setUp() {
-        mContext = ApplicationProvider.getApplicationContext();
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
         mProvider = new BatteryUsageContentProvider();
         mProvider.attachInfo(mContext, /* info= */ null);
         BatteryTestUtils.setUpBatteryStateDatabase(mContext);
@@ -77,7 +88,13 @@
 
     @Test
     public void onCreate_withWorkProfileMode_returnsFalse() {
-        BatteryTestUtils.setWorkProfile(mContext);
+        doReturn(true).when(mUserManager).isManagedProfile();
+        assertThat(mProvider.onCreate()).isFalse();
+    }
+
+    @Test
+    public void onCreate_withPrivateProfileMode_returnsFalse() {
+        doReturn(true).when(mUserManager).isPrivateProfile();
         assertThat(mProvider.onCreate()).isFalse();
     }
 
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
index 704637f..f318a2b 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
@@ -18,6 +18,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 import static org.robolectric.Shadows.shadowOf;
 
 import android.app.AlarmManager;
@@ -26,6 +29,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.os.UserManager;
 
 import androidx.test.core.app.ApplicationProvider;
 
@@ -37,6 +41,8 @@
 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.Shadows;
 import org.robolectric.shadows.ShadowAlarmManager;
@@ -55,10 +61,15 @@
     private ShadowAlarmManager mShadowAlarmManager;
     private PeriodicJobManager mPeriodicJobManager;
 
+    @Mock
+    private UserManager mUserManager;
+
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
         TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
-        mContext = ApplicationProvider.getApplicationContext();
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
         mPeriodicJobManager = PeriodicJobManager.getInstance(mContext);
         mShadowAlarmManager = shadowOf(mContext.getSystemService(AlarmManager.class));
         mReceiver = new BootBroadcastReceiver();
@@ -78,7 +89,15 @@
 
     @Test
     public void onReceive_withWorkProfile_notRefreshesJob() {
-        BatteryTestUtils.setWorkProfile(mContext);
+        doReturn(true).when(mUserManager).isManagedProfile();
+        mReceiver.onReceive(mContext, new Intent(Intent.ACTION_BOOT_COMPLETED));
+
+        assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNull();
+    }
+
+    @Test
+    public void onReceive_withPrivateProfile_notRefreshesJob() {
+        doReturn(true).when(mUserManager).isPrivateProfile();
         mReceiver.onReceive(mContext, new Intent(Intent.ACTION_BOOT_COMPLETED));
 
         assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNull();
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java
index d89e61b..2fda277 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java
@@ -47,7 +47,6 @@
 
 import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity;
-import com.android.settings.testutils.BatteryTestUtils;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -451,6 +450,26 @@
     }
 
     @Test
+    public void getHistoryMap_withPrivateProfile_returnExpectedMap()
+            throws PackageManager.NameNotFoundException {
+        doReturn("com.fake.package").when(mContext).getPackageName();
+        doReturn(mMockContext)
+                .when(mContext)
+                .createPackageContextAsUser("com.fake.package", /* flags= */ 0, UserHandle.OWNER);
+        doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
+        doReturn(UserHandle.CURRENT).when(mContext).getUser();
+        doReturn(true).when(mUserManager).isPrivateProfile();
+        doReturn(UserHandle.SYSTEM).when(mUserManager).getProfileParent(UserHandle.CURRENT);
+
+        DatabaseUtils.sFakeSupplier = () -> getMatrixCursor();
+
+        final Map<Long, Map<String, BatteryHistEntry>> batteryHistMap =
+                DatabaseUtils.getHistoryMapSinceQueryTimestamp(mContext, 0);
+
+        assertThat(batteryHistMap).isEmpty();
+    }
+
+    @Test
     public void removeUsageSource_hasNoData() {
         DatabaseUtils.removeUsageSource(mContext);
         assertThat(
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiverTest.java
index d111de2..ea3c04c 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiverTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiverTest.java
@@ -18,11 +18,15 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 import static org.robolectric.Shadows.shadowOf;
 
 import android.app.AlarmManager;
 import android.content.Context;
 import android.content.Intent;
+import android.os.UserManager;
 
 import androidx.test.core.app.ApplicationProvider;
 
@@ -34,6 +38,8 @@
 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.shadows.ShadowAlarmManager;
 
@@ -53,12 +59,17 @@
     private PeriodicJobManager mPeriodicJobManager;
     private ShadowAlarmManager mShadowAlarmManager;
 
+    @Mock
+    private UserManager mUserManager;
+
     @Before
     public void setUp() {
-        mContext = ApplicationProvider.getApplicationContext();
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(ApplicationProvider.getApplicationContext());
         mPeriodicJobManager = PeriodicJobManager.getInstance(mContext);
         mShadowAlarmManager = shadowOf(mContext.getSystemService(AlarmManager.class));
         mReceiver = new PeriodicJobReceiver();
+        when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
 
         // Inserts fake data into database for testing.
         final BatteryStateDatabase database = BatteryTestUtils.setUpBatteryStateDatabase(mContext);
@@ -114,7 +125,14 @@
 
     @Test
     public void onReceive_inWorkProfileMode_notRefreshesJob() {
-        BatteryTestUtils.setWorkProfile(mContext);
+        doReturn(true).when(mUserManager).isManagedProfile();
+        mReceiver.onReceive(mContext, JOB_UPDATE_INTENT);
+        assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNull();
+    }
+
+    @Test
+    public void onReceive_inPrivateProfileMode_notRefreshesJob() {
+        doReturn(true).when(mUserManager).isPrivateProfile();
         mReceiver.onReceive(mContext, JOB_UPDATE_INTENT);
         assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNull();
     }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java
index d998106..0dd18c5 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java
@@ -18,7 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
 import android.content.Context;
+import android.os.UserManager;
 
 import androidx.test.core.app.ApplicationProvider;
 
@@ -27,6 +31,8 @@
 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 java.io.FileDescriptor;
@@ -46,11 +52,17 @@
     private StringWriter mStringWriter;
     private BugReportContentProvider mBugReportContentProvider;
 
+    @Mock
+    private UserManager mUserManager;
+
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
         mStringWriter = new StringWriter();
         mPrintWriter = new PrintWriter(mStringWriter);
-        mContext = ApplicationProvider.getApplicationContext();
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        doReturn(mContext).when(mContext).getApplicationContext();
+        doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
         mBugReportContentProvider = new BugReportContentProvider();
         mBugReportContentProvider.attachInfo(mContext, /* info= */ null);
         // Inserts fake data into database for testing.
@@ -77,7 +89,14 @@
 
     @Test
     public void dump_inWorkProfileMode_notDumpsBatteryUsageData() {
-        BatteryTestUtils.setWorkProfile(mContext);
+        doReturn(true).when(mUserManager).isManagedProfile();
+        mBugReportContentProvider.dump(FileDescriptor.out, mPrintWriter, new String[] {});
+        assertThat(mStringWriter.toString()).isEmpty();
+    }
+
+    @Test
+    public void dump_inPrivateProfileMode_notDumpsBatteryUsageData() {
+        doReturn(true).when(mUserManager).isPrivateProfile();
         mBugReportContentProvider.dump(FileDescriptor.out, mPrintWriter, new String[] {});
         assertThat(mStringWriter.toString()).isEmpty();
     }
diff --git a/tests/robotests/src/com/android/settings/notification/modes/CircularIconSetTest.java b/tests/robotests/src/com/android/settings/notification/modes/CircularIconSetTest.java
new file mode 100644
index 0000000..826c9df
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/CircularIconSetTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2024 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.modes;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+
+import com.google.common.base.Equivalence;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+
+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 java.util.List;
+import java.util.function.Function;
+
+@RunWith(RobolectricTestRunner.class)
+public class CircularIconSetTest {
+
+    @Mock private Function<Integer, Drawable> mDrawableLoader;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        CircularIconSet.sExecutorService = MoreExecutors.newDirectExecutorService();
+        when(mDrawableLoader.apply(anyInt())).thenReturn(new ColorDrawable(Color.BLACK));
+    }
+
+    @Test
+    public void hasSameItemsAs_sameItems_true() {
+        CircularIconSet<Integer> items1 = new CircularIconSet<>(ImmutableList.of(1, 2),
+                num -> new ColorDrawable(Color.BLUE));
+        CircularIconSet<Integer> items2 = new CircularIconSet<>(ImmutableList.of(1, 2),
+                num -> new ColorDrawable(Color.GREEN));
+
+        assertThat(items1.hasSameItemsAs(items2, null)).isTrue();
+    }
+
+    @Test
+    public void hasSameItemsAs_differentTypes_false() {
+        CircularIconSet<Integer> items1 = new CircularIconSet<>(ImmutableList.of(1, 2),
+                num -> new ColorDrawable(Color.BLUE));
+        CircularIconSet<String> items2 = new CircularIconSet<>(ImmutableList.of("a", "b"),
+                str -> new ColorDrawable(Color.GREEN));
+
+        assertThat(items1.hasSameItemsAs(items2, null)).isFalse();
+    }
+
+    @Test
+    public void hasSameItemsAs_differentItems_false() {
+        CircularIconSet<String> items1 = new CircularIconSet<>(ImmutableList.of("a", "b"),
+                str -> new ColorDrawable(Color.GREEN));
+        CircularIconSet<String> items2 = new CircularIconSet<>(ImmutableList.of("a", "b", "c"),
+                str -> new ColorDrawable(Color.GREEN));
+
+        assertThat(items1.hasSameItemsAs(items2, null)).isFalse();
+    }
+
+    private static class WrapperWithoutEquals<T> {
+        private final T mValue;
+        private WrapperWithoutEquals(T value) {
+            mValue = value;
+        }
+    }
+
+    @Test
+    public void hasSameItemsAs_withEquivalence_trueIfEquivalentItems() {
+        CircularIconSet<WrapperWithoutEquals<Integer>> items1 = new CircularIconSet<>(
+                ImmutableList.of(
+                        new WrapperWithoutEquals<>(1),
+                        new WrapperWithoutEquals<>(2)),
+                unused -> new ColorDrawable(Color.BLACK));
+        CircularIconSet<WrapperWithoutEquals<Integer>> items2 = new CircularIconSet<>(
+                ImmutableList.of(
+                        new WrapperWithoutEquals<>(1),
+                        new WrapperWithoutEquals<>(2)),
+                unused -> new ColorDrawable(Color.BLACK));
+        CircularIconSet<WrapperWithoutEquals<Integer>> items3 = new CircularIconSet<>(
+                ImmutableList.of(
+                        new WrapperWithoutEquals<>(2),
+                        new WrapperWithoutEquals<>(3)),
+                unused -> new ColorDrawable(Color.BLACK));
+        // Needs special equivalence, equals is not enough.
+        assertThat(items1.hasSameItemsAs(items2, null)).isFalse();
+
+        Equivalence<WrapperWithoutEquals<Integer>> equivalence = new Equivalence<>() {
+            @Override
+            protected boolean doEquivalent(WrapperWithoutEquals<Integer> a,
+                    WrapperWithoutEquals<Integer> b) {
+                return a.mValue.equals(b.mValue);
+            }
+
+            @Override
+            protected int doHash(WrapperWithoutEquals<Integer> t) {
+                return t.mValue;
+            }
+        };
+
+        assertThat(items1.hasSameItemsAs(items2, equivalence)).isTrue();
+        assertThat(items1.hasSameItemsAs(items3, equivalence)).isFalse();
+    }
+
+    @Test
+    public void hasSameItemsAs_withEquivalenceButDifferentTypes_falseAndNoClassCastExceptions() {
+        CircularIconSet<Integer> items1 = new CircularIconSet<>(ImmutableList.of(1, 2),
+                num -> new ColorDrawable(Color.BLUE));
+        CircularIconSet<String> items2 = new CircularIconSet<>(ImmutableList.of("one", "two"),
+                num -> new ColorDrawable(Color.GREEN));
+
+        Equivalence<String> stringEquivalence = new Equivalence<String>() {
+            @Override
+            protected boolean doEquivalent(String a, String b) {
+                return a.equals(b);
+            }
+
+            @Override
+            protected int doHash(String t) {
+                return t.hashCode();
+            }
+        };
+        Equivalence<Integer> integerEquivalence = new Equivalence<Integer>() {
+            @Override
+            protected boolean doEquivalent(Integer a, Integer b) {
+                return a.equals(b);
+            }
+
+            @Override
+            protected int doHash(Integer t) {
+                return t.hashCode();
+            }
+        };
+
+        assertThat(items1.hasSameItemsAs(items2, stringEquivalence)).isFalse();
+        assertThat(items2.hasSameItemsAs(items1, integerEquivalence)).isFalse();
+    }
+
+    @Test
+    public void getIcons_loadsAllIcons() {
+        CircularIconSet<Integer> set = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
+                mDrawableLoader);
+
+        List<ListenableFuture<Drawable>> iconFutures = set.getIcons();
+
+        assertThat(iconFutures).hasSize(3);
+        verify(mDrawableLoader).apply(1);
+        verify(mDrawableLoader).apply(2);
+        verify(mDrawableLoader).apply(3);
+    }
+
+    @Test
+    public void getIcons_loadsRequestedIcons() {
+        CircularIconSet<Integer> set = new CircularIconSet<>(ImmutableList.of(1, 2, 3, 4, 5),
+                mDrawableLoader);
+
+        List<ListenableFuture<Drawable>> iconFutures = set.getIcons(2);
+
+        assertThat(iconFutures).hasSize(2);
+        verify(mDrawableLoader).apply(1);
+        verify(mDrawableLoader).apply(2);
+        verifyNoMoreInteractions(mDrawableLoader);
+    }
+
+    @Test
+    public void getIcons_cachesIcons() {
+        CircularIconSet<Integer> set = new CircularIconSet<>(ImmutableList.of(1, 2, 3, 4, 5),
+                mDrawableLoader);
+
+        List<ListenableFuture<Drawable>> iconFutures = set.getIcons(2);
+        assertThat(iconFutures).hasSize(2);
+        verify(mDrawableLoader).apply(1);
+        verify(mDrawableLoader).apply(2);
+        verifyNoMoreInteractions(mDrawableLoader);
+
+        List<ListenableFuture<Drawable>> iconFuturesAgain = set.getIcons(3);
+        assertThat(iconFuturesAgain).hasSize(3);
+        verify(mDrawableLoader).apply(3);
+        verifyNoMoreInteractions(mDrawableLoader);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/CircularIconsPreferenceTest.java b/tests/robotests/src/com/android/settings/notification/modes/CircularIconsPreferenceTest.java
new file mode 100644
index 0000000..73754df
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/CircularIconsPreferenceTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2024 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.modes;
+
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.ColorDrawable;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.stream.IntStream;
+
+@RunWith(RobolectricTestRunner.class)
+public class CircularIconsPreferenceTest {
+
+    private static final int VIEW_WIDTH = 800;
+
+    private Context mContext;
+    private CircularIconsPreference mPreference;
+    private View mIconContainer;
+
+    private int mOneIconWidth;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        CircularIconSet.sExecutorService = MoreExecutors.newDirectExecutorService();
+        mPreference = new CircularIconsPreference(mContext, MoreExecutors.directExecutor());
+        // Tests should call bindAndMeasureViewHolder() so that icons can be added.
+
+        Resources res = mContext.getResources();
+        mOneIconWidth = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter)
+                + res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_margin_between);
+    }
+
+    private void bindAndMeasureViewHolder(int viewWidth) {
+        bindViewHolder();
+        measureViewHolder(viewWidth);
+    }
+
+    private void bindViewHolder() {
+        View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(),
+                null);
+        mIconContainer = checkNotNull(preferenceView.findViewById(R.id.circles_container));
+        PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(preferenceView);
+        mPreference.onBindViewHolder(holder);
+    }
+
+    private void measureViewHolder(int viewWidth) {
+        checkState(mIconContainer != null, "Call bindViewHolder() first!");
+        mIconContainer.measure(makeMeasureSpec(viewWidth, View.MeasureSpec.EXACTLY),
+                makeMeasureSpec(1000, View.MeasureSpec.EXACTLY));
+        mIconContainer.getViewTreeObserver().dispatchOnGlobalLayout();
+    }
+
+    @Test
+    public void displayIcons_loadsIcons() {
+        CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2),
+                ColorDrawable::new);
+
+        bindAndMeasureViewHolder(VIEW_WIDTH);
+        mPreference.displayIcons(iconSet);
+
+        assertThat(mPreference.getIcons()).hasSize(2);
+        assertThat(((ColorDrawable) mPreference.getIcons().get(0)).getColor()).isEqualTo(1);
+        assertThat(((ColorDrawable) mPreference.getIcons().get(1)).getColor()).isEqualTo(2);
+        assertThat(mPreference.getPlusText()).isNull();
+        assertThat(mIconContainer.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void displayIcons_noIcons_hidesRow() {
+        CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(),
+                ColorDrawable::new);
+
+        bindAndMeasureViewHolder(VIEW_WIDTH);
+        mPreference.displayIcons(iconSet);
+
+        assertThat(mIconContainer.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void displayIcons_exactlyMaxIcons_loadsAllIcons() throws Exception {
+        int width = 300;
+        int fittingCircles = width / mOneIconWidth;
+        CircularIconSet<Integer> iconSet = new CircularIconSet<>(
+                IntStream.range(0, fittingCircles).boxed().toList(),
+                ColorDrawable::new);
+
+        bindAndMeasureViewHolder(width);
+        mPreference.displayIcons(iconSet);
+
+        assertThat(mPreference.getIcons()).hasSize(fittingCircles);
+        assertThat(mPreference.getIcons()).containsExactlyElementsIn(
+                Futures.allAsList(iconSet.getIcons()).get()).inOrder();
+        assertThat(mPreference.getPlusText()).isNull();
+
+    }
+
+    @Test
+    public void displayIcons_tooManyIcons_loadsFirstNAndPlusIcon() throws Exception {
+        int width = 300;
+        int fittingCircles = width / mOneIconWidth;
+        CircularIconSet<Integer> iconSet = new CircularIconSet<>(
+                IntStream.range(0, fittingCircles + 5).boxed().toList(),
+                ColorDrawable::new);
+
+        bindAndMeasureViewHolder(width);
+        mPreference.displayIcons(iconSet);
+
+        // N-1 icons, plus (+6) text.
+        assertThat(mPreference.getIcons()).hasSize(fittingCircles - 1);
+        assertThat(mPreference.getIcons()).containsExactlyElementsIn(
+                        Futures.allAsList(iconSet.getIcons(fittingCircles - 1)).get())
+                .inOrder();
+        assertThat(mPreference.getPlusText()).isEqualTo("+6");
+    }
+
+    @Test
+    public void displayIcons_teenyTinySpace_showsPlusIcon_noCrash() {
+        CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2),
+                ColorDrawable::new);
+
+        bindAndMeasureViewHolder(1);
+        mPreference.displayIcons(iconSet);
+
+        assertThat(mPreference.getIcons()).isEmpty();
+        assertThat(mPreference.getPlusText()).isEqualTo("+2");
+    }
+
+    @Test
+    public void displayIcons_beforeBind_loadsIconsOnBindAndMeasure() {
+        CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
+                ColorDrawable::new);
+
+        mPreference.displayIcons(iconSet);
+        assertThat(mPreference.getIcons()).isEmpty(); // Hold...
+
+        bindViewHolder();
+        assertThat(mPreference.getIcons()).isEmpty(); // Hooooold...
+
+        measureViewHolder(VIEW_WIDTH);
+        assertThat(mPreference.getIcons()).hasSize(3);
+    }
+
+    @Test
+    public void displayIcons_beforeMeasure_loadsIconsOnMeasure() {
+        CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
+                ColorDrawable::new);
+        bindViewHolder();
+
+        mPreference.displayIcons(iconSet);
+        assertThat(mPreference.getIcons()).isEmpty();
+
+        measureViewHolder(VIEW_WIDTH);
+        assertThat(mPreference.getIcons()).hasSize(3);
+    }
+
+    @Test
+    public void displayIcons_calledAgain_reloadsIcons() {
+        CircularIconSet<Integer> threeIcons = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
+                ColorDrawable::new);
+        CircularIconSet<Integer> twoIcons = new CircularIconSet<>(ImmutableList.of(1, 2),
+                ColorDrawable::new);
+        CircularIconSet<Integer> fourIcons = new CircularIconSet<>(ImmutableList.of(1, 2, 3, 4),
+                ColorDrawable::new);
+        bindAndMeasureViewHolder(VIEW_WIDTH);
+
+        mPreference.displayIcons(threeIcons);
+        assertThat(mPreference.getIcons()).hasSize(3);
+        mPreference.displayIcons(twoIcons);
+        assertThat(mPreference.getIcons()).hasSize(2);
+        mPreference.displayIcons(fourIcons);
+        assertThat(mPreference.getIcons()).hasSize(4);
+    }
+
+    @Test
+    public void displayIcons_sameSet_doesNotReloadIcons() {
+        CircularIconSet<Integer> one = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
+                ColorDrawable::new);
+        CircularIconSet<Integer> same = Mockito.spy(new CircularIconSet<>(ImmutableList.of(1, 2, 3),
+                ColorDrawable::new));
+        when(same.getIcons()).thenThrow(new RuntimeException("Shouldn't be called!"));
+
+        bindAndMeasureViewHolder(VIEW_WIDTH);
+
+        mPreference.displayIcons(one);
+        mPreference.displayIcons(same); // if no exception, wasn't called.
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
index 6d12594..cc4d306 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
@@ -23,10 +23,13 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 import static org.robolectric.Shadows.shadowOf;
 
@@ -41,8 +44,11 @@
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.notification.ZenPolicy;
+import android.view.LayoutInflater;
+import android.view.View;
 
 import androidx.fragment.app.Fragment;
+import androidx.preference.PreferenceViewHolder;
 
 import com.android.settings.SettingsActivity;
 import com.android.settingslib.applications.ApplicationsState;
@@ -51,7 +57,9 @@
 import com.android.settingslib.notification.modes.TestModeBuilder;
 import com.android.settingslib.notification.modes.ZenMode;
 import com.android.settingslib.notification.modes.ZenModesBackend;
-import com.android.settingslib.widget.SelectorWithWidgetPreference;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.MoreExecutors;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -71,6 +79,7 @@
 public final class ZenModeAppsLinkPreferenceControllerTest {
 
     private ZenModeAppsLinkPreferenceController mController;
+    private CircularIconsPreference mPreference;
 
     private Context mContext;
     @Mock
@@ -91,10 +100,21 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
+        CircularIconSet.sExecutorService = MoreExecutors.newDirectExecutorService();
+        mPreference = new CircularIconsPreference(mContext, MoreExecutors.directExecutor());
+
         when(mApplicationsState.newSession(any(), any())).thenReturn(mSession);
         mController = new ZenModeAppsLinkPreferenceController(
                 mContext, "controller_key", mock(Fragment.class), mApplicationsState,
                 mZenModesBackend, mHelperBackend);
+
+        // Ensure the preference view is bound & measured (needed to add child ImageViews).
+        View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(),
+                null);
+        preferenceView.measure(View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY));
+        PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(preferenceView);
+        mPreference.onBindViewHolder(holder);
     }
 
     private AppEntry createAppEntry(String packageName, int userId) {
@@ -123,13 +143,11 @@
 
     @Test
     public void testUpdateSetsIntent() {
-        // Creates the preference
-        SelectorWithWidgetPreference preference = new SelectorWithWidgetPreference(mContext);
         // Create a zen mode that allows priority channels to breakthrough.
         ZenMode zenMode = createPriorityChannelsZenMode();
 
-        mController.updateState(preference, zenMode);
-        Intent launcherIntent = preference.getIntent();
+        mController.updateState(mPreference, zenMode);
+        Intent launcherIntent = mPreference.getIntent();
 
         assertThat(launcherIntent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
                 .isEqualTo("com.android.settings.notification.modes.ZenModeAppsFragment");
@@ -193,9 +211,20 @@
     }
 
     @Test
-    public void testUpdateTriggersRebuild() {
-        // Creates the preference
-        SelectorWithWidgetPreference preference = new SelectorWithWidgetPreference(mContext);
+    public void updateState_withPolicyAllowingNoChannels_doesNotLoadPriorityApps() {
+        ZenMode zenMode = new TestModeBuilder()
+                .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(false).build())
+                .build();
+
+        mController.updateState(mPreference, zenMode);
+
+        verifyNoMoreInteractions(mSession);
+        verify(mHelperBackend, never()).getPackagesBypassingDnd(anyInt(), anyBoolean());
+        assertThat(String.valueOf(mPreference.getSummary())).isEqualTo("None");
+    }
+
+    @Test
+    public void updateState_withPolicyAllowingPriorityChannels_triggersRebuild() {
         // Create a zen mode that allows priority channels to breakthrough.
         ZenMode zenMode = createPriorityChannelsZenMode();
 
@@ -209,21 +238,35 @@
 
         // Updates the preference with the zen mode. We expect that this causes the app session
         // to trigger a rebuild (and display a temporary text in the meantime).
-        mController.updateZenMode(preference, zenMode);
+        mController.updateZenMode(mPreference, zenMode);
         verify(mSession).rebuild(any(), any(), eq(false));
-        assertThat(String.valueOf(preference.getSummary())).isEqualTo("Calculating…");
+        assertThat(String.valueOf(mPreference.getSummary())).isEqualTo("Calculating…");
 
         // Manually triggers the callback that will happen on rebuild.
         mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
-        assertThat(String.valueOf(preference.getSummary())).isEqualTo("test can interrupt");
+        assertThat(String.valueOf(mPreference.getSummary())).isEqualTo("test can interrupt");
+    }
+
+    @Test
+    public void updateState_withPolicyAllowingPriorityChannels_loadsIcons() {
+        ZenMode zenMode = createPriorityChannelsZenMode();
+
+        mController.updateState(mPreference, zenMode);
+        when(mHelperBackend.getPackagesBypassingDnd(anyInt(), anyBoolean()))
+                .thenReturn(ImmutableList.of("test1", "test2"));
+        ArrayList<ApplicationsState.AppEntry> appEntries = new ArrayList<>();
+        appEntries.add(createAppEntry("test1", mContext.getUserId()));
+        appEntries.add(createAppEntry("test2", mContext.getUserId()));
+        mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
+
+        assertThat(mPreference.getIcons()).hasSize(2);
     }
 
     @Test
     public void testOnPackageListChangedTriggersRebuild() {
-        SelectorWithWidgetPreference preference = new SelectorWithWidgetPreference(mContext);
         // Create a zen mode that allows priority channels to breakthrough.
         ZenMode zenMode = createPriorityChannelsZenMode();
-        mController.updateState(preference, zenMode);
+        mController.updateState(mPreference, zenMode);
         verify(mSession).rebuild(any(), any(), eq(false));
 
         mController.mAppSessionCallbacks.onPackageListChanged();
@@ -232,10 +275,9 @@
 
     @Test
     public void testOnLoadEntriesCompletedTriggersRebuild() {
-        SelectorWithWidgetPreference preference = new SelectorWithWidgetPreference(mContext);
         // Create a zen mode that allows priority channels to breakthrough.
         ZenMode zenMode = createPriorityChannelsZenMode();
-        mController.updateState(preference, zenMode);
+        mController.updateState(mPreference, zenMode);
         verify(mSession).rebuild(any(), any(), eq(false));
 
         mController.mAppSessionCallbacks.onLoadEntriesCompleted();
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
index 772bd1d..7fa4f9f 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.settings.notification.modes;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
@@ -24,10 +25,10 @@
 import android.content.Context;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
-
-import androidx.preference.Preference;
+import android.service.notification.ZenPolicy;
 
 import com.android.settingslib.notification.modes.TestModeBuilder;
+import com.android.settingslib.notification.modes.ZenMode;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -61,10 +62,40 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_MODES_UI)
-    public void testHasSummary() {
-        Preference pref = mock(Preference.class);
+    public void updateState_loadsSummary() {
+        CircularIconsPreference pref = mock(CircularIconsPreference.class);
         mController.updateZenMode(pref, TestModeBuilder.EXAMPLE);
+
         verify(pref).setSummary(any());
     }
+
+    @Test
+    public void updateState_loadsIcons() {
+        CircularIconsPreference pref = mock(CircularIconsPreference.class);
+        ZenMode mode = new TestModeBuilder()
+                .setZenPolicy(new ZenPolicy.Builder()
+                        .disallowAllSounds()
+                        .allowMedia(true)
+                        .allowSystem(true)
+                        .allowReminders(true)
+                        .build())
+                .build();
+
+        mController.updateState(pref, mode);
+
+        verify(pref).displayIcons(argThat(iconSet -> iconSet.size() == 3));
+    }
+
+    @Test
+    public void updateState_loadsAllIcons() {
+        CircularIconsPreference pref = mock(CircularIconsPreference.class);
+        ZenMode mode = new TestModeBuilder()
+                .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
+                .build();
+
+        mController.updateState(pref, mode);
+
+        verify(pref).displayIcons(argThat(iconSet ->
+                iconSet.size() == ZenModeSummaryHelper.OTHER_SOUND_CATEGORIES.size()));
+    }
 }
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
index dd97d6e..8ec980d 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
@@ -16,18 +16,47 @@
 
 package com.android.settings.notification.modes;
 
+import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
+
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.Flags;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.content.Context;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
+import android.service.notification.ConversationChannelWrapper;
+import android.service.notification.ZenPolicy;
+import android.view.LayoutInflater;
+import android.view.View;
 
-import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
 
+import com.android.settings.notification.modes.ZenHelperBackend.Contact;
+import com.android.settingslib.notification.ConversationIconFactory;
 import com.android.settingslib.notification.modes.TestModeBuilder;
+import com.android.settingslib.notification.modes.ZenMode;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.MoreExecutors;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -35,36 +64,160 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
+import java.util.Collection;
+
+@EnableFlags(Flags.FLAG_MODES_UI)
 @RunWith(RobolectricTestRunner.class)
 public final class ZenModePeopleLinkPreferenceControllerTest {
 
     private ZenModePeopleLinkPreferenceController mController;
+    private CircularIconsPreference mPreference;
 
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     private Context mContext;
-    @Mock
-    private ZenHelperBackend mHelperBackend;
+    @Mock private ZenHelperBackend mHelperBackend;
+    @Mock private ConversationIconFactory mConversationIconFactory;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-
         mContext = RuntimeEnvironment.application;
+        CircularIconSet.sExecutorService = MoreExecutors.newDirectExecutorService();
+        mPreference = new CircularIconsPreference(mContext, MoreExecutors.directExecutor());
+
+        // Ensure the preference view is bound & measured (needed to add icons).
+        View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(),
+                null);
+        preferenceView.measure(View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY));
+        PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(preferenceView);
+        mPreference.onBindViewHolder(holder);
 
         mController = new ZenModePeopleLinkPreferenceController(
-                mContext, "something", mHelperBackend);
+                mContext, "something", mHelperBackend, mConversationIconFactory);
+
+        setUpContacts(ImmutableList.of(), ImmutableList.of());
+        setUpImportantConversations(ImmutableList.of());
+
+        when(mHelperBackend.getContactPhoto(any())).then(
+                (Answer<Drawable>) invocationOnMock -> photoOf(invocationOnMock.getArgument(0)));
+        when(mConversationIconFactory.getConversationDrawable((ShortcutInfo) any(), any(), anyInt(),
+                anyBoolean())).thenReturn(new ColorDrawable(Color.BLACK));
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_MODES_UI)
-    public void testHasSummary() {
-        Preference pref = mock(Preference.class);
-        mController.updateZenMode(pref, TestModeBuilder.EXAMPLE);
-        verify(pref).setSummary(any());
+    public void updateState_setsSummary() {
+        mController.updateState(mPreference, TestModeBuilder.EXAMPLE);
+
+        assertThat(mPreference.getSummary()).isNotNull();
+        assertThat(mPreference.getSummary().toString()).isNotEmpty();
+    }
+
+    @Test
+    public void updateState_starredCallsNoMessages_displaysStarredContacts() {
+        setUpContacts(ImmutableList.of(1, 2, 3, 4), ImmutableList.of(2, 3));
+        ZenMode mode = new TestModeBuilder()
+                .setZenPolicy(new ZenPolicy.Builder()
+                        .allowCalls(PEOPLE_TYPE_STARRED)
+                        .allowMessages(PEOPLE_TYPE_NONE)
+                        .build())
+                .build();
+
+        mController.updateState(mPreference, mode);
+
+        assertThat(mPreference.getIcons()).hasSize(2);
+        assertThat(mPreference.getIcons().stream()
+                .map(ColorDrawable.class::cast)
+                .map(d -> d.getColor()).toList())
+                .containsExactly(2, 3).inOrder();
+    }
+
+    @Test
+    public void updateState_starredCallsContactMessages_displaysAllContacts() {
+        setUpContacts(ImmutableList.of(1, 2, 3, 4), ImmutableList.of(2, 3));
+        ZenMode mode = new TestModeBuilder()
+                .setZenPolicy(new ZenPolicy.Builder()
+                        .allowCalls(PEOPLE_TYPE_STARRED)
+                        .allowMessages(PEOPLE_TYPE_CONTACTS)
+                        .build())
+                .build();
+
+        mController.updateState(mPreference, mode);
+
+        assertThat(mPreference.getIcons()).hasSize(4);
+        assertThat(mPreference.getIcons().stream()
+                .map(ColorDrawable.class::cast)
+                .map(d -> d.getColor()).toList())
+                .containsExactly(1, 2, 3, 4).inOrder();
+    }
+
+    @Test
+    public void updateState_anyoneCallsContactMessages_displaysAnyonePlaceholder() {
+        setUpContacts(ImmutableList.of(1, 2, 3, 4), ImmutableList.of(2, 3));
+        ZenMode mode = new TestModeBuilder()
+                .setZenPolicy(new ZenPolicy.Builder()
+                        .allowCalls(PEOPLE_TYPE_ANYONE)
+                        .allowMessages(PEOPLE_TYPE_CONTACTS)
+                        .build())
+                .build();
+
+        mController.updateState(mPreference, mode);
+
+        assertThat(mPreference.getIcons()).hasSize(1);
+        verify(mHelperBackend, never()).getContactPhoto(any());
+    }
+
+    @Test
+    public void updateState_noContactsButImportantConversations_displaysConversations() {
+        setUpContacts(ImmutableList.of(), ImmutableList.of());
+        setUpImportantConversations(ImmutableList.of(1, 2, 3));
+        ZenMode mode = new TestModeBuilder()
+                .setZenPolicy(new ZenPolicy.Builder()
+                        .allowCalls(PEOPLE_TYPE_CONTACTS)
+                        .allowMessages(PEOPLE_TYPE_CONTACTS)
+                        .allowConversations(CONVERSATION_SENDERS_IMPORTANT)
+                        .build())
+                .build();
+
+        mController.updateState(mPreference, mode);
+
+        assertThat(mPreference.getIcons()).hasSize(3);
+        verify(mConversationIconFactory, times(3)).getConversationDrawable((ShortcutInfo) any(),
+                any(), anyInt(), anyBoolean());
+    }
+
+    private void setUpContacts(Collection<Integer> allIds, Collection<Integer> starredIds) {
+        when(mHelperBackend.getAllContacts()).thenReturn(ImmutableList.copyOf(
+                allIds.stream()
+                        .map(id -> new Contact(id, "#" + id, Uri.parse("photo://" + id)))
+                        .toList()));
+
+        when(mHelperBackend.getStarredContacts()).thenReturn(ImmutableList.copyOf(
+                starredIds.stream()
+                        .map(id -> new Contact(id, "#" + id, Uri.parse("photo://" + id)))
+                        .toList()));
+    }
+
+    private void setUpImportantConversations(Collection<Integer> ids) {
+        when(mHelperBackend.getImportantConversations()).thenReturn(ImmutableList.copyOf(
+                ids.stream()
+                        .map(id -> {
+                            ConversationChannelWrapper channel = new ConversationChannelWrapper();
+                            channel.setNotificationChannel(
+                                    new NotificationChannel(id.toString(), id.toString(),
+                                            NotificationManager.IMPORTANCE_DEFAULT));
+                            return channel;
+                        })
+                        .toList()));
+    }
+
+    private static ColorDrawable photoOf(Contact contact) {
+        return new ColorDrawable((int) contact.id());
     }
 }
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java
index 944d432..64de141 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java
@@ -35,13 +35,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Flags;
 import android.content.Context;
-import android.database.Cursor;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.notification.ZenPolicy;
@@ -56,6 +54,8 @@
 import com.android.settingslib.notification.modes.ZenModesBackend;
 import com.android.settingslib.widget.SelectorWithWidgetPreference;
 
+import com.google.common.collect.ImmutableList;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -105,22 +105,11 @@
         mPreferenceScreen.addPreference(mCallsPrefCategory);
         mPreferenceScreen.addPreference(mMessagesPrefCategory);
 
-        Cursor cursor = mock(Cursor.class);
-        when(cursor.getCount()).thenReturn(1);
-        when(mHelperBackend.queryAllContactsData()).thenReturn(cursor);
-    }
-
-    // Makes a preference with the provided key and whether it's a checkbox with
-    // mSelectorClickListener as the onClickListener set.
-    private SelectorWithWidgetPreference makePreference(
-            String key, boolean isCheckbox, boolean isMessages) {
-        final SelectorWithWidgetPreference pref =
-                new SelectorWithWidgetPreference(mContext, isCheckbox);
-        pref.setKey(key);
-        pref.setOnClickListener(
-                isMessages ? mMessagesController.mSelectorClickListener
-                        : mCallsController.mSelectorClickListener);
-        return pref;
+        when(mHelperBackend.getStarredContacts()).thenReturn(ImmutableList.of());
+        when(mHelperBackend.getAllContacts()).thenReturn(
+                ImmutableList.of(new ZenHelperBackend.Contact(1, "The only contact", null)));
+        when(mHelperBackend.getAllContactsCount()).thenReturn(1);
+        when(mHelperBackend.getImportantConversations()).thenReturn(ImmutableList.of());
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java
index fc3cef1..61ca4d8 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java
@@ -22,11 +22,15 @@
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 
-import static com.android.settings.notification.modes.ZenModeSetTriggerLinkPreferenceController.AUTOMATIC_TRIGGER_PREF_KEY;
+import static com.android.settings.notification.modes.ZenModeSetTriggerLinkPreferenceController.ADD_TRIGGER_KEY;
+import static com.android.settings.notification.modes.ZenModeSetTriggerLinkPreferenceController.AUTOMATIC_TRIGGER_KEY;
+import static com.android.settings.notification.modes.ZenModeSetTriggerLinkPreferenceControllerTest.CharSequenceTruth.assertThat;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -35,6 +39,7 @@
 import android.app.Flags;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.platform.test.annotations.EnableFlags;
@@ -42,7 +47,11 @@
 import android.service.notification.SystemZenRules;
 import android.service.notification.ZenModeConfig;
 
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
 import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.settings.R;
@@ -53,6 +62,9 @@
 import com.android.settingslib.notification.modes.ZenMode;
 import com.android.settingslib.notification.modes.ZenModesBackend;
 
+import com.google.common.truth.StringSubject;
+import com.google.common.truth.Truth;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -60,6 +72,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
 import org.robolectric.RobolectricTestRunner;
 
 import java.util.Calendar;
@@ -74,32 +87,47 @@
     private ZenModesBackend mBackend;
     private Context mContext;
 
-    private PrimarySwitchPreference mPreference;
-
     @Mock
     private PackageManager mPm;
     @Mock
     private ConfigurationActivityHelper mConfigurationActivityHelper;
 
-    @Mock
     private PreferenceCategory mPrefCategory;
+    private PrimarySwitchPreference mConfigPreference;
+    private Preference mAddPreference;
+
     @Mock
     private DashboardFragment mFragment;
 
-    private ZenModeSetTriggerLinkPreferenceController mPrefController;
+    private ZenModeSetTriggerLinkPreferenceController mController;
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mContext = ApplicationProvider.getApplicationContext();
 
-        mPrefController = new ZenModeSetTriggerLinkPreferenceController(mContext,
-                "zen_automatic_trigger_category", mFragment, mBackend,
-                mConfigurationActivityHelper,
-                mock(ZenServiceListing.class));
-        mPreference = new PrimarySwitchPreference(mContext);
+        PreferenceManager preferenceManager = new PreferenceManager(mContext);
+        PreferenceScreen preferenceScreen = preferenceManager.inflateFromResource(mContext,
+                R.xml.modes_rule_settings, null);
 
-        when(mPrefCategory.findPreference(AUTOMATIC_TRIGGER_PREF_KEY)).thenReturn(mPreference);
+        mController = new ZenModeSetTriggerLinkPreferenceController(mContext,
+                "zen_automatic_trigger_category", mFragment, mBackend, mPm,
+                mConfigurationActivityHelper, mock(ZenServiceListing.class));
+
+        mPrefCategory = preferenceScreen.findPreference("zen_automatic_trigger_category");
+        mConfigPreference = checkNotNull(mPrefCategory).findPreference(AUTOMATIC_TRIGGER_KEY);
+        mAddPreference = checkNotNull(mPrefCategory).findPreference(ADD_TRIGGER_KEY);
+
+        when(mPm.getApplicationInfo(any(), anyInt())).then(
+                (Answer<ApplicationInfo>) invocationOnMock -> {
+                    ApplicationInfo appInfo = new ApplicationInfo();
+                    appInfo.packageName = invocationOnMock.getArgument(0);
+                    appInfo.labelRes = 1; // Whatever, but != 0 so that loadLabel calls PM.getText()
+                    return appInfo;
+                });
+        when(mPm.getText(any(), anyInt(), any())).then(
+                (Answer<CharSequence>) invocationOnMock ->
+                        "App named " + invocationOnMock.getArgument(0));
     }
 
     @Test
@@ -110,37 +138,37 @@
                 .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                 .build(), true);
 
-        mPrefController.updateZenMode(mPrefCategory, manualMode);
-        assertThat(mPrefController.isAvailable()).isFalse();
+        mController.updateZenMode(mPrefCategory, manualMode);
+        assertThat(mController.isAvailable()).isFalse();
 
         // should be available for other modes
-        mPrefController.updateZenMode(mPrefCategory, TestModeBuilder.EXAMPLE);
-        assertThat(mPrefController.isAvailable()).isTrue();
+        mController.updateZenMode(mPrefCategory, TestModeBuilder.EXAMPLE);
+        assertThat(mController.isAvailable()).isTrue();
     }
 
     @Test
-    public void testUpdateState() {
+    public void updateState_switchCheckedIfRuleEnabled() {
         ZenMode zenMode = new TestModeBuilder().setEnabled(false).build();
 
         // Update preference controller with a zen mode that is not enabled
-        mPrefController.updateZenMode(mPrefCategory, zenMode);
-        assertThat(mPreference.getCheckedState()).isFalse();
+        mController.updateZenMode(mPrefCategory, zenMode);
+        assertThat(mConfigPreference.getCheckedState()).isFalse();
 
         // Now with the rule enabled
         zenMode.getRule().setEnabled(true);
-        mPrefController.updateZenMode(mPrefCategory, zenMode);
-        assertThat(mPreference.getCheckedState()).isTrue();
+        mController.updateZenMode(mPrefCategory, zenMode);
+        assertThat(mConfigPreference.getCheckedState()).isTrue();
     }
 
     @Test
-    public void testOnPreferenceChange() {
+    public void onPreferenceChange_updatesMode() {
         ZenMode zenMode = new TestModeBuilder().setEnabled(false).build();
 
         // start with disabled rule
-        mPrefController.updateZenMode(mPrefCategory, zenMode);
+        mController.updateZenMode(mPrefCategory, zenMode);
 
-        // then update the preference to be checked
-        mPrefController.mSwitchChangeListener.onPreferenceChange(mPreference, true);
+        // then flip the switch
+        mConfigPreference.callChangeListener(true);
 
         // verify the backend got asked to update the mode to be enabled
         ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
@@ -149,7 +177,7 @@
     }
 
     @Test
-    public void testRuleLink_calendar() {
+    public void updateState_scheduleCalendarRule() {
         ZenModeConfig.EventInfo eventInfo = new ZenModeConfig.EventInfo();
         eventInfo.calendarId = 1L;
         eventInfo.calName = "My events";
@@ -159,23 +187,21 @@
                 .setType(TYPE_SCHEDULE_CALENDAR)
                 .setTriggerDescription("My events")
                 .build();
-        mPrefController.updateZenMode(mPrefCategory, mode);
 
-        assertThat(mPreference.getTitle()).isNotNull();
-        assertThat(mPreference.getTitle().toString()).isEqualTo(
-                mContext.getString(R.string.zen_mode_set_calendar_link));
-        assertThat(mPreference.getSummary()).isNotNull();
-        assertThat(mPreference.getSummary().toString()).isEqualTo(
-                mode.getRule().getTriggerDescription());
-        assertThat(mPreference.getIcon()).isNull();
+        mController.updateState(mPrefCategory, mode);
 
+        assertThat(mAddPreference.isVisible()).isFalse();
+        assertThat(mConfigPreference.isVisible()).isTrue();
+        assertThat(mConfigPreference.getTitle()).isEqualTo("Calendar events");
+        assertThat(mConfigPreference.getSummary()).isEqualTo("My events");
         // Destination as written into the intent by SubSettingLauncher
-        assertThat(mPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
+        assertThat(
+                mConfigPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
                 .isEqualTo(ZenModeSetCalendarFragment.class.getName());
     }
 
     @Test
-    public void testRuleLink_schedule() {
+    public void updateState_scheduleTimeRule() {
         ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo();
         scheduleInfo.days = new int[]{Calendar.MONDAY, Calendar.TUESDAY, Calendar.THURSDAY};
         scheduleInfo.startHour = 1;
@@ -186,44 +212,41 @@
                 .setType(TYPE_SCHEDULE_TIME)
                 .setTriggerDescription("some schedule")
                 .build();
-        mPrefController.updateZenMode(mPrefCategory, mode);
 
-        assertThat(mPreference.getTitle()).isNotNull();
-        assertThat(mPreference.getTitle().toString()).isEqualTo(
-                mContext.getString(R.string.zen_mode_set_schedule_link));
-        assertThat(mPreference.getSummary()).isNotNull();
-        assertThat(mPreference.getSummary().toString()).isEqualTo(
-                mode.getRule().getTriggerDescription());
-        assertThat(mPreference.getIcon()).isNull();
+        mController.updateState(mPrefCategory, mode);
 
+        assertThat(mAddPreference.isVisible()).isFalse();
+        assertThat(mConfigPreference.isVisible()).isTrue();
+        assertThat(mConfigPreference.getTitle()).isEqualTo("1:00 AM - 3:00 PM");
+        assertThat(mConfigPreference.getSummary()).isEqualTo("Mon - Tue, Thu");
         // Destination as written into the intent by SubSettingLauncher
-        assertThat(mPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
+        assertThat(
+                mConfigPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
                 .isEqualTo(ZenModeSetScheduleFragment.class.getName());
     }
 
     @Test
-    public void testRuleLink_manual() {
+    public void updateState_customManualRule() {
         ZenMode mode = new TestModeBuilder()
                 .setConditionId(ZenModeConfig.toCustomManualConditionId())
                 .setPackage(SystemZenRules.PACKAGE_ANDROID)
                 .setType(TYPE_OTHER)
                 .setTriggerDescription("Will not be shown")
                 .build();
-        mPrefController.updateZenMode(mPrefCategory, mode);
 
-        assertThat(mPreference.getTitle()).isNotNull();
-        assertThat(mPreference.getTitle().toString()).isEqualTo(
+        mController.updateState(mPrefCategory, mode);
+
+        assertThat(mConfigPreference.isVisible()).isFalse();
+        assertThat(mAddPreference.isVisible()).isTrue();
+        assertThat(mAddPreference.getTitle()).isEqualTo(
                 mContext.getString(R.string.zen_mode_select_schedule));
-        assertThat(mPreference.getIcon()).isNotNull();
-        assertThat(mPreference.getSummary()).isNotNull();
-        assertThat(mPreference.getSummary().toString()).isEqualTo("");
-
-        // Set up a click listener to open the dialog.
-        assertThat(mPreference.getOnPreferenceClickListener()).isNotNull();
+        assertThat(mAddPreference.getSummary()).isNull();
+        // Sets up a click listener to open the dialog.
+        assertThat(mAddPreference.getOnPreferenceClickListener()).isNotNull();
     }
 
     @Test
-    public void testRuleLink_appWithConfigActivity_linksToConfigActivity() {
+    public void updateState_appWithConfigActivity_showsLinkToConfigActivity() {
         ZenMode mode = new TestModeBuilder()
                 .setPackage("some.package")
                 .setTriggerDescription("When The Music's Over")
@@ -232,28 +255,62 @@
         when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any()))
                 .thenReturn(configurationIntent);
 
-        mPrefController.updateZenMode(mPrefCategory, mode);
+        mController.updateState(mPrefCategory, mode);
 
-        assertThat(mPreference.getTitle()).isNotNull();
-        assertThat(mPreference.getTitle().toString()).isEqualTo(
-                mContext.getString(R.string.zen_mode_configuration_link_title));
-        assertThat(mPreference.getSummary()).isNotNull();
-        assertThat(mPreference.getSummary().toString()).isEqualTo("When The Music's Over");
-        assertThat(mPreference.getIntent()).isEqualTo(configurationIntent);
+        assertThat(mConfigPreference.isVisible()).isTrue();
+        assertThat(mConfigPreference.getTitle()).isEqualTo("Linked to app");
+        assertThat(mConfigPreference.getSummary()).isEqualTo("When The Music's Over");
+        assertThat(mConfigPreference.getIntent()).isEqualTo(configurationIntent);
     }
 
     @Test
-    public void testRuleLink_appWithoutConfigActivity_hidden() {
+    public void updateState_appWithoutConfigActivity_showsWithoutLinkToConfigActivity() {
         ZenMode mode = new TestModeBuilder()
                 .setPackage("some.package")
-                .setTriggerDescription("Will not be shown :(")
+                .setTriggerDescription("When the saints go marching in")
                 .build();
         when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any()))
                 .thenReturn(null);
 
-        mPrefController.updateZenMode(mPrefCategory, mode);
+        mController.updateState(mPrefCategory, mode);
 
-        assertThat(mPrefCategory.isVisible()).isFalse();
+        assertThat(mConfigPreference.isVisible()).isTrue();
+        assertThat(mConfigPreference.getTitle()).isEqualTo("Linked to app");
+        assertThat(mConfigPreference.getSummary()).isEqualTo("When the saints go marching in");
+        assertThat(mConfigPreference.getIntent()).isNull();
+    }
+
+    @Test
+    public void updateState_appWithoutTriggerDescriptionWithConfigActivity_showsAppNameInSummary() {
+        ZenMode mode = new TestModeBuilder()
+                .setPackage("some.package")
+                .build();
+        Intent configurationIntent = new Intent("configure the mode");
+        when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any()))
+                .thenReturn(configurationIntent);
+        when(mPm.getText(any(), anyInt(), any())).thenReturn("The App Name");
+
+        mController.updateState(mPrefCategory, mode);
+
+        assertThat(mConfigPreference.isVisible()).isTrue();
+        assertThat(mConfigPreference.getTitle()).isEqualTo("Linked to app");
+        assertThat(mConfigPreference.getSummary()).isEqualTo("Info and settings in The App Name");
+    }
+
+    @Test
+    public void updateState_appWithoutTriggerDescriptionNorConfigActivity_showsAppNameInSummary() {
+        ZenMode mode = new TestModeBuilder()
+                .setPackage("some.package")
+                .build();
+        when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any()))
+                .thenReturn(null);
+        when(mPm.getText(any(), anyInt(), any())).thenReturn("The App Name");
+
+        mController.updateState(mPrefCategory, mode);
+
+        assertThat(mConfigPreference.isVisible()).isTrue();
+        assertThat(mConfigPreference.getTitle()).isEqualTo("Linked to app");
+        assertThat(mConfigPreference.getSummary()).isEqualTo("Managed by The App Name");
     }
 
     @Test
@@ -264,7 +321,7 @@
                 .setType(TYPE_OTHER)
                 .setTriggerDescription("")
                 .build();
-        mPrefController.updateZenMode(mPrefCategory, originalMode);
+        mController.updateZenMode(mPrefCategory, originalMode);
 
         ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo();
         scheduleInfo.days = new int[] { Calendar.MONDAY };
@@ -272,7 +329,7 @@
         scheduleInfo.endHour = 15;
         Uri scheduleUri = ZenModeConfig.toScheduleConditionId(scheduleInfo);
 
-        mPrefController.mOnScheduleOptionListener.onScheduleSelected(scheduleUri);
+        mController.mOnScheduleOptionListener.onScheduleSelected(scheduleUri);
 
         // verify the backend got asked to update the mode to be schedule-based.
         ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
@@ -284,4 +341,17 @@
         assertThat(updatedMode.getRule().getOwner()).isEqualTo(
                 ZenModeConfig.getScheduleConditionProvider());
     }
+
+    static class CharSequenceTruth {
+        /**
+         * Shortcut version of {@link Truth#assertThat(String)} suitable for {@link CharSequence}.
+         * {@link CharSequence} doesn't necessarily provide a good {@code equals()} implementation;
+         * however we don't care about formatting here, so we want to assert on the resulting
+         * string (without needing to worry that {@code assertThat(x.getText().toString())} can
+         * throw if the text is null).
+         */
+        static StringSubject assertThat(@Nullable CharSequence actual) {
+            return Truth.assertThat((String) (actual != null ? actual.toString() : null));
+        }
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java
index 672a0d7..a7257f5 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java
@@ -89,31 +89,38 @@
 
     @Test
     public void getPeopleSummary_noOne() {
-        ZenMode zenMode = new TestModeBuilder()
-                .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
-                .build();
+        ZenPolicy policy = new ZenPolicy.Builder().disallowAllSounds().build();
 
-        assertThat(mSummaryHelper.getPeopleSummary(zenMode)).isEqualTo("No one can interrupt");
+        assertThat(mSummaryHelper.getPeopleSummary(policy)).isEqualTo("No one can interrupt");
     }
 
     @Test
     public void getPeopleSummary_some() {
-        ZenMode zenMode = new TestModeBuilder()
-                .setZenPolicy(new ZenPolicy.Builder().allowCalls(PEOPLE_TYPE_CONTACTS).build())
+        ZenPolicy policy = new ZenPolicy.Builder().allowCalls(PEOPLE_TYPE_CONTACTS).build();
+
+        assertThat(mSummaryHelper.getPeopleSummary(policy)).isEqualTo("Some people can interrupt");
+    }
+
+    @Test
+    public void getPeopleSummary_onlyRepeatCallers() {
+        ZenPolicy policy = new ZenPolicy.Builder()
+                .disallowAllSounds()
+                .allowRepeatCallers(true)
                 .build();
 
-        assertThat(mSummaryHelper.getPeopleSummary(zenMode)).isEqualTo("Some people can interrupt");
+        assertThat(mSummaryHelper.getPeopleSummary(policy)).isEqualTo(
+                "Repeat callers can interrupt");
     }
 
     @Test
     public void getPeopleSummary_all() {
-        ZenMode zenMode = new TestModeBuilder()
-                .setZenPolicy(new ZenPolicy.Builder().allowCalls(PEOPLE_TYPE_ANYONE).
-                        allowConversations(CONVERSATION_SENDERS_ANYONE)
-                        .allowMessages(PEOPLE_TYPE_ANYONE).build())
+        ZenPolicy policy = new ZenPolicy.Builder()
+                .allowCalls(PEOPLE_TYPE_ANYONE)
+                .allowConversations(CONVERSATION_SENDERS_ANYONE)
+                .allowMessages(PEOPLE_TYPE_ANYONE)
                 .build();
 
-        assertThat(mSummaryHelper.getPeopleSummary(zenMode)).isEqualTo("All people can interrupt");
+        assertThat(mSummaryHelper.getPeopleSummary(policy)).isEqualTo("All people can interrupt");
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java
index dea936d..1e81ec0 100644
--- a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java
+++ b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java
@@ -36,19 +36,25 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.robolectric.RuntimeEnvironment.application;
+import static org.robolectric.Shadows.shadowOf;
 
+import android.annotation.ColorInt;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManager.PasswordComplexity;
 import android.app.admin.PasswordMetrics;
 import android.app.admin.PasswordPolicy;
 import android.content.Intent;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.view.View;
 import android.widget.CheckBox;
 import android.widget.TextView;
 
+import androidx.recyclerview.widget.RecyclerView;
+
 import com.android.internal.widget.LockscreenCredential;
 import com.android.settings.R;
+import com.android.settings.Utils;
 import com.android.settings.password.ChooseLockPassword.ChooseLockPasswordFragment;
 import com.android.settings.password.ChooseLockPassword.IntentBuilder;
 import com.android.settings.testutils.shadow.SettingsShadowResources;
@@ -515,6 +521,52 @@
         assertThat(pinAutoConfirmOption.isChecked()).isFalse();
     }
 
+    @Test
+    public void defaultMessage_shouldBeInTextColorPrimary() {
+        final ChooseLockPassword passwordActivity = setupActivityWithPinTypeAndDefaultPolicy();
+
+        final ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(passwordActivity);
+        final ScrollToParentEditText passwordEntry = passwordActivity.findViewById(R.id.password_entry);
+        final RecyclerView view = (RecyclerView) fragment.getPasswordRequirementsView();
+        @ColorInt final int textColorPrimary = Utils.getColorAttrDefaultColor(passwordActivity,
+                android.R.attr.textColorPrimary);
+
+        passwordEntry.setText("");
+        fragment.updateUi();
+        shadowOf(Looper.getMainLooper()).idle();
+        TextView textView = (TextView)view.getLayoutManager().findViewByPosition(0);
+
+        assertThat(textView.getCurrentTextColor()).isEqualTo(textColorPrimary);
+    }
+
+    @Test
+    public void errorMessage_shouldBeColorError() {
+        final ChooseLockPassword passwordActivity = setupActivityWithPinTypeAndDefaultPolicy();
+
+        final ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(passwordActivity);
+        final ScrollToParentEditText passwordEntry = passwordActivity.findViewById(R.id.password_entry);
+        final RecyclerView view = (RecyclerView) fragment.getPasswordRequirementsView();
+        @ColorInt final int textColorPrimary = Utils.getColorAttrDefaultColor(passwordActivity,
+                android.R.attr.textColorPrimary);
+        @ColorInt final int colorError = Utils.getColorAttrDefaultColor(passwordActivity,
+                android.R.attr.colorError);
+
+        passwordEntry.setText("");
+        fragment.updateUi();
+        shadowOf(Looper.getMainLooper()).idle();
+        TextView textView = (TextView)view.getLayoutManager().findViewByPosition(0);
+
+        assertThat(textView.getCurrentTextColor()).isEqualTo(textColorPrimary);
+
+        // Password must be fewer than 17 digits, so this should give an error.
+        passwordEntry.setText("a".repeat(17));
+        fragment.updateUi();
+        shadowOf(Looper.getMainLooper()).idle();
+        textView = (TextView)view.getLayoutManager().findViewByPosition(0);
+
+        assertThat(textView.getCurrentTextColor()).isEqualTo(colorError);
+    }
+
     private ChooseLockPassword setupActivityWithPinTypeAndDefaultPolicy() {
         PasswordPolicy policy = new PasswordPolicy();
         policy.quality = PASSWORD_QUALITY_UNSPECIFIED;
@@ -543,7 +595,7 @@
                         .setForFingerprint(addFingerprintExtra)
                         .build());
         ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(passwordActivity);
-        return Shadows.shadowOf(((GlifLayout) fragment.getView()).getIcon());
+        return shadowOf(((GlifLayout) fragment.getView()).getIcon());
     }
 
     private void assertPasswordValidationResult(PasswordMetrics minMetrics,
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowCrossProfileApps.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowCrossProfileApps.java
index 64a5f11..c52fe2f 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowCrossProfileApps.java
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowCrossProfileApps.java
@@ -19,9 +19,8 @@
 import android.Manifest;
 import android.content.Context;
 import android.content.pm.CrossProfileApps;
-import android.content.pm.ICrossProfileApps;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageInfo;
 
 import androidx.annotation.NonNull;
 
@@ -35,15 +34,7 @@
 @Implements(CrossProfileApps.class)
 public class ShadowCrossProfileApps extends org.robolectric.shadows.ShadowCrossProfileApps {
     private static final Set<String> configurableInteractAcrossProfilePackages = new HashSet<>();
-    private Context mContext;
-    private PackageManager mPackageManager;
 
-    @Implementation
-    protected void __constructor__(Context context, ICrossProfileApps service) {
-        super.__constructor__(context, service);
-        this.mContext = context;
-        this.mPackageManager = context.getPackageManager();
-    }
     public void addCrossProfilePackage(String packageName) {
         configurableInteractAcrossProfilePackages.add(packageName);
     }
@@ -57,7 +48,9 @@
     protected boolean canUserAttemptToConfigureInteractAcrossProfiles(@NonNull String packageName) {
         PackageInfo packageInfo;
         try {
-            packageInfo = mPackageManager.getPackageInfo(packageName, /* flags= */ 0);
+            packageInfo = getContext().getPackageManager().getPackageInfo(
+                packageName,
+                /* flags= */ 0);
         } catch (PackageManager.NameNotFoundException e) {
             return false;
         }
diff --git a/tests/unit/src/com/android/settings/development/bluetooth/BluetoothStackLogPreferenceControllerTest.java b/tests/unit/src/com/android/settings/development/bluetooth/BluetoothStackLogPreferenceControllerTest.java
index ab1f469..2aa10bb 100644
--- a/tests/unit/src/com/android/settings/development/bluetooth/BluetoothStackLogPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/development/bluetooth/BluetoothStackLogPreferenceControllerTest.java
@@ -16,13 +16,9 @@
 
 package com.android.settings.development.bluetooth;
 
-import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY;
-import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY_PERSIST;
-import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BTSTACK_LOG_MODE_VERBOSE_INDEX;
-import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BTSTACK_LOG_MODE_DEBUG_INDEX;
-import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BTSTACK_LOG_MODE_INFO_INDEX;
-import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BTSTACK_LOG_MODE_WARN_INDEX;
-import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BTSTACK_LOG_MODE_ERROR_INDEX;
+import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BT_LOG_LEVEL_DEFAULT_INDEX;
+import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BT_LOG_LEVEL_PROP;
+import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BT_LOG_LEVEL_PROP_PERSIST;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -37,18 +33,21 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
 @RunWith(AndroidJUnit4.class)
-@Ignore("b/339148064")
 public class BluetoothStackLogPreferenceControllerTest {
-    private static final String TAG = "BluetoothStackLogPreferenceControllerTest";
+    private static final String COM_ANDROID_SETTINGS = "com.android.settings";
+    private static final String TYPE_ARRAY = "array";
 
-    @Mock private Context mContext;
+    private static final String XML_DEFINED_PREFERENCE_KEY = "bt_stack_log_level";
+    private static final String XML_DEFINED_ENTRIES_RESOURCE = "bt_stack_log_level_entries";
+    private static final String XML_DEFINED_VALUES_RESOURCE = "bt_stack_log_level_values";
+
+    private static final String PROPERTY_CLEARED = "";
+
+    private Context mContext;
 
     private ListPreference mPreference;
     private PreferenceManager mPreferenceManager;
@@ -61,7 +60,6 @@
 
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
         mContext = ApplicationProvider.getApplicationContext();
 
         if (Looper.myLooper() == null) {
@@ -71,12 +69,11 @@
         mPreferenceManager = new PreferenceManager(mContext);
         mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext);
         mPreference = new ListPreference(mContext);
-
         mController = new BluetoothStackLogPreferenceController(mContext);
 
         mPreference.setKey(mController.getPreferenceKey());
-        mPreference.setEntries(com.android.settings.R.array.bt_stack_log_level_entries);
-        mPreference.setEntryValues(com.android.settings.R.array.bt_stack_log_level_values);
+        mPreference.setEntries(getStringArrayResourceId(XML_DEFINED_ENTRIES_RESOURCE));
+        mPreference.setEntryValues(getStringArrayResourceId(XML_DEFINED_VALUES_RESOURCE));
 
         mPreferenceScreen.addPreference(mPreference);
         mController.displayPreference(mPreferenceScreen);
@@ -86,134 +83,109 @@
     }
 
     /**
-     * Test that default log level is set to INFO
+     * Get the resource ID associated with a resource name
+     *
+     * This looks up the resource id by name using our device's context. This way, we can avoid
+     * hardcoding a resource ID or value from the R class which may not match the resource IDs on
+     * the device under test.
+     *
+     * Usage: int valuesResId = getStringArrayResource("bt_stack_log_level_values");
+     * Usage: int entriesResId = getStringArrayResource("bt_stack_log_level_entries");
+     *
+     * @param res - The resource name to look up
+     * @return The integer resource ID corresponding to the given resource name
      */
-    @Test
-    public void verifyDefaultState_enablesDefaultLogLevelEntriesAndValuesSameSize() {
-        mController.onPreferenceChange(mPreference, mController.getDefaultModeIndex());
-        assertThat(mPreference.getValue().toString()).isEqualTo(mListValues
-                        [BTSTACK_LOG_MODE_INFO_INDEX].toString());
-        assertThat(mPreference.getSummary().toString()).isEqualTo(mListEntries
-                        [BTSTACK_LOG_MODE_INFO_INDEX].toString());
+    public int getStringArrayResourceId(String res) {
+        return mContext.getResources().getIdentifier(res, TYPE_ARRAY, COM_ANDROID_SETTINGS);
     }
 
     /**
-     * Test that log level is changed to VERBOSE when VERBOSE is selected
+     * Test that, for each possible value a user can select, our controller properly handles the
+     * value to update the underlying system property _and_ set the UI entry to the proper value.
      */
     @Test
-    public void onPreferenceChanged_enableBluetoothStackVerboseLogLevel() {
-        mController.onPreferenceChange(mPreference, mListValues[BTSTACK_LOG_MODE_VERBOSE_INDEX]
-                        .toString());
+    public void onPreferenceChange_withEachValue_uiSetProperlyAndAllValuesWrittenToProperties() {
+        for (int index = 0; index < mListValues.length; index++) {
+            String value = mListValues[index].toString();
+            String entry = mListEntries[index].toString();
 
-        final String persistedLogLevel = SystemProperties.get(
-                        BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY_PERSIST);
-        final String logLevel = SystemProperties.get(BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY);
-        assertThat(persistedLogLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_VERBOSE_INDEX]
-                        .toString());
-        assertThat(logLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_VERBOSE_INDEX].toString());
+            mController.onPreferenceChange(mPreference, value);
 
-        assertThat(mPreference.getValue().toString()).isEqualTo(mListValues
-                        [BTSTACK_LOG_MODE_VERBOSE_INDEX].toString());
-        assertThat(mPreference.getSummary().toString()).isEqualTo(mListEntries
-                        [BTSTACK_LOG_MODE_VERBOSE_INDEX].toString());
+            final String persistedLogLevel = SystemProperties.get(BT_LOG_LEVEL_PROP_PERSIST);
+            final String logLevel = SystemProperties.get(BT_LOG_LEVEL_PROP);
+            final String currentValue = mPreference.getValue().toString();
+            final String currentEntry = mPreference.getEntry().toString();
+            final String currentSummary = mPreference.getSummary().toString();
+            final int currentIndex = mPreference.findIndexOfValue(currentValue);
+
+            assertThat(persistedLogLevel).isEqualTo(value);
+            assertThat(logLevel).isEqualTo(value);
+            assertThat(currentIndex).isEqualTo(index);
+            assertThat(currentValue).isEqualTo(value);
+            assertThat(currentEntry).isEqualTo(entry);
+            assertThat(currentSummary).isEqualTo(entry);
+        }
     }
 
     /**
-     * Test that log level is changed to DEBUG when DEBUG is selected
+     * Test that, for each possible log tag log level value, our controller properly handles the
+     * value to set the UI entry to the proper value.
      */
     @Test
-    public void onPreferenceChanged_enableBluetoothStackDebugLogLevel() {
-        mController.onPreferenceChange(mPreference, mListValues[BTSTACK_LOG_MODE_DEBUG_INDEX]
-                        .toString());
+    public void updateState_withEachValue_uiSetProperly() {
+        for (int index = 0; index < mListValues.length; index++) {
+            String value = mListValues[index].toString();
+            String entry = mListEntries[index].toString();
 
-        final String persistedLogLevel = SystemProperties.get(
-                BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY_PERSIST);
-        final String logLevel = SystemProperties.get(BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY);
-        assertThat(persistedLogLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_DEBUG_INDEX]
-                        .toString());
-        assertThat(logLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_DEBUG_INDEX].toString());
+            SystemProperties.set(BT_LOG_LEVEL_PROP_PERSIST, value);
+            SystemProperties.set(BT_LOG_LEVEL_PROP, value);
 
-        assertThat(mPreference.getValue().toString()).isEqualTo(mListValues
-                        [BTSTACK_LOG_MODE_DEBUG_INDEX].toString());
-        assertThat(mPreference.getSummary().toString()).isEqualTo(mListEntries
-                        [BTSTACK_LOG_MODE_DEBUG_INDEX].toString());
+            mController.updateState(mPreference);
+
+            final String currentValue = mPreference.getValue().toString();
+            final String currentEntry = mPreference.getEntry().toString();
+            final String currentSummary = mPreference.getSummary().toString();
+            final int currentIndex = mPreference.findIndexOfValue(currentValue);
+
+            assertThat(currentIndex).isEqualTo(index);
+            assertThat(currentValue).isEqualTo(value);
+            assertThat(currentEntry).isEqualTo(entry);
+            assertThat(currentSummary).isEqualTo(entry);
+        }
     }
 
     /**
-     * Test that log level is changed to INFO when INFO is selected
+     * Test that our controller reverts the log level back to a missing/default value when we're
+     * notified that Developer Options has been disabled.
      */
     @Test
-    public void onPreferenceChanged_enableBluetoothStackInfoLogLevel() {
-        mController.onPreferenceChange(mPreference, mListValues[BTSTACK_LOG_MODE_INFO_INDEX]
-                        .toString());
+    public void onDeveloperOptionsSwitchDisabled_preferenceSetToDefault() {
+        mController.onDeveloperOptionsSwitchDisabled();
 
-        final String persistedLogLevel = SystemProperties.get(
-                BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY_PERSIST);
-        final String logLevel = SystemProperties.get(BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY);
-        assertThat(persistedLogLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_INFO_INDEX]
-                        .toString());
-        assertThat(logLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_INFO_INDEX].toString());
+        final String defaultEntry = mListEntries[BT_LOG_LEVEL_DEFAULT_INDEX].toString();
+        final String defaultValue = mListValues[BT_LOG_LEVEL_DEFAULT_INDEX].toString();
 
-        assertThat(mPreference.getValue().toString()).isEqualTo(mListValues
-                        [BTSTACK_LOG_MODE_INFO_INDEX].toString());
-        assertThat(mPreference.getSummary().toString()).isEqualTo(mListEntries
-                        [BTSTACK_LOG_MODE_INFO_INDEX].toString());
+        final String persistedLogLevel = SystemProperties.get(BT_LOG_LEVEL_PROP_PERSIST);
+        final String logLevel = SystemProperties.get(BT_LOG_LEVEL_PROP);
+        final String currentValue = mPreference.getValue().toString();
+        final String currentEntry = mPreference.getEntry().toString();
+        final String currentSummary = mPreference.getSummary().toString();
+        final int currentIndex = mPreference.findIndexOfValue(currentValue);
+
+        assertThat(persistedLogLevel).isEqualTo(PROPERTY_CLEARED);
+        assertThat(logLevel).isEqualTo(PROPERTY_CLEARED);
+        assertThat(currentIndex).isEqualTo(BT_LOG_LEVEL_DEFAULT_INDEX);
+        assertThat(currentValue).isEqualTo(defaultValue);
+        assertThat(currentEntry).isEqualTo(defaultEntry);
+        assertThat(currentSummary).isEqualTo(defaultEntry);
     }
 
     /**
-     * Test that log level is changed to WARN when WARN is selected
+     * Test that our preference key returned by our controller matches the one defined in the XML
+     * definition.
      */
     @Test
-    public void onPreferenceChanged_enableBluetoothStackWarnLogLevel() {
-        mController.onPreferenceChange(mPreference, mListValues[BTSTACK_LOG_MODE_WARN_INDEX]
-                        .toString());
-
-        final String persistedLogLevel = SystemProperties.get(
-                BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY_PERSIST);
-        final String logLevel = SystemProperties.get(BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY);
-        assertThat(persistedLogLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_WARN_INDEX]
-                        .toString());
-        assertThat(logLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_WARN_INDEX].toString());
-
-        assertThat(mPreference.getValue().toString()).isEqualTo(mListValues
-
-                        [BTSTACK_LOG_MODE_WARN_INDEX].toString());
-        assertThat(mPreference.getSummary().toString()).isEqualTo(mListEntries
-                        [BTSTACK_LOG_MODE_WARN_INDEX].toString());
-    }
-
-    /**
-     * Test that log level is changed to ERROR when ERROR is selected
-     */
-    @Test
-    public void onPreferenceChanged_enableBluetoothStackErrorLogLevel() {
-        mController.onPreferenceChange(mPreference, mListValues[BTSTACK_LOG_MODE_ERROR_INDEX]
-                        .toString());
-
-        final String persistedLogLevel = SystemProperties.get(
-                BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY_PERSIST);
-        final String logLevel = SystemProperties.get(BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY);
-        assertThat(persistedLogLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_ERROR_INDEX]
-                        .toString());
-        assertThat(logLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_ERROR_INDEX].toString());
-
-        assertThat(mPreference.getValue().toString()).isEqualTo(mListValues
-                        [BTSTACK_LOG_MODE_ERROR_INDEX].toString());
-        assertThat(mPreference.getSummary().toString()).isEqualTo(mListEntries
-                        [BTSTACK_LOG_MODE_ERROR_INDEX].toString());
-    }
-
-    /**
-     * Test that preference is disabled when developer options is disabled
-     * Log level is also reset to default
-     */
-    @Test
-    public void onDeveloperOptionsDisabled_shouldDisablePreference() {
-        mController.onDeveloperOptionsDisabled();
-        assertThat(mPreference.isEnabled()).isFalse();
-        assertThat(mPreference.getValue().toString()).isEqualTo(mListValues[mController
-                .getDefaultModeIndex()].toString());
-        assertThat(mPreference.getSummary().toString()).isEqualTo(mListEntries[mController
-                .getDefaultModeIndex()].toString());
+    public void getPreferenceKey_matchesXmlDefinedPreferenceKey() {
+        assertThat(mController.getPreferenceKey()).isEqualTo(XML_DEFINED_PREFERENCE_KEY);
     }
 }