Merge "Refresh templates when fp removal fails" into main
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/preference_circular_icons_plus_item_background.xml b/res/drawable/preference_circular_icons_plus_item_background.xml
index 8200a9b..faaa1fd 100644
--- a/res/drawable/preference_circular_icons_plus_item_background.xml
+++ b/res/drawable/preference_circular_icons_plus_item_background.xml
@@ -22,7 +22,4 @@
android:width="@dimen/zen_mode_circular_icon_diameter"
android:height="@dimen/zen_mode_circular_icon_diameter" />
<solid android:color="?androidprv:attr/materialColorSecondaryContainer" />
- <!-- TODO: b/346551087 - Include border (or not) according to final design
- <stroke android:width="1dp" android:color="?androidprv:attr/materialColorOnSecondaryContainer" />
- -->
</shape>
\ No newline at end of file
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/values/dimens.xml b/res/values/dimens.xml
index 2bb8fc2..68eb99b 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -513,4 +513,5 @@
<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 ec2ff55..f994fbc 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8873,7 +8873,7 @@
<!-- Title for Polite Notifications setting [CHAR LIMIT=45]-->
<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 it\u2019s volume and minimize alerts for up to 2 minutes. Calls, alarms, and priority conversations aren\u2019t affected.
+ <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>
@@ -9424,6 +9424,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 -->
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/notification/modes/CircularIconSet.java b/src/com/android/settings/notification/modes/CircularIconSet.java
index 18f82d9..eb7289a 100644
--- a/src/com/android/settings/notification/modes/CircularIconSet.java
+++ b/src/com/android/settings/notification/modes/CircularIconSet.java
@@ -20,8 +20,10 @@
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;
@@ -29,6 +31,7 @@
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;
@@ -67,8 +70,25 @@
return MoreObjects.toStringHelper(this).add("items", mItems).toString();
}
- boolean hasSameItemsAs(CircularIconSet<?> other) {
- return other != null && this.mItems.equals(other.mItems);
+ @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() {
diff --git a/src/com/android/settings/notification/modes/CircularIconsPreference.java b/src/com/android/settings/notification/modes/CircularIconsPreference.java
index 5e8f720..1ce3476 100644
--- a/src/com/android/settings/notification/modes/CircularIconsPreference.java
+++ b/src/com/android/settings/notification/modes/CircularIconsPreference.java
@@ -41,6 +41,7 @@
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;
@@ -105,8 +106,12 @@
}
}
- void displayIcons(CircularIconSet<?> iconSet) {
- if (mIconSet != null && mIconSet.hasSameItemsAs(iconSet)) {
+ <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;
@@ -189,7 +194,6 @@
}
// ... plus 0/1 TextViews at the end.
if (extraItems > 0 && !(getLastChild(mIconContainer) instanceof TextView)) {
- // TODO: b/346551087 - Check TODO in preference_circular_icons_plus_item_background
TextView plusView = (TextView) inflater.inflate(
R.layout.preference_circular_icons_plus_item, mIconContainer, false);
mIconContainer.addView(plusView);
diff --git a/src/com/android/settings/notification/modes/IconUtil.java b/src/com/android/settings/notification/modes/IconUtil.java
index 07e1440..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) {
@@ -87,9 +99,10 @@
/**
* 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 container" color combination.
+ * with the "material secondary" color combination.
*/
- static Drawable makeSoundIcon(@NonNull Context context, @DrawableRes int iconResId) {
+ static Drawable makeCircularIconPreferenceItem(@NonNull Context context,
+ @DrawableRes int iconResId) {
return composeIconCircle(
Utils.getColorAttr(context,
com.android.internal.R.attr.materialColorSecondaryContainer),
@@ -102,6 +115,53 @@
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());
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 9bff2bb..962e016 100644
--- a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
@@ -40,6 +40,7 @@
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;
@@ -47,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
@@ -137,7 +139,8 @@
mPreference.setSummary(mSummaryHelper.getAppsSummary(mZenMode, apps));
mPreference.displayIcons(new CircularIconSet<>(apps,
- app -> Utils.getBadgedIcon(mContext, app.info)));
+ app -> Utils.getBadgedIcon(mContext, app.info)),
+ APP_ENTRY_EQUIVALENCE);
}
@VisibleForTesting
@@ -158,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/ZenModeOtherLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
index fce48af..d7bd517 100644
--- a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
@@ -29,6 +29,7 @@
import androidx.annotation.NonNull;
import androidx.preference.Preference;
+import com.android.settings.R;
import com.android.settingslib.notification.modes.ZenMode;
import com.google.common.collect.ImmutableList;
@@ -41,19 +42,13 @@
*/
class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceController {
- // TODO: b/346551087 - Use proper icons
private static final ImmutableMap</* @PriorityCategory */ Integer, /* @DrawableRes */ Integer>
PRIORITIES_TO_ICONS = ImmutableMap.of(
- PRIORITY_CATEGORY_ALARMS,
- com.android.internal.R.drawable.ic_audio_alarm,
- PRIORITY_CATEGORY_MEDIA,
- com.android.settings.R.drawable.ic_media_stream,
- PRIORITY_CATEGORY_SYSTEM,
- com.android.settings.R.drawable.ic_settings_keyboards,
- PRIORITY_CATEGORY_REMINDERS,
- com.android.internal.R.drawable.ic_popup_reminder,
- PRIORITY_CATEGORY_EVENTS,
- com.android.internal.R.drawable.ic_zen_mode_type_schedule_calendar);
+ 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;
@@ -87,6 +82,6 @@
}
}
return new CircularIconSet<>(icons.build(),
- iconResId -> IconUtil.makeSoundIcon(mContext, iconResId));
+ 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 2a61418..762cdd5 100644
--- a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
@@ -17,25 +17,67 @@
package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
+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.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.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
@@ -50,8 +92,104 @@
ZenSubSettingLauncher.forModeFragment(mContext, ZenModePeopleFragment.class,
zenMode.getId(), 0).toIntent());
- preference.setSummary(mSummaryHelper.getPeopleSummary(zenMode));
- // TODO: b/346551087 - Show people icons
- ((CircularIconsPreference) preference).displayIcons(CircularIconSet.EMPTY);
+ 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/ZenModeSummaryHelper.java b/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
index dd3a400..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,7 @@
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;
@@ -365,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),
@@ -389,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..b4f13e8 100644
--- a/src/com/android/settings/password/ChooseLockGeneric.java
+++ b/src/com/android/settings/password/ChooseLockGeneric.java
@@ -777,6 +777,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/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/tests/robotests/src/com/android/settings/notification/modes/CircularIconSetTest.java b/tests/robotests/src/com/android/settings/notification/modes/CircularIconSetTest.java
index 9e85243..826c9df 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/CircularIconSetTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/CircularIconSetTest.java
@@ -27,6 +27,7 @@
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;
@@ -54,33 +55,111 @@
}
@Test
- public void equals_sameItems_true() {
+ 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)).isTrue();
+ assertThat(items1.hasSameItemsAs(items2, null)).isTrue();
}
@Test
- public void equals_differentTypes_false() {
+ 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)).isFalse();
+ assertThat(items1.hasSameItemsAs(items2, null)).isFalse();
}
@Test
- public void equals_differentItems_false() {
+ 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)).isFalse();
+ 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
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 0db26c3..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,17 +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.ArgumentMatchers.eq;
-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.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;
@@ -34,39 +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() {
- CircularIconsPreference pref = mock(CircularIconsPreference.class);
+ public void updateState_setsSummary() {
+ mController.updateState(mPreference, TestModeBuilder.EXAMPLE);
- mController.updateZenMode(pref, TestModeBuilder.EXAMPLE);
+ assertThat(mPreference.getSummary()).isNotNull();
+ assertThat(mPreference.getSummary().toString()).isNotEmpty();
+ }
- verify(pref).setSummary(any());
- verify(pref).displayIcons(eq(CircularIconSet.EMPTY));
+ @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/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