Merge "Update Settings Page for Avalanche Suppression/Adaptive Notifications" into main
diff --git a/aconfig/settings_connecteddevice_flag_declarations.aconfig b/aconfig/settings_connecteddevice_flag_declarations.aconfig
index 2d66c30..7942ccd 100644
--- a/aconfig/settings_connecteddevice_flag_declarations.aconfig
+++ b/aconfig/settings_connecteddevice_flag_declarations.aconfig
@@ -14,3 +14,14 @@
description: "Gates whether to require an auth challenge for changing USB preferences"
bug: "317367746"
}
+
+
+flag {
+ name: "enable_bonded_bluetooth_device_searchable"
+ namespace: "pixel_cross_device_control"
+ description: "Set bonded bluetooth devices under connected devices page to be searchable by Settings search."
+ bug: "319056077"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/res/color/modes_set_schedule_text_color.xml b/res/color/modes_set_schedule_text_color.xml
new file mode 100644
index 0000000..5ceb68e
--- /dev/null
+++ b/res/color/modes_set_schedule_text_color.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ 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.
+ -->
+
+<selector
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- when checked, background color will be accent color -->
+ <item
+ android:state_checked="true"
+ android:color="?android:attr/textColorPrimaryInverse" />
+ <!-- when unchecked, background color will be transparent -->
+ <item
+ android:state_checked="false"
+ android:color="?android:attr/colorAccent" />
+</selector>
diff --git a/res/drawable/ic_zen_mode_action_change_icon.xml b/res/drawable/ic_zen_mode_action_change_icon.xml
new file mode 100644
index 0000000..4cf4167
--- /dev/null
+++ b/res/drawable/ic_zen_mode_action_change_icon.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="M620,440Q645,440 662.5,422.5Q680,405 680,380Q680,355 662.5,337.5Q645,320 620,320Q595,320 577.5,337.5Q560,355 560,380Q560,405 577.5,422.5Q595,440 620,440ZM340,440Q365,440 382.5,422.5Q400,405 400,380Q400,355 382.5,337.5Q365,320 340,320Q315,320 297.5,337.5Q280,355 280,380Q280,405 297.5,422.5Q315,440 340,440ZM480,700Q548,700 603.5,661.5Q659,623 684,560L276,560Q301,623 356.5,661.5Q412,700 480,700ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/modes_schedule_day_toggle.xml b/res/drawable/modes_schedule_day_toggle.xml
new file mode 100644
index 0000000..c09f597
--- /dev/null
+++ b/res/drawable/modes_schedule_day_toggle.xml
@@ -0,0 +1,47 @@
+<!--
+ ~ 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.
+ -->
+
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item
+ android:top="2dp"
+ android:bottom="2dp"
+ android:left="2dp"
+ android:right="2dp">
+ <selector>
+ <!-- selected state = solid filled in circle -->
+ <item android:state_checked="true">
+ <shape android:shape="oval"
+ android:tint="?android:attr/colorAccent">
+ <size android:height="34dp"
+ android:width="34dp" />
+ <solid android:color="@android:color/white" />
+ </shape>
+ </item>
+
+ <!-- unselected state = just the outline of a circle -->
+ <item android:state_checked="false">
+ <shape android:shape="oval">
+ <size android:height="34dp"
+ android:width="34dp" />
+ <stroke android:width="2dp"
+ android:color="?android:attr/colorAccent" />
+ <solid android:color="@android:color/transparent" />
+ </shape>
+ </item>
+ </selector>
+ </item>
+</layer-list>
diff --git a/res/layout/modes_icon_list.xml b/res/layout/modes_icon_list.xml
new file mode 100644
index 0000000..87e647e
--- /dev/null
+++ b/res/layout/modes_icon_list.xml
@@ -0,0 +1,38 @@
+<?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.
+-->
+
+
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/icon_list"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:clipToPadding="true"
+ android:nestedScrollingEnabled="false"
+ android:paddingStart="12dp"
+ android:paddingEnd="12dp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/res/layout/modes_icon_list_item.xml b/res/layout/modes_icon_list_item.xml
new file mode 100644
index 0000000..aa45de3
--- /dev/null
+++ b/res/layout/modes_icon_list_item.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/zen_mode_icon_list_item_size"
+ android:clickable="true">
+ <!-- width is match_parent to distribute remaining horizontal space -->
+
+ <ImageView
+ android:id="@+id/icon_image_view"
+ android:layout_width="@dimen/zen_mode_icon_list_circle_diameter"
+ android:layout_height="@dimen/zen_mode_icon_list_circle_diameter"
+ android:importantForAccessibility="no"
+ android:layout_gravity="center" />
+</FrameLayout>
diff --git a/res/layout/modes_set_schedule_layout.xml b/res/layout/modes_set_schedule_layout.xml
new file mode 100644
index 0000000..5758cfb
--- /dev/null
+++ b/res/layout/modes_set_schedule_layout.xml
@@ -0,0 +1,228 @@
+<?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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/modes_set_schedule_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:gravity="fill_horizontal"
+ android:orientation="vertical"
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp"
+ android:paddingTop="24dp"
+ android:paddingBottom="24dp">
+
+ <!-- Start time & end time row -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="fill_horizontal"
+ android:orientation="horizontal">
+
+ <!-- Start time: title (non-clickable preference), time setter -->
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/start_time_label"
+ android:clickable="false"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Medium"
+ android:text="@string/zen_mode_start_time" />
+
+ <TextView
+ android:id="@+id/start_time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Title"
+ android:textColor="?android:attr/colorAccent"
+ android:textSize="40sp" />
+
+ </LinearLayout>
+
+ <!-- End time: title (non-clickable preference), time setter -->
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/end_time_label"
+ android:clickable="false"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Medium"
+ android:text="@string/zen_mode_end_time" />
+
+ <TextView
+ android:id="@+id/end_time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Title"
+ android:textColor="?android:attr/colorAccent"
+ android:textSize="40sp" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <!-- Schedule duration display row -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp">
+
+ <!-- left side line divider -->
+ <View
+ android:layout_width="0dp"
+ android:layout_height="1.5dp"
+ android:layout_weight="1"
+ android:layout_gravity="center_vertical"
+ android:background="?android:attr/dividerHorizontal" />
+
+ <!-- length of schedule -->
+ <TextView
+ android:id="@+id/schedule_duration"
+ android:clickable="false"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingHorizontal="8dp"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Small" />
+
+ <!-- right side line divider -->
+ <View
+ android:layout_width="0dp"
+ android:layout_height="1.5dp"
+ android:layout_weight="1"
+ android:layout_gravity="center_vertical"
+ android:background="?android:attr/dividerHorizontal" />
+
+ </LinearLayout>
+
+ <!-- Buttons for selecting days of the week -->
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/days_of_week_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="10dp"
+ android:maxHeight="60dp"
+ android:orientation="horizontal">
+
+ <ToggleButton
+ android:id="@+id/day0"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:background="@drawable/modes_schedule_day_toggle"
+ android:textColor="@color/modes_set_schedule_text_color"
+ android:textSize="18sp"
+ app:layout_constraintDimensionRatio="1:1"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintEnd_toStartOf="@+id/day1"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <ToggleButton
+ android:id="@+id/day1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:background="@drawable/modes_schedule_day_toggle"
+ android:textColor="@color/modes_set_schedule_text_color"
+ android:textSize="18sp"
+ app:layout_constraintDimensionRatio="1:1"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintStart_toEndOf="@+id/day0"
+ app:layout_constraintEnd_toStartOf="@+id/day2"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <ToggleButton
+ android:id="@+id/day2"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:background="@drawable/modes_schedule_day_toggle"
+ android:textColor="@color/modes_set_schedule_text_color"
+ android:textSize="18sp"
+ app:layout_constraintDimensionRatio="1:1"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintStart_toEndOf="@+id/day1"
+ app:layout_constraintEnd_toStartOf="@+id/day3"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <ToggleButton
+ android:id="@+id/day3"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:background="@drawable/modes_schedule_day_toggle"
+ android:textColor="@color/modes_set_schedule_text_color"
+ android:textSize="18sp"
+ app:layout_constraintDimensionRatio="1:1"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintStart_toEndOf="@+id/day2"
+ app:layout_constraintEnd_toStartOf="@+id/day4"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <ToggleButton
+ android:id="@+id/day4"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:background="@drawable/modes_schedule_day_toggle"
+ android:textColor="@color/modes_set_schedule_text_color"
+ android:textSize="18sp"
+ app:layout_constraintDimensionRatio="1:1"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintStart_toEndOf="@+id/day3"
+ app:layout_constraintEnd_toStartOf="@+id/day5"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <ToggleButton
+ android:id="@+id/day5"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:background="@drawable/modes_schedule_day_toggle"
+ android:textColor="@color/modes_set_schedule_text_color"
+ android:textSize="18sp"
+ app:layout_constraintDimensionRatio="1:1"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintStart_toEndOf="@+id/day4"
+ app:layout_constraintEnd_toStartOf="@+id/day6"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <ToggleButton
+ android:id="@+id/day6"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:background="@drawable/modes_schedule_day_toggle"
+ android:textColor="@color/modes_set_schedule_text_color"
+ android:textSize="18sp"
+ app:layout_constraintDimensionRatio="1:1"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/day5"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+</LinearLayout>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c72c17d..d972e13 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -497,4 +497,9 @@
<dimen name="audio_streams_qrcode_size">264dp</dimen>
<dimen name="audio_streams_qrcode_preview_radius">30dp</dimen>
+
+ <!-- Zen Modes -->
+ <dimen name="zen_mode_icon_list_item_size">96dp</dimen>
+ <dimen name="zen_mode_icon_list_circle_diameter">56dp</dimen>
+ <dimen name="zen_mode_icon_list_icon_size">32dp</dimen>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7d240d6..bfac793 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -4458,15 +4458,15 @@
<string name="trackpad_pointer_speed">Pointer speed</string>
<!-- Title text for mouse pointer fill style. [CHAR LIMIT=35] -->
<string name="pointer_fill_style">Pointer fill style</string>
- <!-- Content description for black pointer fill style. [CHAR LIMIT=35] -->
+ <!-- Content description for black pointer fill style. [CHAR LIMIT=60] -->
<string name="pointer_fill_style_black_button">Change pointer fill style to black</string>
- <!-- Content description for green pointer fill style. [CHAR LIMIT=35] -->
+ <!-- Content description for green pointer fill style. [CHAR LIMIT=60] -->
<string name="pointer_fill_style_green_button">Change pointer fill style to green</string>
- <!-- Content description for yellow pointer fill style. [CHAR LIMIT=35] -->
+ <!-- Content description for yellow pointer fill style. [CHAR LIMIT=60] -->
<string name="pointer_fill_style_yellow_button">Change pointer fill style to yellow</string>
- <!-- Content description for pink pointer fill style. [CHAR LIMIT=35] -->
+ <!-- Content description for pink pointer fill style. [CHAR LIMIT=60] -->
<string name="pointer_fill_style_pink_button">Change pointer fill style to pink</string>
- <!-- Content description for blue pointer fill style. [CHAR LIMIT=35] -->
+ <!-- Content description for blue pointer fill style. [CHAR LIMIT=60] -->
<string name="pointer_fill_style_blue_button">Change pointer fill style to blue</string>
<!-- Title for the button to trigger the 'touch gesture' education. [CHAR LIMIT=35] -->
<string name="trackpad_touch_gesture">Learn touchpad gestures</string>
@@ -7963,6 +7963,15 @@
<!-- Do not disturb: Title on the page where users choose a calendar to determine the schedule for an automatically-triggered DND rule. [CHAR LIMIT=30] -->
<string name="zen_mode_set_calendar_category_title">Schedule</string>
+ <!-- Do not disturb: Title prompting a user to set a time-based schedule to use for an automatic rule [CHAR LIMIT=30] -->
+ <string name="zen_mode_set_schedule_title">Set a schedule</string>
+
+ <!-- Do not disturb: Link text prompting a user to click through to setting a time-based schedule [CHAR LIMIT=40] -->
+ <string name="zen_mode_set_schedule_link">Schedule</string>
+
+ <!-- Duration in hours and minutes for the length of a Do Not Disturb schedule. For example "1 hr, 22 min" -->
+ <string name="zen_mode_schedule_duration"><xliff:g example="10" id="hours">%1$d</xliff:g> hr, <xliff:g example="20" id="minutes">%2$d</xliff:g> min</string>
+
<!-- Do not disturb: Title do not disturb settings representing automatic (scheduled) do not disturb rules. [CHAR LIMIT=30] -->
<string name="zen_mode_schedule_category_title">Schedule</string>
@@ -9303,6 +9312,15 @@
<!-- [CHAR LIMIT=NONE] Zen mode summary spoken when changing mode by voice: Turn on all notifications. -->
<string name="zen_mode_summary_always">Change to always interrupt</string>
+ <!-- [CHAR LIMIT=20] Caption of the action button to change the name of a mode. -->
+ <string name="zen_mode_action_change_name">Rename</string>
+
+ <!-- [CHAR LIMIT=20] Caption of the action button to change the icon of a mode. -->
+ <string name="zen_mode_action_change_icon">Change icon</string>
+
+ <!-- [CHAR LIMIT=40] Zen mode settings: Title for the "choose mode icon" screen -->
+ <string name="zen_mode_icon_picker_title">Change icon</string>
+
<!-- Content description for help icon button [CHAR LIMIT=20] -->
<string name="warning_button_text">Warning</string>
diff --git a/res/xml/cellular_security.xml b/res/xml/cellular_security.xml
index 8e96bbd..dde152d 100644
--- a/res/xml/cellular_security.xml
+++ b/res/xml/cellular_security.xml
@@ -25,17 +25,17 @@
<PreferenceCategory
android:key="cellular_security_notifications_category"
android:title="@string/cellular_security_notifications"
- settings:controller="com.android.settings.network.CellularSecurityNotificationsDividerController">
+ settings:controller="com.android.settings.network.telephony.CellularSecurityNotificationsDividerController">
<SwitchPreferenceCompat
android:key="cellular_security_notifications"
android:title="@string/cellular_security_notifications_controller_title"
android:summary="@string/cellular_security_notifications_controller_summary"
settings:controller=
- "com.android.settings.network.CellularSecurityNotificationsPreferenceController"/>
+ "com.android.settings.network.telephony.CellularSecurityNotificationsPreferenceController"/>
</PreferenceCategory>
<PreferenceCategory
android:title="@string/cellular_security_settings_encryption_title"
- settings:controller="com.android.settings.network.CellularSecurityEncryptionDividerController">
+ settings:controller="com.android.settings.network.telephony.CellularSecurityEncryptionDividerController">
<SwitchPreferenceCompat
android:key="require_cellular_encryption"
android:title="@string/require_cellular_encryption_title"
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index 227f72d..866a529 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -675,6 +675,12 @@
android:title="@string/immediately_destroy_activities"
android:summary="@string/immediately_destroy_activities_summary" />
+ <ListPreference
+ android:key="app_process_limit"
+ android:title="@string/app_process_limit_title"
+ android:entries="@array/app_process_limit_entries"
+ android:entryValues="@array/app_process_limit_values" />
+
<Preference
android:key="background_check"
android:fragment="com.android.settings.applications.appops.BackgroundCheckSummary"
diff --git a/res/xml/modes_icon_picker.xml b/res/xml/modes_icon_picker.xml
new file mode 100644
index 0000000..cb0ff30
--- /dev/null
+++ b/res/xml/modes_icon_picker.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:key="zen_mode_icon_picker_page"
+ settings:searchable="false"
+ android:title="@string/zen_mode_icon_picker_title">
+
+ <com.android.settingslib.widget.LayoutPreference
+ android:key="current_icon"
+ android:layout="@layout/settings_entity_header" />
+
+ <com.android.settings.applications.SpacePreference
+ android:layout_height="16dp" />
+
+ <com.android.settingslib.widget.LayoutPreference
+ android:key="icon_list"
+ android:selectable="false"
+ android:layout="@layout/modes_icon_list"/>
+
+</PreferenceScreen>
diff --git a/res/xml/modes_rule_settings.xml b/res/xml/modes_rule_settings.xml
index f282274..cf090be 100644
--- a/res/xml/modes_rule_settings.xml
+++ b/res/xml/modes_rule_settings.xml
@@ -28,6 +28,10 @@
android:selectable="false"
android:layout="@layout/modes_activation_button"/>
+ <com.android.settingslib.widget.ActionButtonsPreference
+ android:key="actions"
+ android:selectable="true" />
+
<PreferenceCategory
android:title="@string/mode_interruption_filter_title"
android:key="modes_filters">
diff --git a/res/xml/modes_set_schedule.xml b/res/xml/modes_set_schedule.xml
new file mode 100644
index 0000000..dd73ec8
--- /dev/null
+++ b/res/xml/modes_set_schedule.xml
@@ -0,0 +1,38 @@
+<?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.
+ -->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:key="zen_mode_set_schedule"
+ settings:searchable="false"
+ android:title="@string/zen_mode_set_schedule_title">
+
+ <!-- Time picker for schedule -->
+ <com.android.settingslib.widget.LayoutPreference
+ android:key="schedule"
+ android:selectable="false"
+ android:layout="@layout/modes_set_schedule_layout"/>
+
+ <!-- Exit mode with alarm -->
+ <SwitchPreferenceCompat
+ android:key="exit_at_alarm"
+ android:title="@string/zen_mode_schedule_alarm_title"
+ android:summary="@string/zen_mode_schedule_alarm_summary"
+ android:order="99" />
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/private_space_settings.xml b/res/xml/private_space_settings.xml
index eb89dd7..53bbc57 100644
--- a/res/xml/private_space_settings.xml
+++ b/res/xml/private_space_settings.xml
@@ -22,16 +22,10 @@
android:title="@string/private_space_title"
settings:searchable="false">
- <com.android.settingslib.widget.IllustrationPreference
- android:key="private_space_illustration"
- settings:searchable="false"
- settings:lottie_rawRes="@raw/private_space_illustration"/>
-
- <Preference
+ <com.android.settingslib.widget.TopIntroPreference
android:key="private_space_description"
- android:summary="@string/private_space_description"
- android:selectable="false"
- settings:searchable="false" />
+ android:title="@string/private_space_description"
+ settings:searchable="false"/>
<PreferenceCategory
android:title="@string/private_space_category_lock">
diff --git a/src/com/android/settings/ResetNetworkRequest.java b/src/com/android/settings/ResetNetworkRequest.java
index 4be8b32..7632ea0 100644
--- a/src/com/android/settings/ResetNetworkRequest.java
+++ b/src/com/android/settings/ResetNetworkRequest.java
@@ -270,6 +270,7 @@
if ((mResetOptions & RESET_IMS_STACK) != 0) {
builder.resetIms(mSubscriptionIdToResetIms);
}
+ // Reset phone process and RILD may impact above components, keep them at the end
if ((mResetOptions & RESET_PHONE_PROCESS) != 0) {
builder.restartPhoneProcess();
}
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index 02205c1..a79ba80 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -283,7 +283,7 @@
createUiFromIntent(savedState, intent);
}
- protected void createUiFromIntent(Bundle savedState, Intent intent) {
+ protected void createUiFromIntent(@Nullable Bundle savedState, Intent intent) {
long startTime = System.currentTimeMillis();
final FeatureFactory factory = FeatureFactory.getFeatureFactory();
diff --git a/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java b/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java
index b5101b7..2546d44 100644
--- a/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java
+++ b/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java
@@ -148,7 +148,7 @@
// Check if another side of LE audio hearing aid is connected as a pair
final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
- if (memberDevices.stream().anyMatch(m -> m.isConnected())) {
+ if (memberDevices.stream().anyMatch(m -> m.getDevice().isConnected())) {
return mContext.getString(
R.string.accessibility_hearingaid_left_and_right_side_device_summary,
name);
@@ -156,7 +156,7 @@
// Check if another side of ASHA hearing aid is connected as a pair
final CachedBluetoothDevice subDevice = device.getSubDevice();
- if (subDevice != null && subDevice.isConnected()) {
+ if (subDevice != null && subDevice.getDevice().isConnected()) {
return mContext.getString(
R.string.accessibility_hearingaid_left_and_right_side_device_summary, name);
}
diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java b/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java
index 5be761e..56a3005 100644
--- a/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java
+++ b/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java
@@ -19,6 +19,7 @@
import android.content.pm.PackageManager;
import android.hardware.input.InputManager;
import android.util.FeatureFlagUtils;
+import android.util.Log;
import android.view.InputDevice;
import androidx.annotation.VisibleForTesting;
@@ -26,19 +27,29 @@
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
+import com.android.settings.R;
import com.android.settings.bluetooth.BluetoothDeviceUpdater;
import com.android.settings.bluetooth.ConnectedBluetoothDeviceUpdater;
+import com.android.settings.bluetooth.Utils;
import com.android.settings.connecteddevice.dock.DockUpdater;
import com.android.settings.connecteddevice.stylus.StylusDeviceUpdater;
import com.android.settings.connecteddevice.usb.ConnectedUsbDeviceUpdater;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.flags.Flags;
import com.android.settings.overlay.DockUpdaterFeatureProvider;
import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
+import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
+import com.android.settingslib.search.SearchIndexableRaw;
+
+import java.util.List;
/**
* Controller to maintain the {@link androidx.preference.PreferenceGroup} for all
@@ -49,6 +60,7 @@
DevicePreferenceCallback {
private static final String KEY = "connected_device_list";
+ private static final String TAG = "ConnectedDeviceGroupController";
@VisibleForTesting
PreferenceGroup mPreferenceGroup;
@@ -58,11 +70,13 @@
private StylusDeviceUpdater mStylusDeviceUpdater;
private final PackageManager mPackageManager;
private final InputManager mInputManager;
+ private final LocalBluetoothManager mLocalBluetoothManager;
public ConnectedDeviceGroupController(Context context) {
super(context, KEY);
mPackageManager = context.getPackageManager();
mInputManager = context.getSystemService(InputManager.class);
+ mLocalBluetoothManager = Utils.getLocalBluetoothManager(context);
}
@Override
@@ -221,4 +235,31 @@
}
return false;
}
+
+ @Override
+ public void updateDynamicRawDataToIndex(List<SearchIndexableRaw> rawData) {
+ if (!Flags.enableBondedBluetoothDeviceSearchable()) {
+ return;
+ }
+ if (mLocalBluetoothManager == null) {
+ Log.d(TAG, "Bluetooth is not supported");
+ return;
+ }
+ for (CachedBluetoothDevice cachedDevice :
+ mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()) {
+ if (!BluetoothDeviceFilter.BONDED_DEVICE_FILTER.matches(cachedDevice.getDevice())) {
+ continue;
+ }
+ if (BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+ cachedDevice.getDevice())) {
+ continue;
+ }
+ SearchIndexableRaw data = new SearchIndexableRaw(mContext);
+ // Include the identity address as well to ensure the key is unique.
+ data.key = cachedDevice.getName() + cachedDevice.getIdentityAddress();
+ data.title = cachedDevice.getName();
+ data.summaryOn = mContext.getString(R.string.connected_devices_dashboard_title);
+ rawData.add(data);
+ }
+ }
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCompatibilityPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCompatibilityPreferenceController.java
index d2f23ed..581ad62 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCompatibilityPreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCompatibilityPreferenceController.java
@@ -57,9 +57,10 @@
@Nullable private TwoStatePreference mPreference;
private final Executor mExecutor;
private final MetricsFeatureProvider mMetricsFeatureProvider;
- private AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false);
+ private final AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false);
- private final BluetoothLeBroadcast.Callback mBroadcastCallback =
+ @VisibleForTesting
+ protected final BluetoothLeBroadcast.Callback mBroadcastCallback =
new BluetoothLeBroadcast.Callback() {
@Override
public void onBroadcastStarted(int reason, int broadcastId) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
index c3248c7..c7d7407 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
@@ -20,6 +20,8 @@
import android.content.Context;
import android.os.Bundle;
+import androidx.annotation.VisibleForTesting;
+
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsCategoryController;
@@ -31,7 +33,6 @@
private static final String TAG = "AudioSharingDashboardFrag";
SettingsMainSwitchBar mMainSwitchBar;
- private AudioSharingSwitchBarController mSwitchBarController;
private AudioSharingDeviceVolumeGroupController mAudioSharingDeviceVolumeGroupController;
private AudioSharingCallAudioPreferenceController mAudioSharingCallAudioPreferenceController;
private AudioSharingPlaySoundPreferenceController mAudioSharingPlaySoundPreferenceController;
@@ -83,9 +84,10 @@
final SettingsActivity activity = (SettingsActivity) getActivity();
mMainSwitchBar = activity.getSwitchBar();
mMainSwitchBar.setTitle(getText(R.string.audio_sharing_switch_title));
- mSwitchBarController = new AudioSharingSwitchBarController(activity, mMainSwitchBar, this);
- mSwitchBarController.init(this);
- getSettingsLifecycle().addObserver(mSwitchBarController);
+ AudioSharingSwitchBarController switchBarController =
+ new AudioSharingSwitchBarController(activity, mMainSwitchBar, this);
+ switchBarController.init(this);
+ getSettingsLifecycle().addObserver(switchBarController);
mMainSwitchBar.show();
}
@@ -99,6 +101,19 @@
onProfilesConnectedForAttachedPreferences();
}
+ /** Test only: set mock controllers for the {@link AudioSharingDashboardFragment} */
+ @VisibleForTesting
+ protected void setControllers(
+ AudioSharingDeviceVolumeGroupController volumeGroupController,
+ AudioSharingCallAudioPreferenceController callAudioController,
+ AudioSharingPlaySoundPreferenceController playSoundController,
+ AudioStreamsCategoryController streamsCategoryController) {
+ mAudioSharingDeviceVolumeGroupController = volumeGroupController;
+ mAudioSharingCallAudioPreferenceController = callAudioController;
+ mAudioSharingPlaySoundPreferenceController = playSoundController;
+ mAudioStreamsCategoryController = streamsCategoryController;
+ }
+
private void updateVisibilityForAttachedPreferences() {
mAudioSharingDeviceVolumeGroupController.updateVisibility();
mAudioSharingCallAudioPreferenceController.updateVisibility();
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
index 6f7de8c..3d111fd 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
@@ -20,9 +20,11 @@
import android.app.settings.SettingsEnums;
import android.os.Bundle;
import android.util.Log;
+import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
@@ -48,13 +50,17 @@
* @param item The device item clicked.
*/
void onItemClick(AudioSharingDeviceItem item);
+
+ /** Called when users click the cancel button in the dialog. */
+ void onCancelClick();
}
@Nullable private static DialogEventListener sListener;
+ private static Pair<Integer, Object>[] sEventData = new Pair[0];
@Override
public int getMetricsCategory() {
- return SettingsEnums.DIALOG_START_AUDIO_SHARING;
+ return SettingsEnums.DIALOG_AUDIO_SHARING_ADD_DEVICE;
}
/**
@@ -63,14 +69,17 @@
* @param host The Fragment this dialog will be hosted.
* @param deviceItems The connected device items eligible for audio sharing.
* @param listener The callback to handle the user action on this dialog.
+ * @param eventData The eventData to log with for dialog onClick events.
*/
public static void show(
@NonNull Fragment host,
@NonNull List<AudioSharingDeviceItem> deviceItems,
- @NonNull DialogEventListener listener) {
+ @NonNull DialogEventListener listener,
+ @NonNull Pair<Integer, Object>[] eventData) {
if (!AudioSharingUtils.isFeatureEnabled()) return;
final FragmentManager manager = host.getChildFragmentManager();
sListener = listener;
+ sEventData = eventData;
AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG);
if (dialog != null) {
Log.d(TAG, "Dialog is showing, return.");
@@ -84,7 +93,19 @@
dialogFrag.show(manager, TAG);
}
+ /** Return the tag of {@link AudioSharingDialogFragment} dialog. */
+ public static @NonNull String tag() {
+ return TAG;
+ }
+
+ /** Test only: get the event data passed to the dialog. */
+ @VisibleForTesting
+ protected @NonNull Pair<Integer, Object>[] getEventData() {
+ return sEventData;
+ }
+
@Override
+ @NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle arguments = requireArguments();
List<AudioSharingDeviceItem> deviceItems =
@@ -93,12 +114,17 @@
AudioSharingDialogFactory.newBuilder(getActivity())
.setTitleIcon(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing)
.setIsCustomBodyEnabled(true);
+ if (deviceItems == null) {
+ Log.d(TAG, "Create dialog error: null deviceItems");
+ return builder.build();
+ }
if (deviceItems.isEmpty()) {
builder.setTitle(R.string.audio_sharing_share_dialog_title)
.setCustomImage(R.drawable.audio_sharing_guidance)
.setCustomMessage(R.string.audio_sharing_dialog_connect_device_content)
.setNegativeButton(
- R.string.audio_sharing_close_button_label, (dig, which) -> dismiss());
+ R.string.audio_sharing_close_button_label,
+ (dig, which) -> onCancelClick());
} else if (deviceItems.size() == 1) {
AudioSharingDeviceItem deviceItem = Iterables.getOnlyElement(deviceItems);
builder.setTitle(
@@ -111,11 +137,16 @@
v -> {
if (sListener != null) {
sListener.onItemClick(deviceItem);
+ mMetricsFeatureProvider.action(
+ getContext(),
+ SettingsEnums
+ .ACTION_AUDIO_SHARING_DIALOG_POSITIVE_BTN_CLICKED,
+ sEventData);
}
dismiss();
})
.setCustomNegativeButton(
- R.string.audio_sharing_no_thanks_button_label, v -> dismiss());
+ R.string.audio_sharing_no_thanks_button_label, v -> onCancelClick());
} else {
builder.setTitle(R.string.audio_sharing_share_with_more_dialog_title)
.setCustomMessage(R.string.audio_sharing_dialog_share_more_content)
@@ -130,8 +161,20 @@
dismiss();
},
AudioSharingDeviceAdapter.ActionType.SHARE))
- .setCustomNegativeButton(com.android.settings.R.string.cancel, v -> dismiss());
+ .setCustomNegativeButton(
+ com.android.settings.R.string.cancel, v -> onCancelClick());
}
return builder.build();
}
+
+ private void onCancelClick() {
+ if (sListener != null) {
+ sListener.onCancelClick();
+ mMetricsFeatureProvider.action(
+ getContext(),
+ SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED,
+ sEventData);
+ }
+ dismiss();
+ }
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java
index c329e82..5458a9f 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java
@@ -24,6 +24,7 @@
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
import android.util.Log;
+import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -33,15 +34,21 @@
import com.android.settings.bluetooth.Utils;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+import com.android.settingslib.utils.ThreadUtils;
+
+import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.Executor;
public class AudioSharingDialogHandler {
@@ -51,6 +58,7 @@
@Nullable private final LocalBluetoothManager mLocalBtManager;
@Nullable private final LocalBluetoothLeBroadcast mBroadcast;
@Nullable private final LocalBluetoothLeBroadcastAssistant mAssistant;
+ private final MetricsFeatureProvider mMetricsFeatureProvider;
private List<BluetoothDevice> mTargetSinks = new ArrayList<>();
private final BluetoothLeBroadcast.Callback mBroadcastCallback =
@@ -119,9 +127,7 @@
new SubSettingLauncher(mContext)
.setDestination(AudioSharingDashboardFragment.class.getName())
.setSourceMetricsCategory(
- (mHostFragment != null
- && mHostFragment
- instanceof DashboardFragment)
+ (mHostFragment instanceof DashboardFragment)
? ((DashboardFragment) mHostFragment)
.getMetricsCategory()
: SettingsEnums.PAGE_UNKNOWN)
@@ -146,6 +152,7 @@
mLocalBtManager != null
? mLocalBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile()
: null;
+ mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
}
/** Register callbacks for dialog handler */
@@ -191,6 +198,18 @@
List<AudioSharingDeviceItem> deviceItemsInSharingSession =
AudioSharingUtils.buildOrderedConnectedLeadAudioSharingDeviceItem(
mLocalBtManager, groupedDevices, /* filterByInSharing= */ true);
+ AudioSharingStopDialogFragment.DialogEventListener listener =
+ () -> {
+ cachedDevice.setActive();
+ AudioSharingUtils.stopBroadcasting(mLocalBtManager);
+ };
+ Pair<Integer, Object>[] eventData =
+ AudioSharingUtils.buildAudioSharingDialogEventData(
+ SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY,
+ SettingsEnums.DIALOG_STOP_AUDIO_SHARING,
+ userTriggered,
+ deviceItemsInSharingSession.size(),
+ /* candidateDeviceCount= */ 0);
postOnMainThread(
() -> {
closeOpeningDialogsOtherThan(AudioSharingStopDialogFragment.tag());
@@ -198,10 +217,8 @@
mHostFragment,
deviceItemsInSharingSession,
cachedDevice,
- () -> {
- cachedDevice.setActive();
- AudioSharingUtils.stopBroadcasting(mLocalBtManager);
- });
+ listener,
+ eventData);
});
} else {
if (userTriggered) {
@@ -252,6 +269,20 @@
// Show audio sharing switch dialog when the third eligible (LE audio) remote device
// connected during a sharing session.
if (deviceItemsInSharingSession.size() >= 2) {
+ AudioSharingDisconnectDialogFragment.DialogEventListener listener =
+ (AudioSharingDeviceItem item) -> {
+ // Remove all sources from the device user clicked
+ removeSourceForGroup(item.getGroupId(), groupedDevices);
+ // Add current broadcast to the latest connected device
+ addSourceForGroup(groupId, groupedDevices);
+ };
+ Pair<Integer, Object>[] eventData =
+ AudioSharingUtils.buildAudioSharingDialogEventData(
+ SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY,
+ SettingsEnums.DIALOG_AUDIO_SHARING_SWITCH_DEVICE,
+ userTriggered,
+ deviceItemsInSharingSession.size(),
+ /* candidateDeviceCount= */ 1);
postOnMainThread(
() -> {
closeOpeningDialogsOtherThan(
@@ -260,16 +291,29 @@
mHostFragment,
deviceItemsInSharingSession,
cachedDevice,
- (AudioSharingDeviceItem item) -> {
- // Remove all sources from the device user clicked
- removeSourceForGroup(item.getGroupId(), groupedDevices);
- // Add current broadcast to the latest connected device
- addSourceForGroup(groupId, groupedDevices);
- });
+ listener,
+ eventData);
});
} else {
// Show audio sharing join dialog when the first or second eligible (LE audio)
// remote device connected during a sharing session.
+ AudioSharingJoinDialogFragment.DialogEventListener listener =
+ new AudioSharingJoinDialogFragment.DialogEventListener() {
+ @Override
+ public void onShareClick() {
+ addSourceForGroup(groupId, groupedDevices);
+ }
+
+ @Override
+ public void onCancelClick() {}
+ };
+ Pair<Integer, Object>[] eventData =
+ AudioSharingUtils.buildAudioSharingDialogEventData(
+ SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY,
+ SettingsEnums.DIALOG_AUDIO_SHARING_ADD_DEVICE,
+ userTriggered,
+ deviceItemsInSharingSession.size(),
+ /* candidateDeviceCount= */ 1);
postOnMainThread(
() -> {
closeOpeningDialogsOtherThan(AudioSharingJoinDialogFragment.tag());
@@ -277,15 +321,8 @@
mHostFragment,
deviceItemsInSharingSession,
cachedDevice,
- new AudioSharingJoinDialogFragment.DialogEventListener() {
- @Override
- public void onShareClick() {
- addSourceForGroup(groupId, groupedDevices);
- }
-
- @Override
- public void onCancelClick() {}
- });
+ listener,
+ eventData);
});
}
} else {
@@ -302,39 +339,43 @@
// Show audio sharing join dialog when the second eligible (LE audio) remote
// device connect and no sharing session.
if (deviceItems.size() == 1) {
+ AudioSharingJoinDialogFragment.DialogEventListener listener =
+ new AudioSharingJoinDialogFragment.DialogEventListener() {
+ @Override
+ public void onShareClick() {
+ mTargetSinks = new ArrayList<>();
+ for (List<CachedBluetoothDevice> devices :
+ groupedDevices.values()) {
+ for (CachedBluetoothDevice device : devices) {
+ mTargetSinks.add(device.getDevice());
+ }
+ }
+ Log.d(TAG, "Start broadcast with sinks = " + mTargetSinks.size());
+ if (mBroadcast != null) {
+ mBroadcast.startPrivateBroadcast();
+ }
+ }
+
+ @Override
+ public void onCancelClick() {
+ if (userTriggered) {
+ cachedDevice.setActive();
+ }
+ }
+ };
+
+ Pair<Integer, Object>[] eventData =
+ AudioSharingUtils.buildAudioSharingDialogEventData(
+ SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY,
+ SettingsEnums.DIALOG_START_AUDIO_SHARING,
+ userTriggered,
+ /* deviceCountInSharing= */ 0,
+ /* candidateDeviceCount= */ 2);
postOnMainThread(
() -> {
closeOpeningDialogsOtherThan(AudioSharingJoinDialogFragment.tag());
AudioSharingJoinDialogFragment.show(
- mHostFragment,
- deviceItems,
- cachedDevice,
- new AudioSharingJoinDialogFragment.DialogEventListener() {
- @Override
- public void onShareClick() {
- mTargetSinks = new ArrayList<>();
- for (List<CachedBluetoothDevice> devices :
- groupedDevices.values()) {
- for (CachedBluetoothDevice device : devices) {
- mTargetSinks.add(device.getDevice());
- }
- }
- Log.d(
- TAG,
- "Start broadcast with sinks: "
- + mTargetSinks.size());
- if (mBroadcast != null) {
- mBroadcast.startPrivateBroadcast();
- }
- }
-
- @Override
- public void onCancelClick() {
- if (userTriggered) {
- cachedDevice.setActive();
- }
- }
- });
+ mHostFragment, deviceItems, cachedDevice, listener, eventData);
});
} else if (userTriggered) {
cachedDevice.setActive();
@@ -346,9 +387,12 @@
if (mHostFragment == null) return;
List<Fragment> fragments = mHostFragment.getChildFragmentManager().getFragments();
for (Fragment fragment : fragments) {
- if (fragment instanceof DialogFragment && !fragment.getTag().equals(tag)) {
+ if (fragment instanceof DialogFragment
+ && fragment.getTag() != null
+ && !fragment.getTag().equals(tag)) {
Log.d(TAG, "Remove staled opening dialog " + fragment.getTag());
((DialogFragment) fragment).dismiss();
+ logDialogDismissEvent(fragment);
}
}
}
@@ -365,6 +409,7 @@
&& AudioSharingUtils.getGroupId(device) == groupId) {
Log.d(TAG, "Remove staled opening dialog for group " + groupId);
((DialogFragment) fragment).dismiss();
+ logDialogDismissEvent(fragment);
}
}
}
@@ -382,6 +427,7 @@
"Remove staled opening dialog for device "
+ cachedDevice.getDevice().getAnonymizedAddress());
((DialogFragment) fragment).dismiss();
+ logDialogDismissEvent(fragment);
}
}
}
@@ -409,9 +455,9 @@
Log.d(TAG, "Fail to remove source for group " + groupId);
return;
}
- groupedDevices.get(groupId).stream()
+ groupedDevices.getOrDefault(groupId, ImmutableList.of()).stream()
.map(CachedBluetoothDevice::getDevice)
- .filter(device -> device != null)
+ .filter(Objects::nonNull)
.forEach(
device -> {
for (BluetoothLeBroadcastReceiveState source :
@@ -431,9 +477,9 @@
Log.d(TAG, "Fail to add source due to invalid group id, group = " + groupId);
return;
}
- groupedDevices.get(groupId).stream()
+ groupedDevices.getOrDefault(groupId, ImmutableList.of()).stream()
.map(CachedBluetoothDevice::getDevice)
- .filter(device -> device != null)
+ .filter(Objects::nonNull)
.forEach(
device ->
mAssistant.addSource(
@@ -449,4 +495,29 @@
private boolean isBroadcasting() {
return mBroadcast != null && mBroadcast.isEnabled(null);
}
+
+ private void logDialogDismissEvent(Fragment fragment) {
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ int pageId = SettingsEnums.PAGE_UNKNOWN;
+ if (fragment instanceof AudioSharingJoinDialogFragment) {
+ pageId =
+ ((AudioSharingJoinDialogFragment) fragment)
+ .getMetricsCategory();
+ } else if (fragment instanceof AudioSharingStopDialogFragment) {
+ pageId =
+ ((AudioSharingStopDialogFragment) fragment)
+ .getMetricsCategory();
+ } else if (fragment instanceof AudioSharingDisconnectDialogFragment) {
+ pageId =
+ ((AudioSharingDisconnectDialogFragment) fragment)
+ .getMetricsCategory();
+ }
+ mMetricsFeatureProvider.action(
+ mContext,
+ SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_AUTO_DISMISS,
+ pageId);
+ });
+ }
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java
index e859693..5f6d84a 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java
@@ -20,16 +20,20 @@
import android.app.settings.SettingsEnums;
import android.os.Bundle;
import android.util.Log;
+import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.utils.ThreadUtils;
import java.util.List;
import java.util.Locale;
@@ -55,6 +59,7 @@
@Nullable private static DialogEventListener sListener;
@Nullable private static CachedBluetoothDevice sNewDevice;
+ private static Pair<Integer, Object>[] sEventData = new Pair[0];
@Override
public int getMetricsCategory() {
@@ -70,12 +75,14 @@
* @param deviceItems The existing connected device items in audio sharing session.
* @param newDevice The latest connected device triggered this dialog.
* @param listener The callback to handle the user action on this dialog.
+ * @param eventData The eventData to log with for dialog onClick events.
*/
public static void show(
@NonNull Fragment host,
@NonNull List<AudioSharingDeviceItem> deviceItems,
@NonNull CachedBluetoothDevice newDevice,
- @NonNull DialogEventListener listener) {
+ @NonNull DialogEventListener listener,
+ @NonNull Pair<Integer, Object>[] eventData) {
if (!AudioSharingUtils.isFeatureEnabled()) return;
FragmentManager manager = host.getChildFragmentManager();
AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG);
@@ -91,6 +98,7 @@
newGroupId));
sListener = listener;
sNewDevice = newDevice;
+ sEventData = eventData;
return;
} else {
Log.d(
@@ -101,10 +109,22 @@
+ "dismiss current dialog.",
newGroupId));
dialog.dismiss();
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () ->
+ FeatureFactory.getFeatureFactory()
+ .getMetricsFeatureProvider()
+ .action(
+ dialog.getContext(),
+ SettingsEnums
+ .ACTION_AUDIO_SHARING_DIALOG_AUTO_DISMISS,
+ SettingsEnums
+ .DIALOG_AUDIO_SHARING_SWITCH_DEVICE));
}
}
sListener = listener;
sNewDevice = newDevice;
+ sEventData = eventData;
Log.d(TAG, "Show up the dialog.");
final Bundle bundle = new Bundle();
bundle.putParcelableList(BUNDLE_KEY_DEVICE_TO_DISCONNECT_ITEMS, deviceItems);
@@ -125,28 +145,54 @@
return sNewDevice;
}
+ /** Test only: get the event data passed to the dialog. */
+ @VisibleForTesting
+ protected @NonNull Pair<Integer, Object>[] getEventData() {
+ return sEventData;
+ }
+
@Override
+ @NonNull
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Bundle arguments = requireArguments();
List<AudioSharingDeviceItem> deviceItems =
arguments.getParcelable(BUNDLE_KEY_DEVICE_TO_DISCONNECT_ITEMS, List.class);
- return AudioSharingDialogFactory.newBuilder(getActivity())
- .setTitle(R.string.audio_sharing_disconnect_dialog_title)
- .setTitleIcon(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing)
- .setIsCustomBodyEnabled(true)
- .setCustomMessage(R.string.audio_sharing_dialog_disconnect_content)
- .setCustomDeviceActions(
- new AudioSharingDeviceAdapter(
- getContext(),
- deviceItems,
- (AudioSharingDeviceItem item) -> {
- if (sListener != null) {
- sListener.onItemClick(item);
- }
+ AudioSharingDialogFactory.DialogBuilder builder =
+ AudioSharingDialogFactory.newBuilder(getActivity())
+ .setTitle(R.string.audio_sharing_disconnect_dialog_title)
+ .setTitleIcon(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing)
+ .setIsCustomBodyEnabled(true)
+ .setCustomMessage(R.string.audio_sharing_dialog_disconnect_content)
+ .setCustomNegativeButton(
+ com.android.settings.R.string.cancel,
+ v -> {
+ mMetricsFeatureProvider.action(
+ getContext(),
+ SettingsEnums
+ .ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED,
+ sEventData);
dismiss();
- },
- AudioSharingDeviceAdapter.ActionType.REMOVE))
- .setCustomNegativeButton(com.android.settings.R.string.cancel, v -> dismiss())
- .build();
+ });
+ if (deviceItems == null) {
+ Log.d(TAG, "Create dialog error: null deviceItems");
+ return builder.build();
+ }
+ builder.setCustomDeviceActions(
+ new AudioSharingDeviceAdapter(
+ getContext(),
+ deviceItems,
+ (AudioSharingDeviceItem item) -> {
+ if (sListener != null) {
+ sListener.onItemClick(item);
+ mMetricsFeatureProvider.action(
+ getContext(),
+ SettingsEnums
+ .ACTION_AUDIO_SHARING_DIALOG_POSITIVE_BTN_CLICKED,
+ sEventData);
+ }
+ dismiss();
+ },
+ AudioSharingDeviceAdapter.ActionType.REMOVE));
+ return builder.build();
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragment.java
index 4982179..7eebbcb 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragment.java
@@ -20,9 +20,11 @@
import android.app.settings.SettingsEnums;
import android.os.Bundle;
import android.util.Log;
+import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
@@ -52,6 +54,7 @@
@Nullable private static DialogEventListener sListener;
@Nullable private static CachedBluetoothDevice sNewDevice;
+ private static Pair<Integer, Object>[] sEventData = new Pair[0];
@Override
public int getMetricsCategory() {
@@ -69,16 +72,19 @@
* @param deviceItems The existing connected device items eligible for audio sharing.
* @param newDevice The latest connected device triggered this dialog.
* @param listener The callback to handle the user action on this dialog.
+ * @param eventData The eventData to log with for dialog onClick events.
*/
public static void show(
@NonNull Fragment host,
@NonNull List<AudioSharingDeviceItem> deviceItems,
@NonNull CachedBluetoothDevice newDevice,
- @NonNull DialogEventListener listener) {
+ @NonNull DialogEventListener listener,
+ @NonNull Pair<Integer, Object>[] eventData) {
if (!AudioSharingUtils.isFeatureEnabled()) return;
final FragmentManager manager = host.getChildFragmentManager();
sListener = listener;
sNewDevice = newDevice;
+ sEventData = eventData;
AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG);
if (dialog != null) {
Log.d(TAG, "Dialog is showing, update the content.");
@@ -104,7 +110,14 @@
return sNewDevice;
}
+ /** Test only: get the event data passed to the dialog. */
+ @VisibleForTesting
+ protected @NonNull Pair<Integer, Object>[] getEventData() {
+ return sEventData;
+ }
+
@Override
+ @NonNull
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Bundle arguments = requireArguments();
List<AudioSharingDeviceItem> deviceItems =
@@ -121,6 +134,11 @@
v -> {
if (sListener != null) {
sListener.onShareClick();
+ mMetricsFeatureProvider.action(
+ getContext(),
+ SettingsEnums
+ .ACTION_AUDIO_SHARING_DIALOG_POSITIVE_BTN_CLICKED,
+ sEventData);
}
dismiss();
})
@@ -129,11 +147,20 @@
v -> {
if (sListener != null) {
sListener.onCancelClick();
+ mMetricsFeatureProvider.action(
+ getContext(),
+ SettingsEnums
+ .ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED,
+ sEventData);
}
dismiss();
})
.build();
- updateDialog(deviceItems, newDeviceName, dialog);
+ if (deviceItems == null) {
+ Log.d(TAG, "Fail to create dialog: null deviceItems");
+ } else {
+ updateDialog(deviceItems, newDeviceName, dialog);
+ }
dialog.show();
AudioSharingDialogHelper.updateMessageStyle(dialog);
return dialog;
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceController.java
index 54eb722..d27d3a2 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceController.java
@@ -23,6 +23,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
@@ -50,7 +51,8 @@
@Nullable private Preference mPreference;
private final Executor mExecutor;
- private final BluetoothLeBroadcast.Callback mBroadcastCallback =
+ @VisibleForTesting
+ protected final BluetoothLeBroadcast.Callback mBroadcastCallback =
new BluetoothLeBroadcast.Callback() {
@Override
public void onBroadcastStarted(int reason, int broadcastId) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragment.java
index affd54a..beac4b0 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragment.java
@@ -20,16 +20,20 @@
import android.app.settings.SettingsEnums;
import android.os.Bundle;
import android.util.Log;
+import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.utils.ThreadUtils;
import com.google.common.collect.Iterables;
@@ -52,6 +56,7 @@
@Nullable private static DialogEventListener sListener;
@Nullable private static CachedBluetoothDevice sCachedDevice;
+ private static Pair<Integer, Object>[] sEventData = new Pair[0];
@Override
public int getMetricsCategory() {
@@ -67,12 +72,14 @@
* @param deviceItems The existing connected device items in audio sharing session.
* @param newDevice The latest connected device triggered this dialog.
* @param listener The callback to handle the user action on this dialog.
+ * @param eventData The eventData to log with for dialog onClick events.
*/
public static void show(
@NonNull Fragment host,
@NonNull List<AudioSharingDeviceItem> deviceItems,
@NonNull CachedBluetoothDevice newDevice,
- @NonNull DialogEventListener listener) {
+ @NonNull DialogEventListener listener,
+ @NonNull Pair<Integer, Object>[] eventData) {
if (!AudioSharingUtils.isFeatureEnabled()) return;
final FragmentManager manager = host.getChildFragmentManager();
AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG);
@@ -88,6 +95,7 @@
newGroupId));
sListener = listener;
sCachedDevice = newDevice;
+ sEventData = eventData;
return;
} else {
Log.d(
@@ -98,10 +106,21 @@
+ "dismiss current dialog.",
newGroupId));
dialog.dismiss();
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () ->
+ FeatureFactory.getFeatureFactory()
+ .getMetricsFeatureProvider()
+ .action(
+ dialog.getContext(),
+ SettingsEnums
+ .ACTION_AUDIO_SHARING_DIALOG_AUTO_DISMISS,
+ SettingsEnums.DIALOG_STOP_AUDIO_SHARING));
}
}
sListener = listener;
sCachedDevice = newDevice;
+ sEventData = eventData;
Log.d(TAG, "Show up the dialog.");
final Bundle bundle = new Bundle();
bundle.putParcelableList(BUNDLE_KEY_DEVICE_TO_DISCONNECT_ITEMS, deviceItems);
@@ -121,23 +140,34 @@
return sCachedDevice;
}
+ /** Test only: get the event data passed to the dialog. */
+ @VisibleForTesting
+ protected @NonNull Pair<Integer, Object>[] getEventData() {
+ return sEventData;
+ }
+
@Override
+ @NonNull
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Bundle arguments = requireArguments();
List<AudioSharingDeviceItem> deviceItems =
arguments.getParcelable(BUNDLE_KEY_DEVICE_TO_DISCONNECT_ITEMS, List.class);
String newDeviceName = arguments.getString(BUNDLE_KEY_NEW_DEVICE_NAME);
- String customMessage =
- deviceItems.size() == 1
- ? getString(
- R.string.audio_sharing_stop_dialog_content,
- Iterables.getOnlyElement(deviceItems).getName())
- : (deviceItems.size() == 2
- ? getString(
- R.string.audio_sharing_stop_dialog_with_two_content,
- deviceItems.get(0).getName(),
- deviceItems.get(1).getName())
- : getString(R.string.audio_sharing_stop_dialog_with_more_content));
+ String customMessage = "";
+ if (deviceItems != null) {
+ customMessage =
+ deviceItems.size() == 1
+ ? getString(
+ R.string.audio_sharing_stop_dialog_content,
+ Iterables.getOnlyElement(deviceItems).getName())
+ : (deviceItems.size() == 2
+ ? getString(
+ R.string.audio_sharing_stop_dialog_with_two_content,
+ deviceItems.get(0).getName(),
+ deviceItems.get(1).getName())
+ : getString(
+ R.string.audio_sharing_stop_dialog_with_more_content));
+ }
AlertDialog dialog =
AudioSharingDialogFactory.newBuilder(getActivity())
.setTitle(
@@ -150,10 +180,21 @@
(dlg, which) -> {
if (sListener != null) {
sListener.onStopSharingClick();
+ mMetricsFeatureProvider.action(
+ getContext(),
+ SettingsEnums
+ .ACTION_AUDIO_SHARING_DIALOG_POSITIVE_BTN_CLICKED,
+ sEventData);
}
})
.setNegativeButton(
- com.android.settings.R.string.cancel, (dlg, which) -> dismiss())
+ com.android.settings.R.string.cancel,
+ (dlg, which) ->
+ mMetricsFeatureProvider.action(
+ getContext(),
+ SettingsEnums
+ .ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED,
+ sEventData))
.build();
dialog.show();
AudioSharingDialogHelper.updateMessageStyle(dialog);
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
index 475be85..5022579 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
@@ -16,6 +16,7 @@
package com.android.settings.connecteddevice.audiosharing;
+import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcast;
@@ -29,24 +30,27 @@
import android.content.IntentFilter;
import android.util.FeatureFlagUtils;
import android.util.Log;
+import android.util.Pair;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.fragment.app.Fragment;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import com.android.settings.bluetooth.Utils;
import com.android.settings.core.BasePreferenceController;
-import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.utils.ThreadUtils;
import com.google.common.collect.ImmutableList;
@@ -56,6 +60,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -91,14 +96,15 @@
@Nullable private final LocalBluetoothProfileManager mProfileManager;
@Nullable private final LocalBluetoothLeBroadcast mBroadcast;
@Nullable private final LocalBluetoothLeBroadcastAssistant mAssistant;
- @Nullable private DashboardFragment mFragment;
+ @Nullable private Fragment mFragment;
private final Executor mExecutor;
+ private final MetricsFeatureProvider mMetricsFeatureProvider;
private final OnAudioSharingStateChangedListener mListener;
private Map<Integer, List<CachedBluetoothDevice>> mGroupedConnectedDevices = new HashMap<>();
private List<BluetoothDevice> mTargetActiveSinks = new ArrayList<>();
private List<AudioSharingDeviceItem> mDeviceItemsForSharing = new ArrayList<>();
@VisibleForTesting IntentFilter mIntentFilter;
- private AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false);
+ private final AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false);
@VisibleForTesting
BroadcastReceiver mReceiver =
@@ -110,7 +116,8 @@
}
};
- private final BluetoothLeBroadcast.Callback mBroadcastCallback =
+ @VisibleForTesting
+ protected final BluetoothLeBroadcast.Callback mBroadcastCallback =
new BluetoothLeBroadcast.Callback() {
@Override
public void onBroadcastStarted(int reason, int broadcastId) {
@@ -182,7 +189,7 @@
public void onPlaybackStopped(int reason, int broadcastId) {}
};
- private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
+ private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
new BluetoothLeBroadcastAssistant.Callback() {
@Override
public void onSearchStarted(int reason) {}
@@ -251,9 +258,9 @@
@Override
public void onReceiveStateChanged(
- BluetoothDevice sink,
+ @NonNull BluetoothDevice sink,
int sourceId,
- BluetoothLeBroadcastReceiveState state) {}
+ @NonNull BluetoothLeBroadcastReceiveState state) {}
};
AudioSharingSwitchBarController(
@@ -273,6 +280,7 @@
? null
: mProfileManager.getLeAudioBroadcastAssistantProfile();
mExecutor = Executors.newSingleThreadExecutor();
+ mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
}
@Override
@@ -378,7 +386,7 @@
*
* @param fragment The fragment to host the {@link AudioSharingSwitchBarController} dialog.
*/
- public void init(DashboardFragment fragment) {
+ public void init(@NonNull Fragment fragment) {
this.mFragment = fragment;
}
@@ -494,34 +502,58 @@
}
private void handleOnBroadcastReady() {
- AudioSharingUtils.addSourceToTargetSinks(mTargetActiveSinks, mBtManager);
- mTargetActiveSinks.clear();
+ Pair<Integer, Object>[] eventData =
+ AudioSharingUtils.buildAudioSharingDialogEventData(
+ SettingsEnums.AUDIO_SHARING_SETTINGS,
+ SettingsEnums.DIALOG_AUDIO_SHARING_ADD_DEVICE,
+ /* userTriggered= */ false,
+ /* deviceCountInSharing= */ mTargetActiveSinks.isEmpty() ? 0 : 1,
+ /* candidateDeviceCount= */ mDeviceItemsForSharing.size());
+ if (!mTargetActiveSinks.isEmpty()) {
+ Log.d(TAG, "handleOnBroadcastReady: automatically add source to active sinks.");
+ AudioSharingUtils.addSourceToTargetSinks(mTargetActiveSinks, mBtManager);
+ mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING);
+ mTargetActiveSinks.clear();
+ }
if (mFragment == null) {
- Log.w(TAG, "Dialog fail to show due to null fragment.");
+ Log.d(TAG, "handleOnBroadcastReady: dialog fail to show due to null fragment.");
mGroupedConnectedDevices.clear();
mDeviceItemsForSharing.clear();
return;
}
+ showDialog(eventData);
+ }
+
+ private void showDialog(Pair<Integer, Object>[] eventData) {
+ AudioSharingDialogFragment.DialogEventListener listener =
+ new AudioSharingDialogFragment.DialogEventListener() {
+ @Override
+ public void onItemClick(@NonNull AudioSharingDeviceItem item) {
+ AudioSharingUtils.addSourceToTargetSinks(
+ mGroupedConnectedDevices
+ .getOrDefault(item.getGroupId(), ImmutableList.of())
+ .stream()
+ .map(CachedBluetoothDevice::getDevice)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()),
+ mBtManager);
+ mGroupedConnectedDevices.clear();
+ mDeviceItemsForSharing.clear();
+ }
+
+ @Override
+ public void onCancelClick() {
+ mGroupedConnectedDevices.clear();
+ mDeviceItemsForSharing.clear();
+ }
+ };
AudioSharingUtils.postOnMainThread(
mContext,
() -> {
// Check nullability to pass NullAway check
if (mFragment != null) {
AudioSharingDialogFragment.show(
- mFragment,
- mDeviceItemsForSharing,
- item -> {
- AudioSharingUtils.addSourceToTargetSinks(
- mGroupedConnectedDevices
- .getOrDefault(
- item.getGroupId(), ImmutableList.of())
- .stream()
- .map(CachedBluetoothDevice::getDevice)
- .collect(Collectors.toList()),
- mBtManager);
- mGroupedConnectedDevices.clear();
- mDeviceItemsForSharing.clear();
- });
+ mFragment, mDeviceItemsForSharing, listener, eventData);
}
});
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
index f63717e..29f605c 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
@@ -16,6 +16,12 @@
package com.android.settings.connecteddevice.audiosharing;
+import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.MetricKey.METRIC_KEY_CANDIDATE_DEVICE_COUNT;
+import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.MetricKey.METRIC_KEY_DEVICE_COUNT_IN_SHARING;
+import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.MetricKey.METRIC_KEY_PAGE_ID;
+import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.MetricKey.METRIC_KEY_SOURCE_PAGE_ID;
+import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.MetricKey.METRIC_KEY_USER_TRIGGERED;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
@@ -25,6 +31,7 @@
import android.content.Context;
import android.provider.Settings;
import android.util.Log;
+import android.util.Pair;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -54,6 +61,14 @@
private static final String TAG = "AudioSharingUtils";
private static final boolean DEBUG = BluetoothUtils.D;
+ public enum MetricKey {
+ METRIC_KEY_SOURCE_PAGE_ID,
+ METRIC_KEY_PAGE_ID,
+ METRIC_KEY_USER_TRIGGERED,
+ METRIC_KEY_DEVICE_COUNT_IN_SHARING,
+ METRIC_KEY_CANDIDATE_DEVICE_COUNT
+ }
+
/**
* Fetch {@link CachedBluetoothDevice}s connected to the broadcast assistant. The devices are
* grouped by CSIP group id.
@@ -121,7 +136,7 @@
boolean filterByInSharing) {
List<CachedBluetoothDevice> orderedDevices = new ArrayList<>();
for (List<CachedBluetoothDevice> devices : groupedConnectedDevices.values()) {
- @Nullable CachedBluetoothDevice leadDevice = getLeadDevice(devices);
+ CachedBluetoothDevice leadDevice = getLeadDevice(devices);
if (leadDevice == null) {
Log.d(TAG, "Skip due to no lead device");
continue;
@@ -206,7 +221,7 @@
return buildOrderedConnectedLeadDevices(
localBtManager, groupedConnectedDevices, filterByInSharing)
.stream()
- .map(device -> buildAudioSharingDeviceItem(device))
+ .map(AudioSharingUtils::buildAudioSharingDeviceItem)
.collect(Collectors.toList());
}
@@ -315,8 +330,9 @@
manager.getProfileManager().getLeAudioBroadcastProfile();
if (broadcast == null) {
Log.d(TAG, "Skip stop broadcasting due to broadcast profile is null");
+ } else {
+ broadcast.stopBroadcast(broadcast.getLatestBroadcastId());
}
- broadcast.stopBroadcast(broadcast.getLatestBroadcastId());
}
/**
@@ -378,9 +394,32 @@
return false;
}
VolumeControlProfile vc = profileManager.getVolumeControlProfile();
- if (vc == null || !vc.isProfileReady()) {
- return false;
- }
- return true;
+ return vc != null && vc.isProfileReady();
+ }
+
+ /**
+ * Build audio sharing dialog log event data
+ *
+ * @param sourcePageId The source page id on which the dialog is shown. *
+ * @param pageId The page id of the dialog.
+ * @param userTriggered Indicates whether the dialog is triggered by user click.
+ * @param deviceCountInSharing The count of the devices joining the audio sharing.
+ * @param candidateDeviceCount The count of the eligible devices to join the audio sharing.
+ * @return The event data to be attached to the audio sharing action logs.
+ */
+ @NonNull
+ public static Pair<Integer, Object>[] buildAudioSharingDialogEventData(
+ int sourcePageId,
+ int pageId,
+ boolean userTriggered,
+ int deviceCountInSharing,
+ int candidateDeviceCount) {
+ return new Pair[] {
+ Pair.create(METRIC_KEY_SOURCE_PAGE_ID.ordinal(), sourcePageId),
+ Pair.create(METRIC_KEY_PAGE_ID.ordinal(), pageId),
+ Pair.create(METRIC_KEY_USER_TRIGGERED.ordinal(), userTriggered ? 1 : 0),
+ Pair.create(METRIC_KEY_DEVICE_COUNT_IN_SHARING.ordinal(), deviceCountInSharing),
+ Pair.create(METRIC_KEY_CANDIDATE_DEVICE_COUNT.ordinal(), candidateDeviceCount)
+ };
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialogActivity.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialogActivity.java
index ddb0b42..88e2322 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialogActivity.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialogActivity.java
@@ -16,18 +16,92 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import android.content.Intent;
import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
import com.android.settings.SettingsActivity;
+import com.android.settings.bluetooth.Utils;
+import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
-public class AudioStreamConfirmDialogActivity extends SettingsActivity {
+public class AudioStreamConfirmDialogActivity extends SettingsActivity
+ implements LocalBluetoothProfileManager.ServiceListener {
+ private static final String TAG = "AudioStreamConfirmDialogActivity";
+ @Nullable private LocalBluetoothProfileManager mProfileManager;
+ @Nullable private Bundle mSavedState;
+ @Nullable private Intent mIntent;
+
+ @Override
+ protected boolean isToolbarEnabled() {
+ return false;
+ }
@Override
protected void onCreate(Bundle savedState) {
+ var localBluetoothManager = Utils.getLocalBluetoothManager(this);
+ mProfileManager =
+ localBluetoothManager == null ? null : localBluetoothManager.getProfileManager();
super.onCreate(savedState);
}
@Override
+ protected void createUiFromIntent(@Nullable Bundle savedState, Intent intent) {
+ if (AudioSharingUtils.isFeatureEnabled()
+ && !AudioSharingUtils.isAudioSharingProfileReady(mProfileManager)) {
+ Log.d(TAG, "createUiFromIntent() : supported but not ready, skip createUiFromIntent");
+ mSavedState = savedState;
+ mIntent = intent;
+ return;
+ }
+
+ Log.d(
+ TAG,
+ "createUiFromIntent() : not supported or already connected, starting"
+ + " createUiFromIntent");
+ super.createUiFromIntent(savedState, intent);
+ }
+
+ @Override
+ public void onStart() {
+ if (AudioSharingUtils.isFeatureEnabled()
+ && !AudioSharingUtils.isAudioSharingProfileReady(mProfileManager)) {
+ Log.d(TAG, "onStart() : supported but not ready, listen to service ready");
+ if (mProfileManager != null) {
+ mProfileManager.addServiceListener(this);
+ }
+ }
+ super.onStart();
+ }
+
+ @Override
+ public void onStop() {
+ if (mProfileManager != null) {
+ mProfileManager.removeServiceListener(this);
+ }
+ super.onStop();
+ }
+
+ @Override
+ public void onServiceConnected() {
+ if (AudioSharingUtils.isFeatureEnabled()
+ && AudioSharingUtils.isAudioSharingProfileReady(mProfileManager)) {
+ if (mProfileManager != null) {
+ mProfileManager.removeServiceListener(this);
+ }
+ if (mIntent != null) {
+ Log.d(TAG, "onServiceConnected() : service ready, starting createUiFromIntent");
+ super.createUiFromIntent(mSavedState, mIntent);
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected() {}
+
+ @Override
protected boolean isValidFragment(String fragmentName) {
return AudioStreamConfirmDialog.class.getName().equals(fragmentName);
}
diff --git a/src/com/android/settings/development/AdbPreferenceController.java b/src/com/android/settings/development/AdbPreferenceController.java
index 468c5bd..629dea1 100644
--- a/src/com/android/settings/development/AdbPreferenceController.java
+++ b/src/com/android/settings/development/AdbPreferenceController.java
@@ -28,9 +28,10 @@
public class AdbPreferenceController extends AbstractEnableAdbPreferenceController implements
PreferenceControllerMixin {
- private final DevelopmentSettingsDashboardFragment mFragment;
+ @Nullable private final DevelopmentSettingsDashboardFragment mFragment;
- public AdbPreferenceController(Context context, DevelopmentSettingsDashboardFragment fragment) {
+ public AdbPreferenceController(Context context,
+ @Nullable DevelopmentSettingsDashboardFragment fragment) {
super(context);
mFragment = fragment;
}
diff --git a/src/com/android/settings/development/BackAnimationPreferenceController.java b/src/com/android/settings/development/BackAnimationPreferenceController.java
index 09af27a..f87ee09 100644
--- a/src/com/android/settings/development/BackAnimationPreferenceController.java
+++ b/src/com/android/settings/development/BackAnimationPreferenceController.java
@@ -40,7 +40,7 @@
private static final int SETTING_VALUE_OFF = 0;
private static final int SETTING_VALUE_ON = 1;
- private final DevelopmentSettingsDashboardFragment mFragment;
+ @Nullable private final DevelopmentSettingsDashboardFragment mFragment;
@VisibleForTesting
BackAnimationPreferenceController(Context context) {
diff --git a/src/com/android/settings/development/BackgroundProcessLimitPreferenceController.java b/src/com/android/settings/development/BackgroundProcessLimitPreferenceController.java
new file mode 100644
index 0000000..7a7d6fa
--- /dev/null
+++ b/src/com/android/settings/development/BackgroundProcessLimitPreferenceController.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.development;
+
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.os.RemoteException;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.R;
+import com.android.settingslib.development.DeveloperOptionsPreferenceController;
+
+public class BackgroundProcessLimitPreferenceController extends
+ DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener,
+ PreferenceControllerMixin {
+
+ private static final String APP_PROCESS_LIMIT_KEY = "app_process_limit";
+
+ private final String[] mListValues;
+ private final String[] mListSummaries;
+
+ public BackgroundProcessLimitPreferenceController(Context context) {
+ super(context);
+
+ mListValues = context.getResources().getStringArray(R.array.app_process_limit_values);
+ mListSummaries = context.getResources().getStringArray(R.array.app_process_limit_entries);
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return APP_PROCESS_LIMIT_KEY;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ writeAppProcessLimitOptions(newValue);
+ updateAppProcessLimitOptions();
+ return true;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ updateAppProcessLimitOptions();
+ }
+
+ @Override
+ protected void onDeveloperOptionsSwitchDisabled() {
+ super.onDeveloperOptionsSwitchDisabled();
+ writeAppProcessLimitOptions(null);
+ }
+
+ private void updateAppProcessLimitOptions() {
+ try {
+ final int limit = getActivityManagerService().getProcessLimit();
+ int index = 0; // default
+ for (int i = 0; i < mListValues.length; i++) {
+ int val = Integer.parseInt(mListValues[i]);
+ if (val >= limit) {
+ index = i;
+ break;
+ }
+ }
+ final ListPreference listPreference = (ListPreference) mPreference;
+ listPreference.setValue(mListValues[index]);
+ listPreference.setSummary(mListSummaries[index]);
+ } catch (RemoteException e) {
+ // intentional no-op
+ }
+ }
+
+ private void writeAppProcessLimitOptions(Object newValue) {
+ try {
+ final int limit = newValue != null ? Integer.parseInt(newValue.toString()) : -1;
+ getActivityManagerService().setProcessLimit(limit);
+ updateAppProcessLimitOptions();
+ } catch (RemoteException e) {
+ // intentional no-op
+ }
+ }
+
+ @VisibleForTesting
+ IActivityManager getActivityManagerService() {
+ return ActivityManager.getService();
+ }
+}
diff --git a/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java b/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java
index b43303b..f460b9e 100644
--- a/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java
+++ b/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.os.SystemProperties;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
@@ -32,7 +33,7 @@
implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {
private static final String PREFERENCE_KEY = "bluetooth_disable_a2dp_hw_offload";
- private final DevelopmentSettingsDashboardFragment mFragment;
+ @Nullable private final DevelopmentSettingsDashboardFragment mFragment;
static final String A2DP_OFFLOAD_DISABLED_PROPERTY = "persist.bluetooth.a2dp_offload.disabled";
static final String A2DP_OFFLOAD_SUPPORTED_PROPERTY = "ro.bluetooth.a2dp_offload.supported";
@@ -41,7 +42,7 @@
boolean mChanged = false;
public BluetoothA2dpHwOffloadPreferenceController(Context context,
- DevelopmentSettingsDashboardFragment fragment) {
+ @Nullable DevelopmentSettingsDashboardFragment fragment) {
super(context);
mFragment = fragment;
}
diff --git a/src/com/android/settings/development/BluetoothLeAudioAllowListPreferenceController.java b/src/com/android/settings/development/BluetoothLeAudioAllowListPreferenceController.java
index 51533e7..4e9bd1d 100644
--- a/src/com/android/settings/development/BluetoothLeAudioAllowListPreferenceController.java
+++ b/src/com/android/settings/development/BluetoothLeAudioAllowListPreferenceController.java
@@ -49,12 +49,8 @@
BluetoothAdapter mBluetoothAdapter;
@VisibleForTesting boolean mLeAudioConnectionByDefault;
- private final DevelopmentSettingsDashboardFragment mFragment;
-
- public BluetoothLeAudioAllowListPreferenceController(Context context,
- DevelopmentSettingsDashboardFragment fragment) {
+ public BluetoothLeAudioAllowListPreferenceController(Context context) {
super(context);
- mFragment = fragment;
mBluetoothAdapter = context.getSystemService(BluetoothManager.class).getAdapter();
mLeAudioConnectionByDefault =
SystemProperties.getBoolean(LE_AUDIO_CONNECTION_BY_DEFAULT_PROPERTY, true);
diff --git a/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceController.java b/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceController.java
index 91b9eb8..1890fbd 100644
--- a/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceController.java
+++ b/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceController.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.os.SystemProperties;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
@@ -39,7 +40,7 @@
implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {
private static final String PREFERENCE_KEY = "bluetooth_disable_le_audio_hw_offload";
- private final DevelopmentSettingsDashboardFragment mFragment;
+ @Nullable private final DevelopmentSettingsDashboardFragment mFragment;
static final String LE_AUDIO_OFFLOAD_DISABLED_PROPERTY =
"persist.bluetooth.leaudio_offload.disabled";
@@ -53,7 +54,7 @@
boolean mChanged = false;
public BluetoothLeAudioHwOffloadPreferenceController(Context context,
- DevelopmentSettingsDashboardFragment fragment) {
+ @Nullable DevelopmentSettingsDashboardFragment fragment) {
super(context);
mFragment = fragment;
mBluetoothAdapter = context.getSystemService(BluetoothManager.class).getAdapter();
diff --git a/src/com/android/settings/development/BluetoothLeAudioPreferenceController.java b/src/com/android/settings/development/BluetoothLeAudioPreferenceController.java
index 2a544f2..c3b491a 100644
--- a/src/com/android/settings/development/BluetoothLeAudioPreferenceController.java
+++ b/src/com/android/settings/development/BluetoothLeAudioPreferenceController.java
@@ -23,6 +23,7 @@
import android.os.SystemProperties;
import android.sysprop.BluetoothProperties;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
@@ -45,7 +46,7 @@
static final String LE_AUDIO_SWITCHER_DISABLED_PROPERTY =
"persist.bluetooth.leaudio_switcher.disabled";
- private final DevelopmentSettingsDashboardFragment mFragment;
+ @Nullable private final DevelopmentSettingsDashboardFragment mFragment;
@VisibleForTesting
BluetoothAdapter mBluetoothAdapter;
@@ -54,7 +55,7 @@
boolean mChanged = false;
public BluetoothLeAudioPreferenceController(Context context,
- DevelopmentSettingsDashboardFragment fragment) {
+ @Nullable DevelopmentSettingsDashboardFragment fragment) {
super(context);
mFragment = fragment;
mBluetoothAdapter = context.getSystemService(BluetoothManager.class).getAdapter();
diff --git a/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java b/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java
index 1ef4810..9e05891 100644
--- a/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java
+++ b/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java
@@ -22,6 +22,7 @@
import android.provider.Settings;
import android.text.TextUtils;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
@@ -42,10 +43,10 @@
private final String[] mListValues;
private final String[] mListEntries;
- private DevelopmentSettingsDashboardFragment mFragment;
+ @Nullable private DevelopmentSettingsDashboardFragment mFragment;
public BluetoothSnoopLogPreferenceController(
- Context context, DevelopmentSettingsDashboardFragment fragment) {
+ Context context, @Nullable DevelopmentSettingsDashboardFragment fragment) {
super(context);
mListValues = context.getResources()
.getStringArray(com.android.settingslib.R.array.bt_hci_snoop_log_values);
diff --git a/src/com/android/settings/development/ClearAdbKeysPreferenceController.java b/src/com/android/settings/development/ClearAdbKeysPreferenceController.java
index 69e6c69..d1e797d 100644
--- a/src/com/android/settings/development/ClearAdbKeysPreferenceController.java
+++ b/src/com/android/settings/development/ClearAdbKeysPreferenceController.java
@@ -25,6 +25,7 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -40,10 +41,10 @@
private static final String CLEAR_ADB_KEYS = "clear_adb_keys";
private final IAdbManager mAdbManager;
- private final DevelopmentSettingsDashboardFragment mFragment;
+ @Nullable private final DevelopmentSettingsDashboardFragment mFragment;
public ClearAdbKeysPreferenceController(Context context,
- DevelopmentSettingsDashboardFragment fragment) {
+ @Nullable DevelopmentSettingsDashboardFragment fragment) {
super(context);
mFragment = fragment;
diff --git a/src/com/android/settings/development/DesktopModePreferenceController.java b/src/com/android/settings/development/DesktopModePreferenceController.java
index dcd0c14..c6b2397 100644
--- a/src/com/android/settings/development/DesktopModePreferenceController.java
+++ b/src/com/android/settings/development/DesktopModePreferenceController.java
@@ -22,6 +22,7 @@
import android.os.Build;
import android.provider.Settings;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
@@ -41,10 +42,10 @@
@VisibleForTesting
static final int SETTING_VALUE_ON = 1;
- private final DevelopmentSettingsDashboardFragment mFragment;
+ @Nullable private final DevelopmentSettingsDashboardFragment mFragment;
public DesktopModePreferenceController(
- Context context, DevelopmentSettingsDashboardFragment fragment) {
+ Context context, @Nullable DevelopmentSettingsDashboardFragment fragment) {
super(context);
mFragment = fragment;
}
diff --git a/src/com/android/settings/development/DevelopmentMemtagPagePreferenceController.java b/src/com/android/settings/development/DevelopmentMemtagPagePreferenceController.java
index 240079b..f80ebc6 100644
--- a/src/com/android/settings/development/DevelopmentMemtagPagePreferenceController.java
+++ b/src/com/android/settings/development/DevelopmentMemtagPagePreferenceController.java
@@ -26,8 +26,7 @@
implements PreferenceControllerMixin {
private static final String KEY_DEVELOPMENT_MEMTAG_PAGE = "development_memtag_page";
- public DevelopmentMemtagPagePreferenceController(
- Context context, DevelopmentSettingsDashboardFragment fragment) {
+ public DevelopmentMemtagPagePreferenceController(Context context) {
super(context);
}
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index dd9a1f0..38cb6c7 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -655,7 +655,7 @@
controllers.add(new BugReportPreferenceController(context));
controllers.add(new BugReportHandlerPreferenceController(context));
controllers.add(new SystemServerHeapDumpPreferenceController(context));
- controllers.add(new DevelopmentMemtagPagePreferenceController(context, fragment));
+ controllers.add(new DevelopmentMemtagPagePreferenceController(context));
controllers.add(new LocalBackupPasswordPreferenceController(context));
controllers.add(new StayAwakePreferenceController(context, lifecycle));
controllers.add(new HdcpCheckingPreferenceController(context));
@@ -706,7 +706,7 @@
controllers.add(new BluetoothLeAudioPreferenceController(context, fragment));
controllers.add(new BluetoothLeAudioModePreferenceController(context, fragment));
controllers.add(new BluetoothLeAudioDeviceDetailsPreferenceController(context));
- controllers.add(new BluetoothLeAudioAllowListPreferenceController(context, fragment));
+ controllers.add(new BluetoothLeAudioAllowListPreferenceController(context));
controllers.add(new BluetoothA2dpHwOffloadPreferenceController(context, fragment));
controllers.add(new BluetoothLeAudioHwOffloadPreferenceController(context, fragment));
controllers.add(new BluetoothMaxConnectedAudioDevicesPreferenceController(context));
@@ -740,6 +740,7 @@
controllers.add(new StrictModePreferenceController(context));
controllers.add(new ProfileGpuRenderingPreferenceController(context));
controllers.add(new KeepActivitiesPreferenceController(context));
+ controllers.add(new BackgroundProcessLimitPreferenceController(context));
controllers.add(new CachedAppsFreezerPreferenceController(context));
controllers.add(new ShowFirstCrashDialogPreferenceController(context));
controllers.add(new AppsNotRespondingPreferenceController(context));
diff --git a/src/com/android/settings/development/Enable16kPagesPreferenceController.java b/src/com/android/settings/development/Enable16kPagesPreferenceController.java
index 0572b1b..b782788 100644
--- a/src/com/android/settings/development/Enable16kPagesPreferenceController.java
+++ b/src/com/android/settings/development/Enable16kPagesPreferenceController.java
@@ -34,6 +34,7 @@
import android.widget.Toast;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
@@ -86,7 +87,7 @@
private static final int OFFSET_TO_FILE_NAME = 30;
public static final String EXPERIMENTAL_UPDATE_TITLE = "Android 16K Kernel Experimental Update";
- private @NonNull DevelopmentSettingsDashboardFragment mFragment;
+ private @Nullable DevelopmentSettingsDashboardFragment mFragment;
private boolean mEnable16k;
private final ListeningExecutorService mExecutorService =
@@ -95,7 +96,7 @@
private AlertDialog mProgressDialog;
public Enable16kPagesPreferenceController(
- @NonNull Context context, @NonNull DevelopmentSettingsDashboardFragment fragment) {
+ @NonNull Context context, @Nullable DevelopmentSettingsDashboardFragment fragment) {
super(context);
this.mFragment = fragment;
mEnable16k = Enable16kUtils.isUsing16kbPages();
diff --git a/src/com/android/settings/development/FreeformWindowsPreferenceController.java b/src/com/android/settings/development/FreeformWindowsPreferenceController.java
index 872c046..7cf7738 100644
--- a/src/com/android/settings/development/FreeformWindowsPreferenceController.java
+++ b/src/com/android/settings/development/FreeformWindowsPreferenceController.java
@@ -20,6 +20,7 @@
import android.os.Build;
import android.provider.Settings;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
@@ -39,10 +40,10 @@
@VisibleForTesting
static final int SETTING_VALUE_ON = 1;
- private final DevelopmentSettingsDashboardFragment mFragment;
+ @Nullable private final DevelopmentSettingsDashboardFragment mFragment;
public FreeformWindowsPreferenceController(
- Context context, DevelopmentSettingsDashboardFragment fragment) {
+ Context context, @Nullable DevelopmentSettingsDashboardFragment fragment) {
super(context);
mFragment = fragment;
}
diff --git a/src/com/android/settings/development/LogPersistPreferenceController.java b/src/com/android/settings/development/LogPersistPreferenceController.java
index 1386cec..66c128c 100644
--- a/src/com/android/settings/development/LogPersistPreferenceController.java
+++ b/src/com/android/settings/development/LogPersistPreferenceController.java
@@ -28,10 +28,10 @@
public class LogPersistPreferenceController extends AbstractLogpersistPreferenceController
implements PreferenceControllerMixin {
- private final DevelopmentSettingsDashboardFragment mFragment;
+ @Nullable private final DevelopmentSettingsDashboardFragment mFragment;
public LogPersistPreferenceController(Context context,
- DevelopmentSettingsDashboardFragment fragment, Lifecycle lifecycle) {
+ @Nullable DevelopmentSettingsDashboardFragment fragment, Lifecycle lifecycle) {
super(context, lifecycle);
mFragment = fragment;
diff --git a/src/com/android/settings/development/MockLocationAppPreferenceController.java b/src/com/android/settings/development/MockLocationAppPreferenceController.java
index d927c31..51753fe 100644
--- a/src/com/android/settings/development/MockLocationAppPreferenceController.java
+++ b/src/com/android/settings/development/MockLocationAppPreferenceController.java
@@ -30,6 +30,7 @@
import android.provider.Settings;
import android.text.TextUtils;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
@@ -45,12 +46,12 @@
private static final String MOCK_LOCATION_APP_KEY = "mock_location_app";
private static final int[] MOCK_LOCATION_APP_OPS = new int[]{AppOpsManager.OP_MOCK_LOCATION};
- private final DevelopmentSettingsDashboardFragment mFragment;
+ @Nullable private final DevelopmentSettingsDashboardFragment mFragment;
private final AppOpsManager mAppsOpsManager;
private final PackageManager mPackageManager;
public MockLocationAppPreferenceController(Context context,
- DevelopmentSettingsDashboardFragment fragment) {
+ @Nullable DevelopmentSettingsDashboardFragment fragment) {
super(context);
mFragment = fragment;
diff --git a/src/com/android/settings/development/NfcSnoopLogPreferenceController.java b/src/com/android/settings/development/NfcSnoopLogPreferenceController.java
index 01fbb92..ce9d04d 100644
--- a/src/com/android/settings/development/NfcSnoopLogPreferenceController.java
+++ b/src/com/android/settings/development/NfcSnoopLogPreferenceController.java
@@ -20,6 +20,7 @@
import android.os.SystemProperties;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
@@ -45,10 +46,10 @@
@VisibleForTesting
boolean mChanged = false;
- private final DevelopmentSettingsDashboardFragment mFragment;
+ @Nullable private final DevelopmentSettingsDashboardFragment mFragment;
public NfcSnoopLogPreferenceController(Context context,
- DevelopmentSettingsDashboardFragment fragment) {
+ @Nullable DevelopmentSettingsDashboardFragment fragment) {
super(context);
mFragment = fragment;
}
diff --git a/src/com/android/settings/development/NfcVerboseVendorLogPreferenceController.java b/src/com/android/settings/development/NfcVerboseVendorLogPreferenceController.java
index d0f9d9e..2644bbd 100644
--- a/src/com/android/settings/development/NfcVerboseVendorLogPreferenceController.java
+++ b/src/com/android/settings/development/NfcVerboseVendorLogPreferenceController.java
@@ -20,6 +20,7 @@
import android.os.SystemProperties;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
@@ -45,10 +46,10 @@
@VisibleForTesting
boolean mChanged = false;
- private final DevelopmentSettingsDashboardFragment mFragment;
+ @Nullable private final DevelopmentSettingsDashboardFragment mFragment;
public NfcVerboseVendorLogPreferenceController(Context context,
- DevelopmentSettingsDashboardFragment fragment) {
+ @Nullable DevelopmentSettingsDashboardFragment fragment) {
super(context);
mFragment = fragment;
}
diff --git a/src/com/android/settings/development/OemUnlockPreferenceController.java b/src/com/android/settings/development/OemUnlockPreferenceController.java
index 2542a11..3053def 100644
--- a/src/com/android/settings/development/OemUnlockPreferenceController.java
+++ b/src/com/android/settings/development/OemUnlockPreferenceController.java
@@ -31,6 +31,7 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -54,11 +55,11 @@
private final UserManager mUserManager;
private final TelephonyManager mTelephonyManager;
private final Activity mActivity;
- private final DevelopmentSettingsDashboardFragment mFragment;
+ @Nullable private final DevelopmentSettingsDashboardFragment mFragment;
private RestrictedSwitchPreference mPreference;
public OemUnlockPreferenceController(Context context, Activity activity,
- DevelopmentSettingsDashboardFragment fragment) {
+ @Nullable DevelopmentSettingsDashboardFragment fragment) {
super(context);
if (!TextUtils.equals(SystemProperties.get(OEM_UNLOCK_SUPPORTED_KEY, UNSUPPORTED),
diff --git a/src/com/android/settings/development/SelectDebugAppPreferenceController.java b/src/com/android/settings/development/SelectDebugAppPreferenceController.java
index d691149..df524c4 100644
--- a/src/com/android/settings/development/SelectDebugAppPreferenceController.java
+++ b/src/com/android/settings/development/SelectDebugAppPreferenceController.java
@@ -28,6 +28,7 @@
import android.provider.Settings;
import android.text.TextUtils;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
@@ -40,11 +41,11 @@
private static final String DEBUG_APP_KEY = "debug_app";
- private final DevelopmentSettingsDashboardFragment mFragment;
+ @Nullable private final DevelopmentSettingsDashboardFragment mFragment;
private final PackageManager mPackageManager;
public SelectDebugAppPreferenceController(Context context,
- DevelopmentSettingsDashboardFragment fragment) {
+ @Nullable DevelopmentSettingsDashboardFragment fragment) {
super(context);
mFragment = fragment;
mPackageManager = mContext.getPackageManager();
diff --git a/src/com/android/settings/development/bluetooth/BluetoothCodecDialogPreferenceController.java b/src/com/android/settings/development/bluetooth/BluetoothCodecDialogPreferenceController.java
index b7b5574..6b29b8e 100644
--- a/src/com/android/settings/development/bluetooth/BluetoothCodecDialogPreferenceController.java
+++ b/src/com/android/settings/development/bluetooth/BluetoothCodecDialogPreferenceController.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceScreen;
@@ -41,11 +42,11 @@
private static final String KEY = "bluetooth_audio_codec_settings";
private static final String TAG = "BtCodecCtr";
- private final Callback mCallback;
+ @Nullable private final Callback mCallback;
public BluetoothCodecDialogPreferenceController(Context context, Lifecycle lifecycle,
BluetoothA2dpConfigStore store,
- Callback callback) {
+ @Nullable Callback callback) {
super(context, lifecycle, store);
mCallback = callback;
}
diff --git a/src/com/android/settings/development/bluetooth/BluetoothHDAudioPreferenceController.java b/src/com/android/settings/development/bluetooth/BluetoothHDAudioPreferenceController.java
index feaa36e..878ddb6 100644
--- a/src/com/android/settings/development/bluetooth/BluetoothHDAudioPreferenceController.java
+++ b/src/com/android/settings/development/bluetooth/BluetoothHDAudioPreferenceController.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
@@ -36,11 +37,11 @@
private static final String KEY = "bluetooth_hd_audio_settings";
private static final String TAG = "BtHDAudioCtr";
- private final Callback mCallback;
+ @Nullable private final Callback mCallback;
public BluetoothHDAudioPreferenceController(Context context, Lifecycle lifecycle,
BluetoothA2dpConfigStore store,
- Callback callback) {
+ @Nullable Callback callback) {
super(context, lifecycle, store);
mCallback = callback;
}
diff --git a/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverController.java b/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverController.java
index b107501..e7ee9e1 100644
--- a/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverController.java
+++ b/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverController.java
@@ -23,6 +23,7 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
@@ -45,7 +46,7 @@
private static final String ENABLE_ANELE_AS_SYSTEM_DRIVER_KEY = "enable_angle_as_system_driver";
- private final DevelopmentSettingsDashboardFragment mFragment;
+ @Nullable private final DevelopmentSettingsDashboardFragment mFragment;
private final GraphicsDriverSystemPropertiesWrapper mSystemProperties;
@@ -83,7 +84,7 @@
}
public GraphicsDriverEnableAngleAsSystemDriverController(
- Context context, DevelopmentSettingsDashboardFragment fragment) {
+ Context context, @Nullable DevelopmentSettingsDashboardFragment fragment) {
this(context, fragment, new Injector());
}
@@ -96,7 +97,7 @@
@VisibleForTesting
GraphicsDriverEnableAngleAsSystemDriverController(
- Context context, DevelopmentSettingsDashboardFragment fragment, Injector injector) {
+ Context context, @Nullable DevelopmentSettingsDashboardFragment fragment, Injector injector) {
super(context);
mFragment = fragment;
mSystemProperties = injector.createSystemPropertiesWrapper();
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
index 5b28abb..0bb6286 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
@@ -66,10 +66,10 @@
public final class DatabaseUtils {
private static final String TAG = "DatabaseUtils";
private static final String SHARED_PREFS_FILE = "battery_usage_shared_prefs";
+ private static final boolean EXPLICIT_CLEAR_MEMORY_ENABLED = false;
/** 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;
@@ -975,7 +975,8 @@
}
private static void clearMemory() {
- if (SystemClock.uptimeMillis() > CLEAR_MEMORY_THRESHOLD_MS) {
+ if (!EXPLICIT_CLEAR_MEMORY_ENABLED
+ || SystemClock.uptimeMillis() > CLEAR_MEMORY_THRESHOLD_MS) {
return;
}
final Handler mainHandler = new Handler(Looper.getMainLooper());
diff --git a/src/com/android/settings/network/ConnectivityRepository.kt b/src/com/android/settings/network/ConnectivityRepository.kt
new file mode 100644
index 0000000..3f9b61c
--- /dev/null
+++ b/src/com/android/settings/network/ConnectivityRepository.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.network
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.util.Log
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOn
+
+class ConnectivityRepository(context: Context) {
+ private val connectivityManager = context.getSystemService(ConnectivityManager::class.java)!!
+
+ fun networkCapabilitiesFlow(): Flow<NetworkCapabilities> = callbackFlow {
+ val callback = object : NetworkCallback() {
+ override fun onCapabilitiesChanged(
+ network: Network,
+ networkCapabilities: NetworkCapabilities,
+ ) {
+ trySend(networkCapabilities)
+ Log.d(TAG, "onCapabilitiesChanged: $networkCapabilities")
+ }
+
+ override fun onLost(network: Network) {
+ trySend(NetworkCapabilities())
+ Log.d(TAG, "onLost")
+ }
+ }
+ trySend(getNetworkCapabilities())
+ connectivityManager.registerDefaultNetworkCallback(callback)
+
+ awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ }.conflate().flowOn(Dispatchers.Default)
+
+ private fun getNetworkCapabilities(): NetworkCapabilities =
+ connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
+ ?: NetworkCapabilities()
+
+ private companion object {
+ private const val TAG = "ConnectivityRepository"
+ }
+}
diff --git a/src/com/android/settings/network/InternetPreferenceControllerV2.kt b/src/com/android/settings/network/InternetPreferenceControllerV2.kt
index f9d5618..351aca8 100644
--- a/src/com/android/settings/network/InternetPreferenceControllerV2.kt
+++ b/src/com/android/settings/network/InternetPreferenceControllerV2.kt
@@ -22,7 +22,6 @@
import androidx.preference.PreferenceScreen
import com.android.settings.R
import com.android.settings.core.BasePreferenceController
-import com.android.settings.wifi.WifiSummaryRepository
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
class InternetPreferenceControllerV2(context: Context, preferenceKey: String) :
@@ -40,7 +39,7 @@
}
override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
- WifiSummaryRepository(mContext).summaryFlow()
+ InternetPreferenceRepository(mContext).summaryFlow()
.collectLatestWithLifecycle(viewLifecycleOwner) {
preference?.summary = it
}
diff --git a/src/com/android/settings/network/InternetPreferenceRepository.kt b/src/com/android/settings/network/InternetPreferenceRepository.kt
new file mode 100644
index 0000000..30a98d7
--- /dev/null
+++ b/src/com/android/settings/network/InternetPreferenceRepository.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.network
+
+import android.content.Context
+import android.net.NetworkCapabilities
+import android.net.wifi.WifiManager
+import android.provider.Settings
+import android.util.Log
+import com.android.settings.R
+import com.android.settings.wifi.WifiSummaryRepository
+import com.android.settings.wifi.repository.WifiRepository
+import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBooleanFlow
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onEach
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class InternetPreferenceRepository(
+ private val context: Context,
+ private val connectivityRepository: ConnectivityRepository = ConnectivityRepository(context),
+ private val wifiSummaryRepository: WifiSummaryRepository = WifiSummaryRepository(context),
+ private val wifiRepository: WifiRepository = WifiRepository(context),
+ private val airplaneModeOnFlow: Flow<Boolean> =
+ context.settingsGlobalBooleanFlow(Settings.Global.AIRPLANE_MODE_ON),
+) {
+
+ fun summaryFlow(): Flow<String> = connectivityRepository.networkCapabilitiesFlow()
+ .flatMapLatest { capabilities -> capabilities.summaryFlow() }
+ .onEach { Log.d(TAG, "summaryFlow: $it") }
+ .conflate()
+ .flowOn(Dispatchers.Default)
+
+ private fun NetworkCapabilities.summaryFlow(): Flow<String> {
+ if (hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
+ hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ ) {
+ for (transportType in transportTypes) {
+ if (transportType == NetworkCapabilities.TRANSPORT_WIFI) {
+ return wifiSummaryRepository.summaryFlow()
+ }
+ }
+ }
+ return defaultSummaryFlow()
+ }
+
+ private fun defaultSummaryFlow(): Flow<String> = combine(
+ airplaneModeOnFlow,
+ wifiRepository.wifiStateFlow(),
+ ) { airplaneModeOn: Boolean, wifiState: Int ->
+ context.getString(
+ if (airplaneModeOn && wifiState != WifiManager.WIFI_STATE_ENABLED) {
+ R.string.condition_airplane_title
+ } else {
+ R.string.networks_available
+ }
+ )
+ }
+
+ private companion object {
+ private const val TAG = "InternetPreferenceRepo"
+ }
+}
diff --git a/src/com/android/settings/network/ResetNetworkOperationBuilder.java b/src/com/android/settings/network/ResetNetworkOperationBuilder.java
index 4067fba..6f36074 100644
--- a/src/com/android/settings/network/ResetNetworkOperationBuilder.java
+++ b/src/com/android/settings/network/ResetNetworkOperationBuilder.java
@@ -256,16 +256,19 @@
* @return this
*/
public ResetNetworkOperationBuilder restartPhoneProcess() {
- try {
- mContext.getContentResolver().call(
- getResetTelephonyContentProviderAuthority(),
- METHOD_RESTART_PHONE_PROCESS,
- /* arg= */ null,
- /* extras= */ null);
- Log.i(TAG, "Phone process was restarted.");
- } catch (IllegalArgumentException iae) {
- Log.w(TAG, "Fail to restart phone process: " + iae);
- }
+ Runnable runnable = () -> {
+ try {
+ mContext.getContentResolver().call(
+ getResetTelephonyContentProviderAuthority(),
+ METHOD_RESTART_PHONE_PROCESS,
+ /* arg= */ null,
+ /* extras= */ null);
+ Log.i(TAG, "Phone process was restarted.");
+ } catch (IllegalArgumentException iae) {
+ Log.w(TAG, "Fail to restart phone process: " + iae);
+ }
+ };
+ mResetSequence.add(runnable);
return this;
}
@@ -275,16 +278,19 @@
* @return this
*/
public ResetNetworkOperationBuilder restartRild() {
- try {
- mContext.getContentResolver().call(
- getResetTelephonyContentProviderAuthority(),
- METHOD_RESTART_RILD,
- /* arg= */ null,
- /* extras= */ null);
- Log.i(TAG, "RILD was restarted.");
- } catch (IllegalArgumentException iae) {
- Log.w(TAG, "Fail to restart RILD: " + iae);
- }
+ Runnable runnable = () -> {
+ try {
+ mContext.getContentResolver().call(
+ getResetTelephonyContentProviderAuthority(),
+ METHOD_RESTART_RILD,
+ /* arg= */ null,
+ /* extras= */ null);
+ Log.i(TAG, "RILD was restarted.");
+ } catch (IllegalArgumentException iae) {
+ Log.w(TAG, "Fail to restart RILD: " + iae);
+ }
+ };
+ mResetSequence.add(runnable);
return this;
}
diff --git a/src/com/android/settings/network/apn/ApnSettings.java b/src/com/android/settings/network/apn/ApnSettings.java
index 5249eb2..2debba1 100644
--- a/src/com/android/settings/network/apn/ApnSettings.java
+++ b/src/com/android/settings/network/apn/ApnSettings.java
@@ -99,6 +99,8 @@
private UserManager mUserManager;
private int mSubId;
private PreferredApnRepository mPreferredApnRepository;
+ @Nullable
+ private String mPreferredApnKey;
private String mMvnoType;
private String mMvnoMatchData;
@@ -175,6 +177,7 @@
});
mPreferredApnRepository.collectPreferredApn(viewLifecycleOwner, (preferredApn) -> {
+ mPreferredApnKey = preferredApn;
final PreferenceGroup apnPreferenceList = findPreference(APN_LIST);
for (int i = 0; i < apnPreferenceList.getPreferenceCount(); i++) {
ApnPreference apnPreference = (ApnPreference) apnPreferenceList.getPreference(i);
@@ -259,6 +262,7 @@
((type == null) || type.contains(ApnSetting.TYPE_DEFAULT_STRING));
pref.setDefaultSelectable(defaultSelectable);
if (defaultSelectable) {
+ pref.setIsChecked(key.equals(mPreferredApnKey));
apnList.add(pref);
} else {
mmsApnList.add(pref);
diff --git a/src/com/android/settings/notification/modes/IconUtil.java b/src/com/android/settings/notification/modes/IconUtil.java
new file mode 100644
index 0000000..c6ecaa0
--- /dev/null
+++ b/src/com/android/settings/notification/modes/IconUtil.java
@@ -0,0 +1,69 @@
+/*
+ * 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 android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+
+import com.android.settings.R;
+import com.android.settingslib.Utils;
+
+class IconUtil {
+
+ static Drawable applyTint(@NonNull Context context, @NonNull Drawable icon) {
+ icon = icon.mutate();
+ icon.setTintList(
+ Utils.getColorAttr(context, android.R.attr.colorControlNormal));
+ return icon;
+ }
+
+ /**
+ * Returns a variant of the supplied {@code icon} to be used in the icon picker. The inner icon
+ * is 36x36dp and it's contained into a circle of diameter 54dp.
+ */
+ static Drawable makeIconCircle(@NonNull Context context, @NonNull Drawable icon) {
+ ShapeDrawable background = new ShapeDrawable(new OvalShape());
+ background.getPaint().setColor(Utils.getColorAttrDefaultColor(context,
+ com.android.internal.R.attr.materialColorSecondaryContainer));
+ icon.setTint(Utils.getColorAttrDefaultColor(context,
+ com.android.internal.R.attr.materialColorOnSecondaryContainer));
+
+ LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { background, icon });
+
+ int circleDiameter = context.getResources().getDimensionPixelSize(
+ R.dimen.zen_mode_icon_list_circle_diameter);
+ int iconSize = context.getResources().getDimensionPixelSize(
+ R.dimen.zen_mode_icon_list_icon_size);
+ int iconPadding = (circleDiameter - iconSize) / 2;
+ layerDrawable.setBounds(0, 0, circleDiameter, circleDiameter);
+ layerDrawable.setLayerInset(1, iconPadding, iconPadding, iconPadding, iconPadding);
+
+ return layerDrawable;
+ }
+
+ static Drawable makeIconCircle(@NonNull Context context, @DrawableRes int iconResId) {
+ return makeIconCircle(context, checkNotNull(context.getDrawable(iconResId)));
+ }
+}
diff --git a/src/com/android/settings/notification/modes/ZenMode.java b/src/com/android/settings/notification/modes/ZenMode.java
index 1be7e5f..aca959f 100644
--- a/src/com/android/settings/notification/modes/ZenMode.java
+++ b/src/com/android/settings/notification/modes/ZenMode.java
@@ -204,6 +204,14 @@
: new ZenDeviceEffects.Builder().build();
}
+ public boolean canEditName() {
+ return !isManualDnd();
+ }
+
+ public boolean canEditIcon() {
+ return !isManualDnd();
+ }
+
public boolean canBeDeleted() {
return !mIsManualDnd;
}
diff --git a/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java
new file mode 100644
index 0000000..5695fbc
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java
@@ -0,0 +1,64 @@
+/*
+ * 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.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settingslib.widget.ActionButtonsPreference;
+
+class ZenModeActionsPreferenceController extends AbstractZenModePreferenceController {
+
+ private ActionButtonsPreference mPreference;
+
+ ZenModeActionsPreferenceController(@NonNull Context context, @NonNull String key,
+ @Nullable ZenModesBackend backend) {
+ super(context, key, backend);
+ }
+
+ @Override
+ void updateState(Preference preference, @NonNull ZenMode zenMode) {
+ ActionButtonsPreference buttonsPreference = (ActionButtonsPreference) preference;
+
+ // TODO: b/346278854 - Add rename action (with setButton1Enabled(zenMode.canEditName())
+ buttonsPreference.setButton1Text(R.string.zen_mode_action_change_name);
+ buttonsPreference.setButton1Icon(R.drawable.ic_mode_edit);
+ buttonsPreference.setButton1Enabled(false);
+
+ buttonsPreference.setButton2Text(R.string.zen_mode_action_change_icon);
+ buttonsPreference.setButton2Icon(R.drawable.ic_zen_mode_action_change_icon);
+ buttonsPreference.setButton2Enabled(zenMode.canEditIcon());
+ buttonsPreference.setButton2OnClickListener(v -> {
+ Bundle bundle = new Bundle();
+ bundle.putString(MODE_ID, zenMode.getId());
+ new SubSettingLauncher(mContext)
+ .setDestination(ZenModeIconPickerFragment.class.getName())
+ // TODO: b/332937635 - Update metrics category
+ .setSourceMetricsCategory(0)
+ .setArguments(bundle)
+ .launch();
+ });
+ }
+}
diff --git a/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceController.java
new file mode 100644
index 0000000..8517af1
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceController.java
@@ -0,0 +1,56 @@
+/*
+ * 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.content.Context;
+import android.service.notification.ZenModeConfig;
+
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+import androidx.preference.TwoStatePreference;
+
+/**
+ * Preference controller controlling whether a time schedule-based mode ends at the next alarm.
+ */
+class ZenModeExitAtAlarmPreferenceController extends
+ AbstractZenModePreferenceController implements Preference.OnPreferenceChangeListener {
+ private ZenModeConfig.ScheduleInfo mSchedule;
+
+ ZenModeExitAtAlarmPreferenceController(Context context,
+ String key, ZenModesBackend backend) {
+ super(context, key, backend);
+ }
+
+ @Override
+ public void updateState(Preference preference, @NonNull ZenMode zenMode) {
+ mSchedule = ZenModeConfig.tryParseScheduleConditionId(zenMode.getRule().getConditionId());
+ ((TwoStatePreference) preference).setChecked(mSchedule.exitAtAlarm);
+ }
+
+ @Override
+ public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
+ final boolean exitAtAlarm = (Boolean) newValue;
+ if (mSchedule.exitAtAlarm != exitAtAlarm) {
+ mSchedule.exitAtAlarm = exitAtAlarm;
+ return saveMode(mode -> {
+ mode.getRule().setConditionId(ZenModeConfig.toScheduleConditionId(mSchedule));
+ return mode;
+ });
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/settings/notification/modes/ZenModeFragment.java b/src/com/android/settings/notification/modes/ZenModeFragment.java
index 7084f51..87165b8 100644
--- a/src/com/android/settings/notification/modes/ZenModeFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeFragment.java
@@ -38,6 +38,7 @@
List<AbstractPreferenceController> prefControllers = new ArrayList<>();
prefControllers.add(new ZenModeHeaderController(context, "header", this, mBackend));
prefControllers.add(new ZenModeButtonPreferenceController(context, "activate", mBackend));
+ prefControllers.add(new ZenModeActionsPreferenceController(context, "actions", mBackend));
prefControllers.add(new ZenModePeopleLinkPreferenceController(
context, "zen_mode_people", mBackend));
prefControllers.add(new ZenModeAppsLinkPreferenceController(
diff --git a/src/com/android/settings/notification/modes/ZenModeFragmentBase.java b/src/com/android/settings/notification/modes/ZenModeFragmentBase.java
index 5e6cfa5..e086524 100644
--- a/src/com/android/settings/notification/modes/ZenModeFragmentBase.java
+++ b/src/com/android/settings/notification/modes/ZenModeFragmentBase.java
@@ -51,12 +51,12 @@
if (bundle != null && bundle.containsKey(MODE_ID)) {
String id = bundle.getString(MODE_ID);
if (!reloadMode(id)) {
- Log.d(TAG, "Mode id " + id + " not found");
+ Log.e(TAG, "Mode id " + id + " not found");
toastAndFinish();
return;
}
} else {
- Log.d(TAG, "Mode id required to set mode config settings");
+ Log.e(TAG, "Mode id required to set mode config settings");
toastAndFinish();
return;
}
diff --git a/src/com/android/settings/notification/modes/ZenModeHeaderController.java b/src/com/android/settings/notification/modes/ZenModeHeaderController.java
index ba6e9d9..d8f0a67 100644
--- a/src/com/android/settings/notification/modes/ZenModeHeaderController.java
+++ b/src/com/android/settings/notification/modes/ZenModeHeaderController.java
@@ -63,9 +63,8 @@
FutureUtil.whenDone(
zenMode.getIcon(mContext, IconLoader.getInstance()),
- icon -> mHeaderController.setIcon(icon)
- .setLabel(zenMode.getRule().getName())
- .done(false /* rebindActions */),
+ icon -> mHeaderController.setIcon(IconUtil.applyTint(mContext, icon))
+ .done(/* rebindActions= */ false),
mContext.getMainExecutor());
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModeIconPickerFragment.java b/src/com/android/settings/notification/modes/ZenModeIconPickerFragment.java
new file mode 100644
index 0000000..950849e
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeIconPickerFragment.java
@@ -0,0 +1,49 @@
+/*
+ * 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.app.settings.SettingsEnums;
+import android.content.Context;
+
+import com.android.settings.R;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+public class ZenModeIconPickerFragment extends ZenModeFragmentBase {
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.modes_icon_picker;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ // TODO: b/332937635 - make this the correct metrics category
+ return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION;
+ }
+
+ @Override
+ protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+ return ImmutableList.of(
+ new ZenModeIconPickerIconPreferenceController(context, "current_icon", this,
+ mBackend),
+ new ZenModeIconPickerListPreferenceController(context, "icon_list", this,
+ mBackend));
+ }
+}
diff --git a/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java
new file mode 100644
index 0000000..9eaaa97
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java
@@ -0,0 +1,59 @@
+/*
+ * 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.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.widget.EntityHeaderController;
+import com.android.settingslib.widget.LayoutPreference;
+
+class ZenModeIconPickerIconPreferenceController extends AbstractZenModePreferenceController {
+
+ private final DashboardFragment mFragment;
+ private EntityHeaderController mHeaderController;
+
+ ZenModeIconPickerIconPreferenceController(@NonNull Context context, @NonNull String key,
+ @NonNull DashboardFragment fragment, @Nullable ZenModesBackend backend) {
+ super(context, key, backend);
+ mFragment = fragment;
+ }
+
+ @Override
+ void updateState(Preference preference, @NonNull ZenMode zenMode) {
+ preference.setSelectable(false);
+
+ if (mHeaderController == null) {
+ final LayoutPreference pref = (LayoutPreference) preference;
+ mHeaderController = EntityHeaderController.newInstance(
+ mFragment.getActivity(),
+ mFragment,
+ pref.findViewById(R.id.entity_header));
+ }
+
+ FutureUtil.whenDone(
+ zenMode.getIcon(mContext, IconLoader.getInstance()),
+ icon -> mHeaderController.setIcon(IconUtil.applyTint(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
new file mode 100644
index 0000000..b07c26f
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java
@@ -0,0 +1,170 @@
+/*
+ * 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.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settingslib.widget.LayoutPreference;
+
+import com.google.common.collect.ImmutableList;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+
+class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenceController {
+
+ private final DashboardFragment mFragment;
+ private IconAdapter mAdapter;
+
+ ZenModeIconPickerListPreferenceController(@NonNull Context context, @NonNull String key,
+ @NonNull DashboardFragment fragment, @Nullable ZenModesBackend backend) {
+ super(context, key, backend);
+ mFragment = fragment;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ LayoutPreference pref = screen.findPreference(getPreferenceKey());
+ if (pref == null) {
+ return;
+ }
+
+ if (mAdapter == null) {
+ // TODO: b/333901673 - This is just an example; replace with correct list.
+ List<IconInfo> exampleIcons =
+ Arrays.stream(android.R.drawable.class.getFields())
+ .filter(
+ f -> Modifier.isStatic(f.getModifiers())
+ && f.getName().startsWith("ic_"))
+ .sorted(Comparator.comparing(Field::getName))
+ .limit(20)
+ .map(f -> {
+ try {
+ return new IconInfo(f.getInt(null), f.getName());
+ } catch (IllegalAccessException e) {
+ return null;
+ }
+ })
+ .filter(Objects::nonNull)
+ .toList();
+ mAdapter = new IconAdapter(exampleIcons);
+ }
+ RecyclerView recyclerView = pref.findViewById(R.id.icon_list);
+ recyclerView.setLayoutManager(new AutoFitGridLayoutManager(mContext));
+ recyclerView.setAdapter(mAdapter);
+ recyclerView.setHasFixedSize(true);
+ }
+
+ @VisibleForTesting
+ void onIconSelected(@DrawableRes int resId) {
+ saveMode(mode -> {
+ mode.getRule().setIconResId(resId);
+ return mode;
+ });
+ mFragment.finish();
+ }
+
+ @Override
+ void updateState(Preference preference, @NonNull ZenMode zenMode) {
+ // Nothing to do, the current icon is shown in a different preference.
+ }
+
+ private record IconInfo(@DrawableRes int resId, String description) { }
+
+ private class IconHolder extends RecyclerView.ViewHolder {
+
+ private final ImageView mImageView;
+
+ IconHolder(@NonNull View itemView) {
+ super(itemView);
+ mImageView = itemView.findViewById(R.id.icon_image_view);
+ }
+
+ void bindIcon(IconInfo icon) {
+ mImageView.setImageDrawable(
+ IconUtil.makeIconCircle(itemView.getContext(), icon.resId()));
+ itemView.setContentDescription(icon.description());
+ itemView.setOnClickListener(v -> onIconSelected(icon.resId()));
+ }
+ }
+
+ private class IconAdapter extends RecyclerView.Adapter<IconHolder> {
+
+ private final ImmutableList<IconInfo> mIconResources;
+
+ private IconAdapter(List<IconInfo> iconOptions) {
+ mIconResources = ImmutableList.copyOf(iconOptions);
+ }
+
+ @NonNull
+ @Override
+ public IconHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View itemView = LayoutInflater.from(parent.getContext()).inflate(
+ R.layout.modes_icon_list_item, parent, false);
+ return new IconHolder(itemView);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull IconHolder holder, int position) {
+ holder.bindIcon(mIconResources.get(position));
+ }
+
+ @Override
+ public int getItemCount() {
+ return mIconResources.size();
+ }
+ }
+
+ private static class AutoFitGridLayoutManager extends GridLayoutManager {
+ private final float mColumnWidth;
+
+ AutoFitGridLayoutManager(Context context) {
+ super(context, /* spanCount= */ 1);
+ this.mColumnWidth = context
+ .getResources()
+ .getDimensionPixelSize(R.dimen.zen_mode_icon_list_item_size);
+ }
+
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ final int totalSpace = getWidth() - getPaddingRight() - getPaddingLeft();
+ final int spanCount = Math.max(1, (int) (totalSpace / mColumnWidth));
+ setSpanCount(spanCount);
+ super.onLayoutChildren(recycler, state);
+ }
+ }
+}
diff --git a/src/com/android/settings/notification/modes/ZenModeListPreference.java b/src/com/android/settings/notification/modes/ZenModeListPreference.java
index a106bdd..c3daa61 100644
--- a/src/com/android/settings/notification/modes/ZenModeListPreference.java
+++ b/src/com/android/settings/notification/modes/ZenModeListPreference.java
@@ -23,7 +23,6 @@
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.RestrictedPreference;
-import com.android.settingslib.Utils;
/**
* Preference representing a single mode item on the modes aggregator page. Clicking on this
@@ -59,11 +58,7 @@
FutureUtil.whenDone(
mZenMode.getIcon(mContext, IconLoader.getInstance()),
- icon -> {
- icon.setTintList(
- Utils.getColorAttr(mContext, android.R.attr.colorControlNormal));
- setIcon(icon);
- },
+ icon -> setIcon(IconUtil.applyTint(mContext, icon)),
mContext.getMainExecutor());
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModeSetScheduleFragment.java b/src/com/android/settings/notification/modes/ZenModeSetScheduleFragment.java
new file mode 100644
index 0000000..4d58097
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeSetScheduleFragment.java
@@ -0,0 +1,54 @@
+/*
+ * 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.app.settings.SettingsEnums;
+import android.content.Context;
+
+import com.android.settings.R;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Settings page to set a schedule for a mode that turns on automatically based on specific days
+ * of the week and times of day.
+ */
+public class ZenModeSetScheduleFragment extends ZenModeFragmentBase {
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.modes_set_schedule;
+ }
+
+ @Override
+ protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+ List<AbstractPreferenceController> controllers = new ArrayList<>();
+ controllers.add(
+ new ZenModeSetSchedulePreferenceController(mContext, this, "schedule", mBackend));
+ controllers.add(
+ new ZenModeExitAtAlarmPreferenceController(mContext, "exit_at_alarm", mBackend));
+ return controllers;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ // TODO: b/332937635 - make this the correct metrics category
+ return SettingsEnums.NOTIFICATION_ZEN_MODE_SCHEDULE_RULE;
+ }
+}
diff --git a/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceController.java
new file mode 100644
index 0000000..a6008cc
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceController.java
@@ -0,0 +1,274 @@
+/*
+ * 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.app.Flags;
+import android.content.Context;
+import android.service.notification.SystemZenRules;
+import android.service.notification.ZenModeConfig;
+import android.text.format.DateFormat;
+import android.util.ArraySet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import android.widget.ToggleButton;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.fragment.app.Fragment;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settingslib.widget.LayoutPreference;
+
+import java.text.SimpleDateFormat;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.function.Function;
+
+/**
+ * Preference controller for setting the start and end time and days of the week associated with
+ * an automatic zen mode.
+ */
+class ZenModeSetSchedulePreferenceController extends AbstractZenModePreferenceController {
+ // per-instance to ensure we're always using the current locale
+ // E = day of the week; "EEEEE" is the shortest version; "EEEE" is the full name
+ private final SimpleDateFormat mShortDayFormat = new SimpleDateFormat("EEEEE");
+ private final SimpleDateFormat mLongDayFormat = new SimpleDateFormat("EEEE");
+
+ private static final String TAG = "ZenModeSetSchedulePreferenceController";
+ private Fragment mParent;
+ private ZenModeConfig.ScheduleInfo mSchedule;
+
+ ZenModeSetSchedulePreferenceController(Context context, Fragment parent, String key,
+ ZenModesBackend backend) {
+ super(context, key, backend);
+ mParent = parent;
+ }
+
+ @Override
+ public void updateState(Preference preference, @NonNull ZenMode zenMode) {
+ mSchedule = ZenModeConfig.tryParseScheduleConditionId(zenMode.getRule().getConditionId());
+ LayoutPreference layoutPref = (LayoutPreference) preference;
+
+ TextView start = layoutPref.findViewById(R.id.start_time);
+ start.setText(timeString(mSchedule.startHour, mSchedule.startMinute));
+ start.setOnClickListener(
+ timePickerLauncher(mSchedule.startHour, mSchedule.startMinute, mStartSetter));
+
+ TextView end = layoutPref.findViewById(R.id.end_time);
+ end.setText(timeString(mSchedule.endHour, mSchedule.endMinute));
+ end.setOnClickListener(
+ timePickerLauncher(mSchedule.endHour, mSchedule.endMinute, mEndSetter));
+
+ TextView durationView = layoutPref.findViewById(R.id.schedule_duration);
+ durationView.setText(getScheduleDurationDescription(mSchedule));
+
+ ViewGroup daysContainer = layoutPref.findViewById(R.id.days_of_week_container);
+ setupDayToggles(daysContainer, mSchedule, Calendar.getInstance());
+ }
+
+ private String timeString(int hour, int minute) {
+ final Calendar c = Calendar.getInstance();
+ c.set(Calendar.HOUR_OF_DAY, hour);
+ c.set(Calendar.MINUTE, minute);
+ return DateFormat.getTimeFormat(mContext).format(c.getTime());
+ }
+
+ private boolean isValidTime(int hour, int minute) {
+ return ZenModeConfig.isValidHour(hour) && ZenModeConfig.isValidMinute(minute);
+ }
+
+ private String getScheduleDurationDescription(ZenModeConfig.ScheduleInfo schedule) {
+ final int startMin = 60 * schedule.startHour + schedule.startMinute;
+ final int endMin = 60 * schedule.endHour + schedule.endMinute;
+ final boolean nextDay = startMin >= endMin;
+
+ Duration scheduleDuration;
+ if (nextDay) {
+ // add one day's worth of minutes (24h x 60min) to end minute for end time calculation
+ int endMinNextDay = endMin + (24 * 60);
+ scheduleDuration = Duration.ofMinutes(endMinNextDay - startMin);
+ } else {
+ scheduleDuration = Duration.ofMinutes(endMin - startMin);
+ }
+
+ int hours = scheduleDuration.toHoursPart();
+ int minutes = scheduleDuration.minusHours(hours).toMinutesPart();
+ return mContext.getString(R.string.zen_mode_schedule_duration, hours, minutes);
+ }
+
+ @VisibleForTesting
+ protected Function<ZenMode, ZenMode> updateScheduleMode(ZenModeConfig.ScheduleInfo schedule) {
+ return (zenMode) -> {
+ zenMode.getRule().setConditionId(ZenModeConfig.toScheduleConditionId(schedule));
+ if (Flags.modesApi() && Flags.modesUi()) {
+ zenMode.getRule().setTriggerDescription(
+ SystemZenRules.getTriggerDescriptionForScheduleTime(mContext, schedule));
+ }
+ return zenMode;
+ };
+ }
+
+ private ZenModeTimePickerFragment.TimeSetter mStartSetter = (hour, minute) -> {
+ if (!isValidTime(hour, minute)) {
+ return;
+ }
+ if (hour == mSchedule.startHour && minute == mSchedule.startMinute) {
+ return;
+ }
+ mSchedule.startHour = hour;
+ mSchedule.startMinute = minute;
+ saveMode(updateScheduleMode(mSchedule));
+ };
+
+ private ZenModeTimePickerFragment.TimeSetter mEndSetter = (hour, minute) -> {
+ if (!isValidTime(hour, minute)) {
+ return;
+ }
+ if (hour == mSchedule.endHour && minute == mSchedule.endMinute) {
+ return;
+ }
+ mSchedule.endHour = hour;
+ mSchedule.endMinute = minute;
+ saveMode(updateScheduleMode(mSchedule));
+ };
+
+ private View.OnClickListener timePickerLauncher(int hour, int minute,
+ ZenModeTimePickerFragment.TimeSetter timeSetter) {
+ return v -> {
+ final ZenModeTimePickerFragment frag = new ZenModeTimePickerFragment(mContext, hour,
+ minute, timeSetter);
+ frag.show(mParent.getParentFragmentManager(), TAG);
+ };
+ }
+
+ protected static int[] getDaysOfWeekForLocale(Calendar c) {
+ int[] daysOfWeek = new int[7];
+ int currentDay = c.getFirstDayOfWeek();
+ for (int i = 0; i < daysOfWeek.length; i++) {
+ if (currentDay > 7) currentDay = 1;
+ daysOfWeek[i] = currentDay;
+ currentDay++;
+ }
+ return daysOfWeek;
+ }
+
+ @VisibleForTesting
+ protected void setupDayToggles(ViewGroup dayContainer, ZenModeConfig.ScheduleInfo schedule,
+ Calendar c) {
+ int[] daysOfWeek = getDaysOfWeekForLocale(c);
+
+ // Index in daysOfWeek is associated with the [idx]'th object in the list of days in the
+ // layout. Note that because the order of the days of the week may differ per locale, this
+ // is not necessarily the same as the actual value of the day number at that index.
+ for (int i = 0; i < daysOfWeek.length; i++) {
+ ToggleButton dayToggle = dayContainer.findViewById(resIdForDayIndex(i));
+ if (dayToggle == null) {
+ continue;
+ }
+
+ final int day = daysOfWeek[i];
+ c.set(Calendar.DAY_OF_WEEK, day);
+
+ // find current setting for this day
+ boolean dayEnabled = false;
+ if (schedule.days != null) {
+ for (int idx = 0; idx < schedule.days.length; idx++) {
+ if (schedule.days[idx] == day) {
+ dayEnabled = true;
+ break;
+ }
+ }
+ }
+
+ // On/off is indicated by visuals, and both states share the shortest (one-character)
+ // day label.
+ dayToggle.setTextOn(mShortDayFormat.format(c.getTime()));
+ dayToggle.setTextOff(mShortDayFormat.format(c.getTime()));
+ dayToggle.setContentDescription(mLongDayFormat.format(c.getTime()));
+
+ dayToggle.setChecked(dayEnabled);
+ dayToggle.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ if (updateScheduleDays(schedule, day, isChecked)) {
+ saveMode(updateScheduleMode(schedule));
+ }
+ });
+
+ // If display and text settings cause the text to be larger than its containing box,
+ // don't show scrollbars.
+ dayToggle.setVerticalScrollBarEnabled(false);
+ dayToggle.setHorizontalScrollBarEnabled(false);
+ }
+ }
+
+ // Updates the set of enabled days in provided schedule to either turn on or off the given day.
+ // The format of days in ZenModeConfig.ScheduleInfo is an array of days, where inclusion means
+ // the schedule is set to run on that day. Returns whether anything was changed.
+ @VisibleForTesting
+ protected static boolean updateScheduleDays(ZenModeConfig.ScheduleInfo schedule, int day,
+ boolean set) {
+ // Build a set representing the days that are currently set in mSchedule.
+ ArraySet<Integer> daySet = new ArraySet();
+ if (schedule.days != null) {
+ for (int i = 0; i < schedule.days.length; i++) {
+ daySet.add(schedule.days[i]);
+ }
+ }
+
+ if (daySet.contains(day) != set) {
+ if (set) {
+ daySet.add(day);
+ } else {
+ daySet.remove(day);
+ }
+
+ // rebuild days array for mSchedule
+ final int[] out = new int[daySet.size()];
+ for (int i = 0; i < daySet.size(); i++) {
+ out[i] = daySet.valueAt(i);
+ }
+ Arrays.sort(out);
+ schedule.days = out;
+ return true;
+ }
+ // If the setting is the same as it was before, no need to update anything.
+ return false;
+ }
+
+ protected static int resIdForDayIndex(int idx) {
+ switch (idx) {
+ case 0:
+ return R.id.day0;
+ case 1:
+ return R.id.day1;
+ case 2:
+ return R.id.day2;
+ case 3:
+ return R.id.day3;
+ case 4:
+ return R.id.day4;
+ case 5:
+ return R.id.day5;
+ case 6:
+ return R.id.day6;
+ default:
+ return 0; // unknown
+ }
+ }
+}
diff --git a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java
index a3bc508..14d5d59 100644
--- a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java
@@ -16,6 +16,7 @@
package com.android.settings.notification.modes;
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
+import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
@@ -32,13 +33,13 @@
import com.android.settingslib.PrimarySwitchPreference;
/**
- * Preference controller for the link
+ * Preference controller for the link to an individual mode's configuration page.
*/
-public class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenceController {
+class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenceController {
@VisibleForTesting
protected static final String AUTOMATIC_TRIGGER_PREF_KEY = "zen_automatic_trigger_settings";
- public ZenModeSetTriggerLinkPreferenceController(Context context, String key,
+ ZenModeSetTriggerLinkPreferenceController(Context context, String key,
ZenModesBackend backend) {
super(context, key, backend);
}
@@ -66,6 +67,16 @@
// TODO: b/341961712 - direct preference to app-owned intent if available
switch (zenMode.getRule().getType()) {
+ case TYPE_SCHEDULE_TIME:
+ switchPref.setTitle(R.string.zen_mode_set_schedule_link);
+ switchPref.setSummary(zenMode.getRule().getTriggerDescription());
+ switchPref.setIntent(new SubSettingLauncher(mContext)
+ .setDestination(ZenModeSetScheduleFragment.class.getName())
+ // TODO: b/332937635 - set correct metrics category
+ .setSourceMetricsCategory(0)
+ .setArguments(bundle)
+ .toIntent());
+ break;
case TYPE_SCHEDULE_CALENDAR:
switchPref.setTitle(R.string.zen_mode_set_calendar_link);
switchPref.setSummary(zenMode.getRule().getTriggerDescription());
diff --git a/src/com/android/settings/notification/modes/ZenModeTimePickerFragment.java b/src/com/android/settings/notification/modes/ZenModeTimePickerFragment.java
new file mode 100644
index 0000000..d8e1b38
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeTimePickerFragment.java
@@ -0,0 +1,76 @@
+/*
+ * 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.app.Dialog;
+import android.app.TimePickerDialog;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.format.DateFormat;
+import android.widget.TimePicker;
+
+import androidx.annotation.NonNull;
+
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+
+/**
+ * Dialog that shows when a user selects a (start or end) time to edit for a schedule-based mode.
+ */
+public class ZenModeTimePickerFragment extends InstrumentedDialogFragment implements
+ TimePickerDialog.OnTimeSetListener {
+ private final Context mContext;
+ private final TimeSetter mTimeSetter;
+ private final int mHour;
+ private final int mMinute;
+
+ public ZenModeTimePickerFragment(Context context, int hour, int minute,
+ @NonNull TimeSetter timeSetter) {
+ super();
+ mContext = context;
+ mHour = hour;
+ mMinute = minute;
+ mTimeSetter = timeSetter;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return new TimePickerDialog(mContext, this, mHour, mMinute,
+ DateFormat.is24HourFormat(mContext));
+ }
+
+ /**
+ * Calls the provided TimeSetter's setTime() method when a time is set on the TimePicker.
+ */
+ public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
+ mTimeSetter.setTime(hourOfDay, minute);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ // TODO: b/332937635 - set correct metrics category (or decide to keep this one?)
+ return SettingsEnums.DIALOG_ZEN_TIMEPICKER;
+ }
+
+ /**
+ * Interface for a method to pass into the TimePickerFragment that specifies what to do when the
+ * time is updated.
+ */
+ public interface TimeSetter {
+ void setTime(int hour, int minute);
+ }
+}
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
index 30fd619..7f362c3 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
@@ -23,6 +23,8 @@
import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+import static com.android.systemui.biometrics.Utils.toBitmap;
+
import android.app.Activity;
import android.app.KeyguardManager;
import android.app.RemoteLockscreenValidationSession;
@@ -35,6 +37,7 @@
import android.content.pm.PackageManager;
import android.content.pm.UserProperties;
import android.content.res.Configuration;
+import android.graphics.Bitmap;
import android.graphics.Color;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
@@ -215,9 +218,10 @@
&& android.multiuser.Flags.usePrivateSpaceIconInBiometricPrompt()
&& hasSetBiometricDialogAdvanced(mContext, getLaunchedFromUid())
) {
- int iconResId = intent.getIntExtra(CUSTOM_BIOMETRIC_PROMPT_LOGO_RES_ID_KEY, 0);
+ final int iconResId = intent.getIntExtra(CUSTOM_BIOMETRIC_PROMPT_LOGO_RES_ID_KEY, 0);
if (iconResId != 0) {
- promptInfo.setLogoRes(iconResId);
+ final Bitmap iconBitmap = toBitmap(mContext.getDrawable(iconResId));
+ promptInfo.setLogo(iconResId, iconBitmap);
}
String logoDescription = intent.getStringExtra(
CUSTOM_BIOMETRIC_PROMPT_LOGO_DESCRIPTION_KEY);
diff --git a/src/com/android/settings/privatespace/PrivateSpaceCreationFragment.java b/src/com/android/settings/privatespace/PrivateSpaceCreationFragment.java
index eb88644..ce85d72 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceCreationFragment.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceCreationFragment.java
@@ -157,22 +157,26 @@
/** Start new activity in private profile to add an account to private profile */
private void startAccountLogin() {
- Intent intent = new Intent(getContext(), PrivateProfileContextHelperActivity.class);
- intent.putExtra(EXTRA_ACTION_TYPE, ACCOUNT_LOGIN_ACTION);
- mMetricsFeatureProvider.action(
- getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_ACCOUNT_LOGIN_START);
- getActivity().startActivityForResult(intent, ACCOUNT_LOGIN_ACTION);
+ if (isAdded() && getContext() != null && getActivity() != null) {
+ Intent intent = new Intent(getContext(), PrivateProfileContextHelperActivity.class);
+ intent.putExtra(EXTRA_ACTION_TYPE, ACCOUNT_LOGIN_ACTION);
+ mMetricsFeatureProvider.action(
+ getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_ACCOUNT_LOGIN_START);
+ getActivity().startActivityForResult(intent, ACCOUNT_LOGIN_ACTION);
+ }
}
private void registerReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PROFILE_ACCESSIBLE);
- getActivity().registerReceiver(mProfileAccessReceiver, filter);
+ if (getContext() != null) {
+ getContext().registerReceiver(mProfileAccessReceiver, filter);
+ }
}
private void unRegisterReceiver() {
- if (mProfileAccessReceiver != null) {
- getActivity().unregisterReceiver(mProfileAccessReceiver);
+ if (mProfileAccessReceiver != null && isAdded() && getContext() != null) {
+ getContext().unregisterReceiver(mProfileAccessReceiver);
}
}
}
diff --git a/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java b/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java
index ed70030..906b01b 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java
@@ -26,12 +26,10 @@
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
-import com.android.settingslib.widget.IllustrationPreference;
/** Fragment representing the Private Space dashboard in Settings. */
public class PrivateSpaceDashboardFragment extends DashboardFragment {
private static final String TAG = "PSDashboardFragment";
- private static final String PRIVATE_SPACE_ILLUSTRATION_KEY = "private_space_illustration";
@Override
public void onCreate(Bundle icicle) {
@@ -64,14 +62,6 @@
}
@Override
- public void onResume() {
- super.onResume();
- final IllustrationPreference illustrationPreference =
- getPreferenceScreen().findPreference(PRIVATE_SPACE_ILLUSTRATION_KEY);
- illustrationPreference.applyDynamicColor();
- }
-
- @Override
protected int getPreferenceScreenResId() {
return R.xml.private_space_settings;
}
diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java
index 1f0d824..b48c717 100644
--- a/src/com/android/settings/users/UserDetailsSettings.java
+++ b/src/com/android/settings/users/UserDetailsSettings.java
@@ -127,7 +127,7 @@
public void onResume() {
super.onResume();
mSwitchUserPref.setEnabled(canSwitchUserNow());
- if (mGuestUserAutoCreated) {
+ if (mUserInfo.isGuest() && mGuestUserAutoCreated) {
mRemoveUserPref.setEnabled((mUserInfo.flags & UserInfo.FLAG_INITIALIZED) != 0);
}
}
diff --git a/src/com/android/settings/wifi/repository/WifiRepository.kt b/src/com/android/settings/wifi/repository/WifiRepository.kt
new file mode 100644
index 0000000..77f0b1b
--- /dev/null
+++ b/src/com/android/settings/wifi/repository/WifiRepository.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.wifi.repository
+
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.wifi.WifiManager
+import android.util.Log
+import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+
+class WifiRepository(
+ private val context: Context,
+ private val wifiStateChangedActionFlow: Flow<Intent> =
+ context.broadcastReceiverFlow(IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)),
+) {
+
+ fun wifiStateFlow() = wifiStateChangedActionFlow
+ .map { intent ->
+ intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN)
+ }
+ .onEach { Log.d(TAG, "wifiStateFlow: $it") }
+
+ private companion object {
+ private const val TAG = "WifiRepository"
+ }
+}
diff --git a/tests/Enable16KbTests/Android.bp b/tests/Enable16KbTests/Android.bp
index ecb0357..781ea8f 100644
--- a/tests/Enable16KbTests/Android.bp
+++ b/tests/Enable16KbTests/Android.bp
@@ -33,7 +33,7 @@
],
platform_apis: true,
certificate: "platform",
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
libs: [
"android.test.runner",
"android.test.base",
@@ -57,6 +57,6 @@
data: [
":test_16kb_app",
],
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
test_config: "AndroidTest.xml",
}
diff --git a/tests/robotests/Android.bp b/tests/robotests/Android.bp
index 0521e52..83f4f2e 100644
--- a/tests/robotests/Android.bp
+++ b/tests/robotests/Android.bp
@@ -75,6 +75,7 @@
//"src/com/android/settings/homepage/**/*.java",
"src/com/android/settings/inputmethod/**/*.java",
"src/com/android/settings/network/ShadowServiceManagerExtend.java",
+ "src/com/android/settings/notification/modes/**/*.java",
"src/com/android/settings/password/**/*.java",
"src/com/android/settings/search/DatabaseIndexingUtils.java",
"src/com/android/settings/testutils/**/*.java",
diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java
index c521f25..97faa63 100644
--- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java
@@ -19,6 +19,7 @@
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;
@@ -82,6 +83,8 @@
private BluetoothAdapter mBluetoothAdapter;
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
private BluetoothDevice mBluetoothDevice;
+
+ private BluetoothDevice mSubBluetoothDevice;
private final Context mContext = ApplicationProvider.getApplicationContext();
private Preference mHearingAidPreference;
@@ -143,8 +146,8 @@
public void getSummary_connectedAshaHearingAidBothSide_connectedBothSideSummary() {
when(mCachedBluetoothDevice.getDeviceSide()).thenReturn(
HearingAidInfo.DeviceSide.SIDE_LEFT);
- when(mCachedSubBluetoothDevice.isConnected()).thenReturn(true);
when(mCachedBluetoothDevice.getSubDevice()).thenReturn(mCachedSubBluetoothDevice);
+ when(mSubBluetoothDevice.isConnected()).thenReturn(true);
when(mHearingAidProfile.getConnectedDevices()).thenReturn(generateHearingAidDeviceList());
mPreferenceController.onStart();
@@ -211,8 +214,8 @@
@Test
public void getSummary_connectedLeAudioHearingAidBothSide_connectedBothSideSummary() {
when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(generateMemberDevices());
- when(mCachedSubBluetoothDevice.isConnected()).thenReturn(true);
when(mHapClientProfile.getConnectedDevices()).thenReturn(generateHearingAidDeviceList());
+ when(mSubBluetoothDevice.isConnected()).thenReturn(true);
mPreferenceController.onStart();
Intent intent = new Intent(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED);
@@ -288,7 +291,8 @@
mShadowBluetoothAdapter = Shadow.extract(mBluetoothAdapter);
mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID);
mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HAP_CLIENT);
- mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
+ mBluetoothDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS));
+ mSubBluetoothDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_2));
mBluetoothAdapter.enable();
doReturn(mEventManager).when(mLocalBluetoothManager).getEventManager();
@@ -299,8 +303,12 @@
when(mHearingAidProfile.isProfileReady()).thenReturn(true);
when(mHapClientProfile.isProfileReady()).thenReturn(true);
when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
when(mCachedBluetoothDevice.getName()).thenReturn(TEST_DEVICE_NAME);
+ when(mCachedDeviceManager.findDevice(mSubBluetoothDevice)).thenReturn(
+ mCachedSubBluetoothDevice);
+ when(mCachedSubBluetoothDevice.getDevice()).thenReturn(mSubBluetoothDevice);
}
private void sendIntent(Intent intent) {
@@ -319,7 +327,7 @@
// Generates different Bluetooth devices for testing multiple devices
final List<BluetoothDevice> deviceList = new ArrayList<>(2);
deviceList.add(mBluetoothDevice);
- deviceList.add(mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_2));
+ deviceList.add(mSubBluetoothDevice);
return deviceList;
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java
index a35ef45..d28ab3b 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java
@@ -27,9 +27,12 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.input.InputManager;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.FeatureFlagUtils;
import android.view.InputDevice;
@@ -39,13 +42,23 @@
import androidx.preference.PreferenceScreen;
import com.android.settings.bluetooth.ConnectedBluetoothDeviceUpdater;
+import com.android.settings.bluetooth.Utils;
import com.android.settings.connecteddevice.dock.DockUpdater;
import com.android.settings.connecteddevice.stylus.StylusDeviceUpdater;
import com.android.settings.connecteddevice.usb.ConnectedUsbDeviceUpdater;
import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.flags.Flags;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
+import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.search.SearchIndexableRaw;
+
+import com.google.common.collect.ImmutableList;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
@@ -57,11 +70,16 @@
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplicationPackageManager;
+import java.util.ArrayList;
+import java.util.List;
+
@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowApplicationPackageManager.class, ShadowBluetoothAdapter.class})
+@Config(shadows = {ShadowApplicationPackageManager.class, ShadowBluetoothUtils.class,
+ ShadowBluetoothAdapter.class})
public class ConnectedDeviceGroupControllerTest {
private static final String PREFERENCE_KEY_1 = "pref_key_1";
+ private static final String DEVICE_NAME = "device";
@Mock
private DashboardFragment mDashboardFragment;
@@ -79,6 +97,14 @@
private PreferenceManager mPreferenceManager;
@Mock
private InputManager mInputManager;
+ @Mock
+ private CachedBluetoothDeviceManager mCachedDeviceManager;
+ @Mock
+ private LocalBluetoothManager mLocalBluetoothManager;
+ @Mock
+ private CachedBluetoothDevice mCachedDevice;
+ @Mock
+ private BluetoothDevice mDevice;
private ShadowApplicationPackageManager mPackageManager;
private PreferenceGroup mPreferenceGroup;
@@ -86,6 +112,9 @@
private Preference mPreference;
private ConnectedDeviceGroupController mConnectedDeviceGroupController;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -102,11 +131,20 @@
when(mContext.getSystemService(InputManager.class)).thenReturn(mInputManager);
when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{});
+ ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
+ mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
+ when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
+
mConnectedDeviceGroupController = new ConnectedDeviceGroupController(mContext);
mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
mConnectedUsbDeviceUpdater, mConnectedDockUpdater, mStylusDeviceUpdater);
mConnectedDeviceGroupController.mPreferenceGroup = mPreferenceGroup;
+ when(mCachedDevice.getName()).thenReturn(DEVICE_NAME);
+ when(mCachedDevice.getDevice()).thenReturn(mDevice);
+ when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
+ ImmutableList.of(mCachedDevice));
+
FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES,
true);
}
@@ -267,4 +305,27 @@
mConnectedDeviceGroupController.onStart();
mConnectedDeviceGroupController.onStop();
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_BONDED_BLUETOOTH_DEVICE_SEARCHABLE)
+ public void updateDynamicRawDataToIndex_deviceNotBonded_deviceIsNotSearchable() {
+ when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+ List<SearchIndexableRaw> searchData = new ArrayList<>();
+
+ mConnectedDeviceGroupController.updateDynamicRawDataToIndex(searchData);
+
+ assertThat(searchData).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_BONDED_BLUETOOTH_DEVICE_SEARCHABLE)
+ public void updateDynamicRawDataToIndex_deviceBonded_deviceIsSearchable() {
+ when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ List<SearchIndexableRaw> searchData = new ArrayList<>();
+
+ mConnectedDeviceGroupController.updateDynamicRawDataToIndex(searchData);
+
+ assertThat(searchData).isNotEmpty();
+ assertThat(searchData.get(0).key).contains(DEVICE_NAME);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCompatibilityPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCompatibilityPreferenceControllerTest.java
index 1a9d09e..19221a6 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCompatibilityPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCompatibilityPreferenceControllerTest.java
@@ -25,6 +25,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
@@ -34,6 +35,7 @@
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.os.Looper;
@@ -94,28 +96,28 @@
@Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
@Mock private VolumeControlProfile mVolumeControl;
@Mock private TwoStatePreference mPreference;
+ @Mock private BluetoothLeBroadcastMetadata mMetadata;
private AudioSharingCompatibilityPreferenceController mController;
- private ShadowBluetoothAdapter mShadowBluetoothAdapter;
- private LocalBluetoothManager mLocalBluetoothManager;
private FakeFeatureFactory mFeatureFactory;
private Lifecycle mLifecycle;
private LifecycleOwner mLifecycleOwner;
@Before
public void setUp() {
- mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- mShadowBluetoothAdapter.setEnabled(true);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+ shadowBluetoothAdapter.setEnabled(true);
+ shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
mLifecycleOwner = () -> mLifecycle;
mLifecycle = new Lifecycle(mLifecycleOwner);
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
- mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
+ LocalBluetoothManager localBluetoothManager = Utils.getLocalBtManager(mContext);
mFeatureFactory = FakeFeatureFactory.setupForTest();
- when(mLocalBluetoothManager.getEventManager()).thenReturn(mBtEventManager);
- when(mLocalBluetoothManager.getProfileManager()).thenReturn(mBtProfileManager);
+ when(localBluetoothManager.getEventManager()).thenReturn(mBtEventManager);
+ when(localBluetoothManager.getProfileManager()).thenReturn(mBtProfileManager);
when(mBtProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
when(mBtProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
when(mBtProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControl);
@@ -133,7 +135,7 @@
verify(mBroadcast)
.registerServiceCallBack(
any(Executor.class), any(BluetoothLeBroadcast.Callback.class));
- verify(mBtProfileManager, times(0)).addServiceListener(mController);
+ verify(mBtProfileManager, never()).addServiceListener(mController);
}
@Test
@@ -141,7 +143,7 @@
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mBroadcast.isProfileReady()).thenReturn(false);
mController.onStart(mLifecycleOwner);
- verify(mBroadcast, times(0))
+ verify(mBroadcast, never())
.registerServiceCallBack(
any(Executor.class), any(BluetoothLeBroadcast.Callback.class));
verify(mBtProfileManager).addServiceListener(mController);
@@ -151,7 +153,7 @@
public void onStart_flagOff_doNothing() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mController.onStart(mLifecycleOwner);
- verify(mBroadcast, times(0))
+ verify(mBroadcast, never())
.registerServiceCallBack(
any(Executor.class), any(BluetoothLeBroadcast.Callback.class));
}
@@ -170,9 +172,9 @@
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mController.setCallbacksRegistered(true);
mController.onStop(mLifecycleOwner);
- verify(mBroadcast, times(0))
+ verify(mBroadcast, never())
.unregisterServiceCallBack(any(BluetoothLeBroadcast.Callback.class));
- verify(mBtProfileManager, times(0)).removeServiceListener(mController);
+ verify(mBtProfileManager, never()).removeServiceListener(mController);
}
@Test
@@ -224,11 +226,10 @@
mController.displayPreference(mScreen);
shadowOf(Looper.getMainLooper()).idle();
verify(mPreference).setEnabled(false);
- verify(mPreference)
- .setSummary(
- eq(mContext.getString(
- R.string
- .audio_sharing_stream_compatibility_disabled_description)));
+ String expected =
+ mContext.getString(
+ R.string.audio_sharing_stream_compatibility_disabled_description);
+ verify(mPreference).setSummary(eq(expected));
}
@Test
@@ -237,10 +238,9 @@
mController.displayPreference(mScreen);
shadowOf(Looper.getMainLooper()).idle();
verify(mPreference).setEnabled(true);
- verify(mPreference)
- .setSummary(
- eq(mContext.getString(
- R.string.audio_sharing_stream_compatibility_description)));
+ String expected =
+ mContext.getString(R.string.audio_sharing_stream_compatibility_description);
+ verify(mPreference).setSummary(eq(expected));
}
@Test
@@ -272,8 +272,73 @@
public void setCheckedToCurrentValue_returnsFalse() {
when(mBroadcast.getImproveCompatibility()).thenReturn(true);
boolean setChecked = mController.setChecked(true);
- verify(mBroadcast, times(0)).setImproveCompatibility(anyBoolean());
+ verify(mBroadcast, never()).setImproveCompatibility(anyBoolean());
verifyNoInteractions(mFeatureFactory.metricsFeatureProvider);
assertThat(setChecked).isFalse();
}
+
+ @Test
+ public void testBluetoothLeBroadcastCallbacks_refreshPreference() {
+ when(mBroadcast.isEnabled(any())).thenReturn(false);
+ mController.displayPreference(mScreen);
+ shadowOf(Looper.getMainLooper()).idle();
+ verify(mPreference).setEnabled(true);
+ String expected =
+ mContext.getString(R.string.audio_sharing_stream_compatibility_description);
+ verify(mPreference).setSummary(eq(expected));
+
+ when(mBroadcast.isEnabled(any())).thenReturn(true);
+ mController.mBroadcastCallback.onBroadcastStarted(/* reason= */ 1, /* broadcastId= */ 1);
+ shadowOf(Looper.getMainLooper()).idle();
+ verify(mPreference).setEnabled(false);
+ expected =
+ mContext.getString(
+ R.string.audio_sharing_stream_compatibility_disabled_description);
+ verify(mPreference).setSummary(eq(expected));
+
+ when(mBroadcast.isEnabled(any())).thenReturn(false);
+ mController.mBroadcastCallback.onBroadcastStopped(/* reason= */ 1, /* broadcastId= */ 1);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ // Verify one extra setEnabled/setSummary is called other than the first call in
+ // displayPreference.
+ verify(mPreference, times(2)).setEnabled(true);
+ expected = mContext.getString(R.string.audio_sharing_stream_compatibility_description);
+ verify(mPreference, times(2)).setSummary(eq(expected));
+ }
+
+ @Test
+ public void testBluetoothLeBroadcastCallbacks_doNothing() {
+ when(mBroadcast.isEnabled(any())).thenReturn(false);
+ mController.displayPreference(mScreen);
+ shadowOf(Looper.getMainLooper()).idle();
+ verify(mPreference).setEnabled(true);
+ String expected =
+ mContext.getString(R.string.audio_sharing_stream_compatibility_description);
+ verify(mPreference).setSummary(eq(expected));
+
+ // Verify no extra setEnabled/setSummary is called other than call in displayPreference.
+ mController.mBroadcastCallback.onBroadcastMetadataChanged(/* reason= */ 1, mMetadata);
+ verify(mPreference).setEnabled(anyBoolean());
+ verify(mPreference).setSummary(any());
+ mController.mBroadcastCallback.onBroadcastUpdated(/* reason= */ 1, /* broadcastId= */ 1);
+ verify(mPreference).setEnabled(anyBoolean());
+ verify(mPreference).setSummary(any());
+ mController.mBroadcastCallback.onPlaybackStarted(/* reason= */ 1, /* broadcastId= */ 1);
+ verify(mPreference).setEnabled(anyBoolean());
+ verify(mPreference).setSummary(any());
+ mController.mBroadcastCallback.onPlaybackStopped(/* reason= */ 1, /* broadcastId= */ 1);
+ verify(mPreference).setEnabled(anyBoolean());
+ verify(mPreference).setSummary(any());
+ mController.mBroadcastCallback.onBroadcastStartFailed(/* reason= */ 1);
+ verify(mPreference).setEnabled(anyBoolean());
+ verify(mPreference).setSummary(any());
+ mController.mBroadcastCallback.onBroadcastStopFailed(/* reason= */ 1);
+ verify(mPreference).setEnabled(anyBoolean());
+ verify(mPreference).setSummary(any());
+ mController.mBroadcastCallback.onBroadcastUpdateFailed(
+ /* reason= */ 1, /* broadcastId= */ 1);
+ verify(mPreference).setEnabled(anyBoolean());
+ verify(mPreference).setSummary(any());
+ }
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingConfirmDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingConfirmDialogFragmentTest.java
new file mode 100644
index 0000000..e5facc1
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingConfirmDialogFragmentTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.connecteddevice.audiosharing;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
+
+import android.app.settings.SettingsEnums;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothStatusCodes;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.view.View;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
+import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
+import com.android.settingslib.flags.Flags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.androidx.fragment.FragmentController;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(
+ shadows = {
+ ShadowAlertDialogCompat.class,
+ ShadowBluetoothAdapter.class,
+ })
+public class AudioSharingConfirmDialogFragmentTest {
+ @Rule public final MockitoRule mocks = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ private Fragment mParent;
+ private AudioSharingConfirmDialogFragment mFragment;
+
+ @Before
+ public void setUp() {
+ cleanUpDialogs();
+ ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+ shadowBluetoothAdapter.setEnabled(true);
+ shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mFragment = new AudioSharingConfirmDialogFragment();
+ mParent = new Fragment();
+ FragmentController.setupFragment(
+ mParent, FragmentActivity.class, /* containerViewId= */ 0, /* bundle= */ null);
+ }
+
+ @After
+ public void tearDown() {
+ cleanUpDialogs();
+ }
+
+ @Test
+ public void getMetricsCategory_correctValue() {
+ assertThat(mFragment.getMetricsCategory())
+ .isEqualTo(SettingsEnums.DIALOG_AUDIO_SHARING_CONFIRMATION);
+ }
+
+ @Test
+ public void onCreateDialog_flagOff_dialogNotExist() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ AudioSharingConfirmDialogFragment.show(mParent);
+ shadowMainLooper().idle();
+ AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+ assertThat(dialog).isNull();
+ }
+
+ @Test
+ public void onCreateDialog_flagOn_showDialog() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ AudioSharingConfirmDialogFragment.show(mParent);
+ shadowMainLooper().idle();
+ AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+ assertThat(dialog).isNotNull();
+ assertThat(dialog.isShowing()).isTrue();
+ }
+
+ @Test
+ public void onCreateDialog_clickOk_dialogDismiss() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ AudioSharingConfirmDialogFragment.show(mParent);
+ shadowMainLooper().idle();
+ AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+ assertThat(dialog).isNotNull();
+ View btnView = dialog.findViewById(android.R.id.button1);
+ assertThat(btnView).isNotNull();
+ btnView.performClick();
+ shadowMainLooper().idle();
+ assertThat(dialog.isShowing()).isFalse();
+ }
+
+ private void cleanUpDialogs() {
+ AlertDialog latestAlertDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+ if (latestAlertDialog != null) {
+ latestAlertDialog.dismiss();
+ ShadowAlertDialogCompat.reset();
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragmentTest.java
index c1afeaa..8e4915c 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragmentTest.java
@@ -18,22 +18,45 @@
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.verify;
+
import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsCategoryController;
+import com.android.settings.testutils.shadow.ShadowFragment;
+import com.android.settings.widget.SettingsMainSwitchBar;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowFragment.class})
public class AudioSharingDashboardFragmentTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Mock private SettingsActivity mActivity;
+ @Mock private SettingsMainSwitchBar mSwitchBar;
+ @Mock private AudioSharingDeviceVolumeGroupController mVolumeGroupController;
+ @Mock private AudioSharingCallAudioPreferenceController mCallAudioController;
+ @Mock private AudioSharingPlaySoundPreferenceController mPlaySoundController;
+ @Mock private AudioStreamsCategoryController mStreamsCategoryController;
+ private final Context mContext = ApplicationProvider.getApplicationContext();
private AudioSharingDashboardFragment mFragment;
@Before
@@ -59,7 +82,42 @@
@Test
public void getHelpResource_returnsCorrectResource() {
- assertThat(mFragment.getHelpResource())
- .isEqualTo(R.string.help_url_audio_sharing);
+ assertThat(mFragment.getHelpResource()).isEqualTo(R.string.help_url_audio_sharing);
+ }
+
+ @Test
+ public void onActivityCreated_showSwitchBar() {
+ doReturn(mSwitchBar).when(mActivity).getSwitchBar();
+ mFragment = spy(new AudioSharingDashboardFragment());
+ doReturn(mActivity).when(mFragment).getActivity();
+ doReturn(mContext).when(mFragment).getContext();
+ mFragment.onAttach(mContext);
+ mFragment.onActivityCreated(new Bundle());
+ verify(mSwitchBar).show();
+ }
+
+ @Test
+ public void onAudioSharingStateChanged_updateVisibilityForControllers() {
+ mFragment.setControllers(
+ mVolumeGroupController,
+ mCallAudioController,
+ mPlaySoundController,
+ mStreamsCategoryController);
+ mFragment.onAudioSharingStateChanged();
+ verify(mVolumeGroupController).updateVisibility();
+ verify(mCallAudioController).updateVisibility();
+ verify(mPlaySoundController).updateVisibility();
+ verify(mStreamsCategoryController).updateVisibility();
+ }
+
+ @Test
+ public void onAudioSharingProfilesConnected_registerCallbacksForVolumeGroupController() {
+ mFragment.setControllers(
+ mVolumeGroupController,
+ mCallAudioController,
+ mPlaySoundController,
+ mStreamsCategoryController);
+ mFragment.onAudioSharingProfilesConnected();
+ verify(mVolumeGroupController).onAudioSharingProfilesConnected();
}
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceItemTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceItemTest.java
index 1bae3d1..b23882d 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceItemTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceItemTest.java
@@ -63,4 +63,19 @@
public void creator_newArray() {
assertThat(AudioSharingDeviceItem.CREATOR.newArray(2)).hasLength(2);
}
+
+ @Test
+ public void creator_createFromParcel() {
+ AudioSharingDeviceItem item =
+ new AudioSharingDeviceItem(TEST_NAME, TEST_GROUP_ID, TEST_IS_ACTIVE);
+ Parcel parcel = Parcel.obtain();
+ item.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ AudioSharingDeviceItem itemFromParcel =
+ AudioSharingDeviceItem.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+ assertThat(itemFromParcel.getName()).isEqualTo(TEST_NAME);
+ assertThat(itemFromParcel.getGroupId()).isEqualTo(TEST_GROUP_ID);
+ assertThat(itemFromParcel.isActive()).isEqualTo(TEST_IS_ACTIVE);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java
index 4336e77..c63a1a9 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java
@@ -18,11 +18,17 @@
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.verify;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
+import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothStatusCodes;
+import android.content.Context;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.Pair;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
@@ -34,6 +40,7 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R;
+import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settingslib.flags.Flags;
@@ -72,20 +79,33 @@
new AudioSharingDeviceItem(TEST_DEVICE_NAME2, /* groupId= */ 2, /* isActive= */ false);
private static final AudioSharingDeviceItem TEST_DEVICE_ITEM3 =
new AudioSharingDeviceItem(TEST_DEVICE_NAME3, /* groupId= */ 3, /* isActive= */ false);
+ private static final AudioSharingDialogFragment.DialogEventListener EMPTY_EVENT_LISTENER =
+ new AudioSharingDialogFragment.DialogEventListener() {
+ @Override
+ public void onItemClick(AudioSharingDeviceItem item) {}
+
+ @Override
+ public void onCancelClick() {}
+ };
+ private static final Pair<Integer, Object> TEST_EVENT_DATA = Pair.create(1, 1);
+ private static final Pair<Integer, Object>[] TEST_EVENT_DATA_LIST =
+ new Pair[] {TEST_EVENT_DATA};
private Fragment mParent;
private AudioSharingDialogFragment mFragment;
- private ShadowBluetoothAdapter mShadowBluetoothAdapter;
+ private FakeFeatureFactory mFeatureFactory;
@Before
public void setUp() {
ShadowAlertDialogCompat.reset();
- mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- mShadowBluetoothAdapter.setEnabled(true);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+ shadowBluetoothAdapter.setEnabled(true);
+ shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
mFragment = new AudioSharingDialogFragment();
mParent = new Fragment();
FragmentController.setupFragment(
@@ -93,9 +113,16 @@
}
@Test
+ public void getMetricsCategory_correctValue() {
+ assertThat(mFragment.getMetricsCategory())
+ .isEqualTo(SettingsEnums.DIALOG_AUDIO_SHARING_ADD_DEVICE);
+ }
+
+ @Test
public void onCreateDialog_flagOff_dialogNotExist() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- mFragment.show(mParent, new ArrayList<>(), (item) -> {});
+ AudioSharingDialogFragment.show(
+ mParent, new ArrayList<>(), EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -105,14 +132,20 @@
@Test
public void onCreateDialog_flagOn_noConnectedDevice() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- mFragment.show(mParent, new ArrayList<>(), (item) -> {});
+ AudioSharingDialogFragment.show(
+ mParent, new ArrayList<>(), EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+ assertThat(dialog).isNotNull();
TextView description = dialog.findViewById(R.id.description_text);
+ assertThat(description).isNotNull();
ImageView image = dialog.findViewById(R.id.description_image);
+ assertThat(image).isNotNull();
Button shareBtn = dialog.findViewById(R.id.positive_btn);
+ assertThat(shareBtn).isNotNull();
Button cancelBtn = dialog.findViewById(R.id.negative_btn);
+ assertThat(cancelBtn).isNotNull();
assertThat(dialog.isShowing()).isTrue();
assertThat(description.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(description.getText().toString())
@@ -125,13 +158,22 @@
@Test
public void onCreateDialog_noConnectedDevice_dialogDismiss() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- mFragment.show(mParent, new ArrayList<>(), (item) -> {});
+ AudioSharingDialogFragment.show(
+ mParent, new ArrayList<>(), EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- dialog.findViewById(android.R.id.button2).performClick();
+ assertThat(dialog).isNotNull();
+ View btnView = dialog.findViewById(android.R.id.button2);
+ assertThat(btnView).isNotNull();
+ btnView.performClick();
shadowMainLooper().idle();
assertThat(dialog.isShowing()).isFalse();
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED),
+ eq(TEST_EVENT_DATA));
}
@Test
@@ -139,15 +181,21 @@
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
ArrayList<AudioSharingDeviceItem> list = new ArrayList<>();
list.add(TEST_DEVICE_ITEM1);
- mFragment.show(mParent, list, (item) -> {});
+ AudioSharingDialogFragment.show(mParent, list, EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+ assertThat(dialog).isNotNull();
TextView title = dialog.findViewById(R.id.title_text);
+ assertThat(title).isNotNull();
TextView description = dialog.findViewById(R.id.description_text);
+ assertThat(description).isNotNull();
ImageView image = dialog.findViewById(R.id.description_image);
+ assertThat(image).isNotNull();
Button shareBtn = dialog.findViewById(R.id.positive_btn);
+ assertThat(shareBtn).isNotNull();
Button cancelBtn = dialog.findViewById(R.id.negative_btn);
+ assertThat(cancelBtn).isNotNull();
assertThat(dialog.isShowing()).isTrue();
assertThat(title.getText().toString())
.isEqualTo(
@@ -166,12 +214,22 @@
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
ArrayList<AudioSharingDeviceItem> list = new ArrayList<>();
list.add(TEST_DEVICE_ITEM1);
- mFragment.show(mParent, list, (item) -> {});
+ AudioSharingDialogFragment.show(mParent, list, EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- dialog.findViewById(R.id.negative_btn).performClick();
+ assertThat(dialog).isNotNull();
+ View btnView = dialog.findViewById(R.id.negative_btn);
+ assertThat(btnView).isNotNull();
+ btnView.performClick();
+ shadowMainLooper().idle();
+
assertThat(dialog.isShowing()).isFalse();
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED),
+ eq(TEST_EVENT_DATA));
}
@Test
@@ -180,13 +238,35 @@
ArrayList<AudioSharingDeviceItem> list = new ArrayList<>();
list.add(TEST_DEVICE_ITEM1);
AtomicBoolean isShareBtnClicked = new AtomicBoolean(false);
- mFragment.show(mParent, list, (item) -> isShareBtnClicked.set(true));
+ AudioSharingDialogFragment.show(
+ mParent,
+ list,
+ new AudioSharingDialogFragment.DialogEventListener() {
+ @Override
+ public void onItemClick(AudioSharingDeviceItem item) {
+ isShareBtnClicked.set(true);
+ }
+
+ @Override
+ public void onCancelClick() {}
+ },
+ TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- dialog.findViewById(R.id.positive_btn).performClick();
+ assertThat(dialog).isNotNull();
+ View btnView = dialog.findViewById(R.id.positive_btn);
+ assertThat(btnView).isNotNull();
+ btnView.performClick();
+ shadowMainLooper().idle();
+
assertThat(dialog.isShowing()).isFalse();
assertThat(isShareBtnClicked.get()).isTrue();
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_POSITIVE_BTN_CLICKED),
+ eq(TEST_EVENT_DATA));
}
@Test
@@ -196,15 +276,21 @@
list.add(TEST_DEVICE_ITEM1);
list.add(TEST_DEVICE_ITEM2);
list.add(TEST_DEVICE_ITEM3);
- mFragment.show(mParent, list, (item) -> {});
+ AudioSharingDialogFragment.show(mParent, list, EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+ assertThat(dialog).isNotNull();
TextView description = dialog.findViewById(R.id.description_text);
+ assertThat(description).isNotNull();
ImageView image = dialog.findViewById(R.id.description_image);
+ assertThat(image).isNotNull();
Button shareBtn = dialog.findViewById(R.id.positive_btn);
+ assertThat(shareBtn).isNotNull();
Button cancelBtn = dialog.findViewById(R.id.negative_btn);
+ assertThat(cancelBtn).isNotNull();
RecyclerView recyclerView = dialog.findViewById(R.id.device_btn_list);
+ assertThat(recyclerView).isNotNull();
assertThat(dialog.isShowing()).isTrue();
assertThat(description.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(description.getText().toString())
@@ -223,11 +309,35 @@
list.add(TEST_DEVICE_ITEM1);
list.add(TEST_DEVICE_ITEM2);
list.add(TEST_DEVICE_ITEM3);
- mFragment.show(mParent, list, (item) -> {});
+ AtomicBoolean isCancelBtnClicked = new AtomicBoolean(false);
+ AudioSharingDialogFragment.show(
+ mParent,
+ list,
+ new AudioSharingDialogFragment.DialogEventListener() {
+ @Override
+ public void onItemClick(AudioSharingDeviceItem item) {}
+
+ @Override
+ public void onCancelClick() {
+ isCancelBtnClicked.set(true);
+ }
+ },
+ TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- dialog.findViewById(R.id.negative_btn).performClick();
+ assertThat(dialog).isNotNull();
+ View btnView = dialog.findViewById(R.id.negative_btn);
+ assertThat(btnView).isNotNull();
+ btnView.performClick();
+ shadowMainLooper().idle();
+
assertThat(dialog.isShowing()).isFalse();
+ assertThat(isCancelBtnClicked.get()).isTrue();
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED),
+ eq(TEST_EVENT_DATA));
}
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandlerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandlerTest.java
index 570af1f..633bc06 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandlerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandlerTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
+import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
@@ -32,6 +33,7 @@
import android.content.Context;
import android.os.Looper;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.Pair;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
@@ -39,6 +41,7 @@
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.bluetooth.Utils;
+import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -51,6 +54,7 @@
import com.android.settingslib.flags.Flags;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.common.truth.Correspondence;
import org.junit.Before;
@@ -87,6 +91,7 @@
Correspondence.from(
(Fragment fragment, String tag) ->
fragment instanceof DialogFragment
+ && ((DialogFragment) fragment).getTag() != null
&& ((DialogFragment) fragment).getTag().equals(tag),
"is equal to");
@@ -107,20 +112,22 @@
private Fragment mParentFragment;
@Mock private BluetoothLeBroadcastReceiveState mState;
private Context mContext;
- private ShadowBluetoothAdapter mShadowBluetoothAdapter;
private AudioSharingDialogHandler mHandler;
+ private FakeFeatureFactory mFeatureFactory;
@Before
public void setup() {
mContext = ApplicationProvider.getApplicationContext();
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
mLocalBtManager = Utils.getLocalBtManager(mContext);
- mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- mShadowBluetoothAdapter.setEnabled(true);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+ shadowBluetoothAdapter.setEnabled(true);
+ shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
when(mLocalBtManager.getProfileManager()).thenReturn(mLocalBtProfileManager);
when(mLocalBtProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
@@ -183,9 +190,33 @@
when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState));
mHandler.handleDeviceConnected(mCachedDevice2, /* userTriggered= */ true);
shadowOf(Looper.getMainLooper()).idle();
- assertThat(mParentFragment.getChildFragmentManager().getFragments())
+ List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+ assertThat(childFragments)
.comparingElementsUsing(TAG_EQUALS)
.containsExactly(AudioSharingStopDialogFragment.tag());
+
+ AudioSharingStopDialogFragment fragment =
+ (AudioSharingStopDialogFragment) Iterables.getOnlyElement(childFragments);
+ Pair<Integer, Object>[] eventData = fragment.getEventData();
+ assertThat(eventData)
+ .asList()
+ .containsExactly(
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_SOURCE_PAGE_ID.ordinal(),
+ SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_PAGE_ID.ordinal(),
+ SettingsEnums.DIALOG_STOP_AUDIO_SHARING),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_USER_TRIGGERED.ordinal(), 1),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_DEVICE_COUNT_IN_SHARING
+ .ordinal(),
+ 1),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_CANDIDATE_DEVICE_COUNT
+ .ordinal(),
+ 0));
}
@Test
@@ -211,9 +242,33 @@
when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of());
mHandler.handleDeviceConnected(mCachedDevice1, /* userTriggered= */ true);
shadowOf(Looper.getMainLooper()).idle();
- assertThat(mParentFragment.getChildFragmentManager().getFragments())
+ List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+ assertThat(childFragments)
.comparingElementsUsing(TAG_EQUALS)
.containsExactly(AudioSharingJoinDialogFragment.tag());
+
+ AudioSharingJoinDialogFragment fragment =
+ (AudioSharingJoinDialogFragment) Iterables.getOnlyElement(childFragments);
+ Pair<Integer, Object>[] eventData = fragment.getEventData();
+ assertThat(eventData)
+ .asList()
+ .containsExactly(
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_SOURCE_PAGE_ID.ordinal(),
+ SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_PAGE_ID.ordinal(),
+ SettingsEnums.DIALOG_START_AUDIO_SHARING),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_USER_TRIGGERED.ordinal(), 1),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_DEVICE_COUNT_IN_SHARING
+ .ordinal(),
+ 0),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_CANDIDATE_DEVICE_COUNT
+ .ordinal(),
+ 2));
}
@Test
@@ -227,9 +282,33 @@
when(mAssistant.getAllSources(mDevice3)).thenReturn(ImmutableList.of(mState));
mHandler.handleDeviceConnected(mCachedDevice1, /* userTriggered= */ true);
shadowOf(Looper.getMainLooper()).idle();
- assertThat(mParentFragment.getChildFragmentManager().getFragments())
+ List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+ assertThat(childFragments)
.comparingElementsUsing(TAG_EQUALS)
.containsExactly(AudioSharingJoinDialogFragment.tag());
+
+ AudioSharingJoinDialogFragment fragment =
+ (AudioSharingJoinDialogFragment) Iterables.getOnlyElement(childFragments);
+ Pair<Integer, Object>[] eventData = fragment.getEventData();
+ assertThat(eventData)
+ .asList()
+ .containsExactly(
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_SOURCE_PAGE_ID.ordinal(),
+ SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_PAGE_ID.ordinal(),
+ SettingsEnums.DIALOG_AUDIO_SHARING_ADD_DEVICE),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_USER_TRIGGERED.ordinal(), 1),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_DEVICE_COUNT_IN_SHARING
+ .ordinal(),
+ 1),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_CANDIDATE_DEVICE_COUNT
+ .ordinal(),
+ 1));
}
@Test
@@ -245,9 +324,33 @@
when(mAssistant.getAllSources(mDevice4)).thenReturn(ImmutableList.of(mState));
mHandler.handleDeviceConnected(mCachedDevice1, /* userTriggered= */ true);
shadowOf(Looper.getMainLooper()).idle();
- assertThat(mParentFragment.getChildFragmentManager().getFragments())
+ List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+ assertThat(childFragments)
.comparingElementsUsing(TAG_EQUALS)
.containsExactly(AudioSharingDisconnectDialogFragment.tag());
+
+ AudioSharingDisconnectDialogFragment fragment =
+ (AudioSharingDisconnectDialogFragment) Iterables.getOnlyElement(childFragments);
+ Pair<Integer, Object>[] eventData = fragment.getEventData();
+ assertThat(eventData)
+ .asList()
+ .containsExactly(
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_SOURCE_PAGE_ID.ordinal(),
+ SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_PAGE_ID.ordinal(),
+ SettingsEnums.DIALOG_AUDIO_SHARING_SWITCH_DEVICE),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_USER_TRIGGERED.ordinal(), 1),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_DEVICE_COUNT_IN_SHARING
+ .ordinal(),
+ 2),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_CANDIDATE_DEVICE_COUNT
+ .ordinal(),
+ 1));
}
@Test
@@ -273,9 +376,33 @@
when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState));
mHandler.handleDeviceConnected(mCachedDevice2, /* userTriggered= */ false);
shadowOf(Looper.getMainLooper()).idle();
- assertThat(mParentFragment.getChildFragmentManager().getFragments())
+ List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+ assertThat(childFragments)
.comparingElementsUsing(TAG_EQUALS)
.containsExactly(AudioSharingStopDialogFragment.tag());
+
+ AudioSharingStopDialogFragment fragment =
+ (AudioSharingStopDialogFragment) Iterables.getOnlyElement(childFragments);
+ Pair<Integer, Object>[] eventData = fragment.getEventData();
+ assertThat(eventData)
+ .asList()
+ .containsExactly(
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_SOURCE_PAGE_ID.ordinal(),
+ SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_PAGE_ID.ordinal(),
+ SettingsEnums.DIALOG_STOP_AUDIO_SHARING),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_USER_TRIGGERED.ordinal(), 0),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_DEVICE_COUNT_IN_SHARING
+ .ordinal(),
+ 1),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_CANDIDATE_DEVICE_COUNT
+ .ordinal(),
+ 0));
}
@Test
@@ -301,9 +428,33 @@
when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of());
mHandler.handleDeviceConnected(mCachedDevice1, /* userTriggered= */ false);
shadowOf(Looper.getMainLooper()).idle();
- assertThat(mParentFragment.getChildFragmentManager().getFragments())
+ List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+ assertThat(childFragments)
.comparingElementsUsing(TAG_EQUALS)
.containsExactly(AudioSharingJoinDialogFragment.tag());
+
+ AudioSharingJoinDialogFragment fragment =
+ (AudioSharingJoinDialogFragment) Iterables.getOnlyElement(childFragments);
+ Pair<Integer, Object>[] eventData = fragment.getEventData();
+ assertThat(eventData)
+ .asList()
+ .containsExactly(
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_SOURCE_PAGE_ID.ordinal(),
+ SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_PAGE_ID.ordinal(),
+ SettingsEnums.DIALOG_START_AUDIO_SHARING),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_USER_TRIGGERED.ordinal(), 0),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_DEVICE_COUNT_IN_SHARING
+ .ordinal(),
+ 0),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_CANDIDATE_DEVICE_COUNT
+ .ordinal(),
+ 2));
}
@Test
@@ -317,9 +468,33 @@
when(mAssistant.getAllSources(mDevice3)).thenReturn(ImmutableList.of(mState));
mHandler.handleDeviceConnected(mCachedDevice1, /* userTriggered= */ false);
shadowOf(Looper.getMainLooper()).idle();
- assertThat(mParentFragment.getChildFragmentManager().getFragments())
+ List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+ assertThat(childFragments)
.comparingElementsUsing(TAG_EQUALS)
.containsExactly(AudioSharingJoinDialogFragment.tag());
+
+ AudioSharingJoinDialogFragment fragment =
+ (AudioSharingJoinDialogFragment) Iterables.getOnlyElement(childFragments);
+ Pair<Integer, Object>[] eventData = fragment.getEventData();
+ assertThat(eventData)
+ .asList()
+ .containsExactly(
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_SOURCE_PAGE_ID.ordinal(),
+ SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_PAGE_ID.ordinal(),
+ SettingsEnums.DIALOG_AUDIO_SHARING_ADD_DEVICE),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_USER_TRIGGERED.ordinal(), 0),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_DEVICE_COUNT_IN_SHARING
+ .ordinal(),
+ 1),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_CANDIDATE_DEVICE_COUNT
+ .ordinal(),
+ 1));
}
@Test
@@ -334,9 +509,33 @@
when(mAssistant.getAllSources(mDevice4)).thenReturn(ImmutableList.of(mState));
mHandler.handleDeviceConnected(mCachedDevice1, /* userTriggered= */ false);
shadowOf(Looper.getMainLooper()).idle();
- assertThat(mParentFragment.getChildFragmentManager().getFragments())
+ List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+ assertThat(childFragments)
.comparingElementsUsing(TAG_EQUALS)
.containsExactly(AudioSharingDisconnectDialogFragment.tag());
+
+ AudioSharingDisconnectDialogFragment fragment =
+ (AudioSharingDisconnectDialogFragment) Iterables.getOnlyElement(childFragments);
+ Pair<Integer, Object>[] eventData = fragment.getEventData();
+ assertThat(eventData)
+ .asList()
+ .containsExactly(
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_SOURCE_PAGE_ID.ordinal(),
+ SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_PAGE_ID.ordinal(),
+ SettingsEnums.DIALOG_AUDIO_SHARING_SWITCH_DEVICE),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_USER_TRIGGERED.ordinal(), 0),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_DEVICE_COUNT_IN_SHARING
+ .ordinal(),
+ 2),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_CANDIDATE_DEVICE_COUNT
+ .ordinal(),
+ 1));
}
@Test
@@ -357,6 +556,11 @@
mHandler.closeOpeningDialogsForLeaDevice(mCachedDevice1);
shadowOf(Looper.getMainLooper()).idle();
assertThat(mParentFragment.getChildFragmentManager().getFragments()).isEmpty();
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ mContext,
+ SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_AUTO_DISMISS,
+ SettingsEnums.DIALOG_START_AUDIO_SHARING);
}
@Test
@@ -377,6 +581,11 @@
mHandler.closeOpeningDialogsForNonLeaDevice(mCachedDevice2);
shadowOf(Looper.getMainLooper()).idle();
assertThat(mParentFragment.getChildFragmentManager().getFragments()).isEmpty();
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ mContext,
+ SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_AUTO_DISMISS,
+ SettingsEnums.DIALOG_STOP_AUDIO_SHARING);
}
private void setUpBroadcast(boolean isBroadcasting) {
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragmentTest.java
index 348efbe..481c78d 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragmentTest.java
@@ -18,13 +18,21 @@
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.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
+import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothStatusCodes;
+import android.content.Context;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.Pair;
+import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AlertDialog;
@@ -33,6 +41,7 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R;
+import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -78,15 +87,19 @@
new AudioSharingDeviceItem(TEST_DEVICE_NAME2, TEST_GROUP_ID2, /* isActive= */ false);
private static final AudioSharingDeviceItem TEST_DEVICE_ITEM3 =
new AudioSharingDeviceItem(TEST_DEVICE_NAME3, TEST_GROUP_ID3, /* isActive= */ false);
+ private static final AudioSharingDisconnectDialogFragment.DialogEventListener
+ EMPTY_EVENT_LISTENER = (AudioSharingDeviceItem item) -> {};
+ private static final Pair<Integer, Object> TEST_EVENT_DATA = Pair.create(1, 1);
+ private static final Pair<Integer, Object>[] TEST_EVENT_DATA_LIST =
+ new Pair[] {TEST_EVENT_DATA};
@Mock private BluetoothDevice mDevice1;
@Mock private BluetoothDevice mDevice3;
-
@Mock private CachedBluetoothDevice mCachedDevice1;
@Mock private CachedBluetoothDevice mCachedDevice3;
+ private FakeFeatureFactory mFeatureFactory;
private Fragment mParent;
private AudioSharingDisconnectDialogFragment mFragment;
- private ShadowBluetoothAdapter mShadowBluetoothAdapter;
private ArrayList<AudioSharingDeviceItem> mDeviceItems = new ArrayList<>();
@Before
@@ -96,12 +109,14 @@
latestAlertDialog.dismiss();
ShadowAlertDialogCompat.reset();
}
- mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- mShadowBluetoothAdapter.setEnabled(true);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+ shadowBluetoothAdapter.setEnabled(true);
+ shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
when(mDevice1.getAnonymizedAddress()).thenReturn(TEST_ADDRESS1);
when(mDevice3.getAnonymizedAddress()).thenReturn(TEST_ADDRESS3);
when(mCachedDevice1.getName()).thenReturn(TEST_DEVICE_NAME1);
@@ -117,12 +132,19 @@
}
@Test
+ public void getMetricsCategory_correctValue() {
+ assertThat(mFragment.getMetricsCategory())
+ .isEqualTo(SettingsEnums.DIALOG_AUDIO_SHARING_SWITCH_DEVICE);
+ }
+
+ @Test
public void onCreateDialog_flagOff_dialogNotExist() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mDeviceItems = new ArrayList<>();
mDeviceItems.add(TEST_DEVICE_ITEM1);
mDeviceItems.add(TEST_DEVICE_ITEM2);
- mFragment.show(mParent, mDeviceItems, mCachedDevice3, (item) -> {});
+ AudioSharingDisconnectDialogFragment.show(
+ mParent, mDeviceItems, mCachedDevice3, EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -135,12 +157,15 @@
mDeviceItems = new ArrayList<>();
mDeviceItems.add(TEST_DEVICE_ITEM1);
mDeviceItems.add(TEST_DEVICE_ITEM2);
- mFragment.show(mParent, mDeviceItems, mCachedDevice3, (item) -> {});
+ AudioSharingDisconnectDialogFragment.show(
+ mParent, mDeviceItems, mCachedDevice3, EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+ assertThat(dialog).isNotNull();
assertThat(dialog.isShowing()).isTrue();
RecyclerView view = dialog.findViewById(R.id.device_btn_list);
+ assertThat(view).isNotNull();
assertThat(view.getAdapter().getItemCount()).isEqualTo(2);
}
@@ -150,12 +175,14 @@
mDeviceItems = new ArrayList<>();
mDeviceItems.add(TEST_DEVICE_ITEM1);
mDeviceItems.add(TEST_DEVICE_ITEM2);
- mFragment.show(mParent, mDeviceItems, mCachedDevice3, (item) -> {});
+ AudioSharingDisconnectDialogFragment.show(
+ mParent, mDeviceItems, mCachedDevice3, EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
- AtomicBoolean isItemBtnClicked = new AtomicBoolean(false);
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+ assertThat(dialog).isNotNull();
assertThat(dialog.isShowing()).isTrue();
RecyclerView view = dialog.findViewById(R.id.device_btn_list);
+ assertThat(view).isNotNull();
assertThat(view.getAdapter().getItemCount()).isEqualTo(2);
Button btn1 =
view.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.device_button);
@@ -173,37 +200,71 @@
TEST_DEVICE_NAME2));
// Update dialog content for device with same group
- mFragment.show(mParent, mDeviceItems, mCachedDevice3, (item) -> isItemBtnClicked.set(true));
+ AtomicBoolean isItemBtnClicked = new AtomicBoolean(false);
+ AudioSharingDisconnectDialogFragment.show(
+ mParent,
+ mDeviceItems,
+ mCachedDevice3,
+ (AudioSharingDeviceItem item) -> isItemBtnClicked.set(true),
+ TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog.isShowing()).isTrue();
+ verify(mFeatureFactory.metricsFeatureProvider, times(0))
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_AUTO_DISMISS),
+ eq(SettingsEnums.DIALOG_AUDIO_SHARING_SWITCH_DEVICE));
+
btn1 = view.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.device_button);
btn1.performClick();
+ shadowMainLooper().idle();
+ assertThat(dialog.isShowing()).isFalse();
assertThat(isItemBtnClicked.get()).isTrue();
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_POSITIVE_BTN_CLICKED),
+ eq(TEST_EVENT_DATA));
}
@Test
- public void onCreateDialog_dialogIsShowingForNewGroup_updateDialog() {
+ public void onCreateDialog_dialogIsShowingForNewGroup_showNewDialog() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mDeviceItems = new ArrayList<>();
mDeviceItems.add(TEST_DEVICE_ITEM1);
mDeviceItems.add(TEST_DEVICE_ITEM2);
- mFragment.show(mParent, mDeviceItems, mCachedDevice3, (item) -> {});
+ AudioSharingDisconnectDialogFragment.show(
+ mParent, mDeviceItems, mCachedDevice3, EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+ assertThat(dialog).isNotNull();
assertThat(dialog.isShowing()).isTrue();
RecyclerView view = dialog.findViewById(R.id.device_btn_list);
+ assertThat(view).isNotNull();
assertThat(view.getAdapter().getItemCount()).isEqualTo(2);
// Show new dialog for device with new group
ArrayList<AudioSharingDeviceItem> newDeviceItems = new ArrayList<>();
newDeviceItems.add(TEST_DEVICE_ITEM2);
newDeviceItems.add(TEST_DEVICE_ITEM3);
- mFragment.show(mParent, newDeviceItems, mCachedDevice1, (item) -> {});
+ AudioSharingDisconnectDialogFragment.show(
+ mParent,
+ newDeviceItems,
+ mCachedDevice1,
+ EMPTY_EVENT_LISTENER,
+ TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog.isShowing()).isTrue();
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_AUTO_DISMISS),
+ eq(SettingsEnums.DIALOG_AUDIO_SHARING_SWITCH_DEVICE));
+
view = dialog.findViewById(R.id.device_btn_list);
+ assertThat(view).isNotNull();
assertThat(view.getAdapter().getItemCount()).isEqualTo(2);
Button btn1 =
view.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.device_button);
@@ -227,12 +288,27 @@
mDeviceItems = new ArrayList<>();
mDeviceItems.add(TEST_DEVICE_ITEM1);
mDeviceItems.add(TEST_DEVICE_ITEM2);
- mFragment.show(mParent, mDeviceItems, mCachedDevice3, (item) -> {});
+ AudioSharingDisconnectDialogFragment.show(
+ mParent, mDeviceItems, mCachedDevice3, EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
+ shadowMainLooper().idle();
+ AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+ assertThat(dialog).isNotNull();
+ assertThat(dialog.isShowing()).isTrue();
+ View btnView = dialog.findViewById(R.id.negative_btn);
+ assertThat(btnView).isNotNull();
+ btnView.performClick();
shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog.isShowing()).isTrue();
- dialog.findViewById(R.id.negative_btn).performClick();
assertThat(dialog.isShowing()).isFalse();
+ verify(mFeatureFactory.metricsFeatureProvider, times(0))
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_AUTO_DISMISS),
+ eq(SettingsEnums.DIALOG_AUDIO_SHARING_SWITCH_DEVICE));
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED),
+ eq(TEST_EVENT_DATA));
}
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragmentTest.java
index 2d55d97..c7b21ad 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragmentTest.java
@@ -18,13 +18,19 @@
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.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothStatusCodes;
+import android.content.Context;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.Pair;
+import android.view.View;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
@@ -32,6 +38,7 @@
import com.android.settings.R;
import com.android.settings.bluetooth.Utils;
+import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
@@ -82,6 +89,9 @@
@Override
public void onCancelClick() {}
};
+ private static final Pair<Integer, Object> TEST_EVENT_DATA = Pair.create(1, 1);
+ private static final Pair<Integer, Object>[] TEST_EVENT_DATA_LIST =
+ new Pair[] {TEST_EVENT_DATA};
@Mock private CachedBluetoothDevice mCachedDevice1;
@Mock private CachedBluetoothDevice mCachedDevice2;
@@ -90,7 +100,7 @@
@Mock private LocalBluetoothLeBroadcast mBroadcast;
private Fragment mParent;
private AudioSharingJoinDialogFragment mFragment;
- private ShadowBluetoothAdapter mShadowBluetoothAdapter;
+ private FakeFeatureFactory mFeatureFactory;
@Before
public void setUp() {
@@ -99,12 +109,14 @@
latestAlertDialog.dismiss();
ShadowAlertDialogCompat.reset();
}
- mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- mShadowBluetoothAdapter.setEnabled(true);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+ shadowBluetoothAdapter.setEnabled(true);
+ shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
when(mCachedDevice1.getName()).thenReturn(TEST_DEVICE_NAME1);
when(mCachedDevice2.getName()).thenReturn(TEST_DEVICE_NAME2);
mFragment = new AudioSharingJoinDialogFragment();
@@ -137,7 +149,12 @@
@Test
public void onCreateDialog_flagOff_dialogNotExist() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- mFragment.show(mParent, new ArrayList<>(), mCachedDevice2, EMPTY_EVENT_LISTENER);
+ AudioSharingJoinDialogFragment.show(
+ mParent,
+ new ArrayList<>(),
+ mCachedDevice2,
+ EMPTY_EVENT_LISTENER,
+ TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNull();
@@ -146,7 +163,12 @@
@Test
public void onCreateDialog_flagOn_dialogShowTextForSingleDevice() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- mFragment.show(mParent, new ArrayList<>(), mCachedDevice2, EMPTY_EVENT_LISTENER);
+ AudioSharingJoinDialogFragment.show(
+ mParent,
+ new ArrayList<>(),
+ mCachedDevice2,
+ EMPTY_EVENT_LISTENER,
+ TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNotNull();
@@ -160,7 +182,8 @@
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
ArrayList<AudioSharingDeviceItem> list = new ArrayList<>();
list.add(TEST_DEVICE_ITEM1);
- mFragment.show(mParent, list, mCachedDevice2, EMPTY_EVENT_LISTENER);
+ AudioSharingJoinDialogFragment.show(
+ mParent, list, mCachedDevice2, EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNotNull();
@@ -179,7 +202,8 @@
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
ArrayList<AudioSharingDeviceItem> list = new ArrayList<>();
list.add(TEST_DEVICE_ITEM1);
- mFragment.show(mParent, list, mCachedDevice2, EMPTY_EVENT_LISTENER);
+ AudioSharingJoinDialogFragment.show(
+ mParent, list, mCachedDevice2, EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNotNull();
@@ -188,7 +212,8 @@
// Update the content
ArrayList<AudioSharingDeviceItem> list2 = new ArrayList<>();
list2.add(TEST_DEVICE_ITEM2);
- mFragment.show(mParent, list2, mCachedDevice1, EMPTY_EVENT_LISTENER);
+ AudioSharingJoinDialogFragment.show(
+ mParent, list2, mCachedDevice1, EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNotNull();
@@ -205,11 +230,25 @@
@Test
public void onCreateDialog_clickCancel_dialogDismiss() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- mFragment.show(mParent, new ArrayList<>(), mCachedDevice2, EMPTY_EVENT_LISTENER);
+ AudioSharingJoinDialogFragment.show(
+ mParent,
+ new ArrayList<>(),
+ mCachedDevice2,
+ EMPTY_EVENT_LISTENER,
+ TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- dialog.findViewById(R.id.negative_btn).performClick();
+ assertThat(dialog).isNotNull();
+ View btnView = dialog.findViewById(R.id.negative_btn);
+ assertThat(btnView).isNotNull();
+ btnView.performClick();
+ shadowMainLooper().idle();
assertThat(dialog.isShowing()).isFalse();
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED),
+ eq(TEST_EVENT_DATA));
}
@Test
@@ -228,12 +267,22 @@
@Override
public void onCancelClick() {}
- });
+ },
+ TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- dialog.findViewById(R.id.positive_btn).performClick();
+ assertThat(dialog).isNotNull();
+ View btnView = dialog.findViewById(R.id.positive_btn);
+ assertThat(btnView).isNotNull();
+ btnView.performClick();
+ shadowMainLooper().idle();
assertThat(dialog.isShowing()).isFalse();
assertThat(isShareBtnClicked.get()).isTrue();
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_POSITIVE_BTN_CLICKED),
+ eq(TEST_EVENT_DATA));
}
@Test
@@ -252,11 +301,21 @@
public void onCancelClick() {
isCancelBtnClicked.set(true);
}
- });
+ },
+ TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- dialog.findViewById(R.id.negative_btn).performClick();
+ assertThat(dialog).isNotNull();
+ View btnView = dialog.findViewById(R.id.negative_btn);
+ assertThat(btnView).isNotNull();
+ btnView.performClick();
+ shadowMainLooper().idle();
assertThat(dialog.isShowing()).isFalse();
assertThat(isCancelBtnClicked.get()).isTrue();
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED),
+ eq(TEST_EVENT_DATA));
}
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceControllerTest.java
index b8bee1a..046a4ce 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceControllerTest.java
@@ -25,12 +25,15 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+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.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.os.Looper;
@@ -84,48 +87,68 @@
@Mock private BluetoothEventManager mBtEventManager;
@Mock private LocalBluetoothProfileManager mLocalBtProfileManager;
@Mock private LocalBluetoothLeBroadcast mBroadcast;
+ @Mock private BluetoothLeBroadcastMetadata mMetadata;
private AudioSharingPreferenceController mController;
- private ShadowBluetoothAdapter mShadowBluetoothAdapter;
- private LocalBluetoothManager mLocalBluetoothManager;
private Lifecycle mLifecycle;
private LifecycleOwner mLifecycleOwner;
- private Preference mPreference;
+ @Spy private Preference mPreference;
@Before
public void setUp() {
- mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- mShadowBluetoothAdapter.setEnabled(true);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+ shadowBluetoothAdapter.setEnabled(true);
+ shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
mLifecycleOwner = () -> mLifecycle;
mLifecycle = new Lifecycle(mLifecycleOwner);
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
- mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
- when(mLocalBluetoothManager.getEventManager()).thenReturn(mBtEventManager);
- when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBtProfileManager);
+ LocalBluetoothManager localBluetoothManager = Utils.getLocalBtManager(mContext);
+ when(localBluetoothManager.getEventManager()).thenReturn(mBtEventManager);
+ when(localBluetoothManager.getProfileManager()).thenReturn(mLocalBtProfileManager);
when(mLocalBtProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
mController = new AudioSharingPreferenceController(mContext, PREF_KEY);
- mPreference = new Preference(mContext);
+ mPreference = spy(new Preference(mContext));
when(mScreen.findPreference(PREF_KEY)).thenReturn(mPreference);
}
@Test
- public void onStart_registerCallback() {
+ public void onStart_flagOn_registerCallback() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mController.onStart(mLifecycleOwner);
verify(mBtEventManager).registerCallback(mController);
verify(mBroadcast).registerServiceCallBack(any(), any(BluetoothLeBroadcast.Callback.class));
}
@Test
- public void onStop_unregisterCallback() {
+ public void onStart_flagOff_skipRegisterCallback() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mController.onStart(mLifecycleOwner);
+ verify(mBtEventManager, never()).registerCallback(mController);
+ verify(mBroadcast, never())
+ .registerServiceCallBack(any(), any(BluetoothLeBroadcast.Callback.class));
+ }
+
+ @Test
+ public void onStop_flagOn_unregisterCallback() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mController.onStop(mLifecycleOwner);
verify(mBtEventManager).unregisterCallback(mController);
verify(mBroadcast).unregisterServiceCallBack(any(BluetoothLeBroadcast.Callback.class));
}
@Test
+ public void onStop_flagOff_skipUnregisterCallback() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mController.onStop(mLifecycleOwner);
+ verify(mBtEventManager, never()).unregisterCallback(mController);
+ verify(mBroadcast, never())
+ .unregisterServiceCallBack(any(BluetoothLeBroadcast.Callback.class));
+ }
+
+ @Test
public void getAvailabilityStatus_flagOn() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
@@ -166,4 +189,42 @@
assertThat(mPreference.getSummary().toString())
.isEqualTo(mContext.getString(R.string.audio_sharing_summary_off));
}
+
+ @Test
+ public void testBluetoothLeBroadcastCallbacks_refreshSummary() {
+ mController.displayPreference(mScreen);
+
+ when(mBroadcast.isEnabled(any())).thenReturn(true);
+ mController.mBroadcastCallback.onBroadcastStarted(/* reason= */ 1, /* broadcastId= */ 1);
+ shadowOf(Looper.getMainLooper()).idle();
+ assertThat(mPreference.getSummary().toString())
+ .isEqualTo(mContext.getString(R.string.audio_sharing_summary_on));
+
+ when(mBroadcast.isEnabled(any())).thenReturn(false);
+ mController.mBroadcastCallback.onBroadcastStopped(/* reason= */ 1, /* broadcastId= */ 1);
+ shadowOf(Looper.getMainLooper()).idle();
+ assertThat(mPreference.getSummary().toString())
+ .isEqualTo(mContext.getString(R.string.audio_sharing_summary_off));
+ }
+
+ @Test
+ public void testBluetoothLeBroadcastCallbacks_doNothing() {
+ mController.displayPreference(mScreen);
+
+ mController.mBroadcastCallback.onBroadcastMetadataChanged(/* reason= */ 1, mMetadata);
+ verify(mPreference, never()).setSummary(any());
+ mController.mBroadcastCallback.onBroadcastUpdated(/* reason= */ 1, /* broadcastId= */ 1);
+ verify(mPreference, never()).setSummary(any());
+ mController.mBroadcastCallback.onPlaybackStarted(/* reason= */ 1, /* broadcastId= */ 1);
+ verify(mPreference, never()).setSummary(any());
+ mController.mBroadcastCallback.onPlaybackStopped(/* reason= */ 1, /* broadcastId= */ 1);
+ verify(mPreference, never()).setSummary(any());
+ mController.mBroadcastCallback.onBroadcastStartFailed(/* reason= */ 1);
+ verify(mPreference, never()).setSummary(any());
+ mController.mBroadcastCallback.onBroadcastStopFailed(/* reason= */ 1);
+ verify(mPreference, never()).setSummary(any());
+ mController.mBroadcastCallback.onBroadcastUpdateFailed(
+ /* reason= */ 1, /* broadcastId= */ 1);
+ verify(mPreference, never()).setSummary(any());
+ }
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragmentTest.java
index 84d7a31..7d46a18 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragmentTest.java
@@ -18,13 +18,21 @@
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.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
+import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothStatusCodes;
+import android.content.Context;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.Pair;
+import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
@@ -32,6 +40,7 @@
import androidx.fragment.app.FragmentActivity;
import com.android.settings.R;
+import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -76,14 +85,19 @@
private static final AudioSharingDeviceItem TEST_DEVICE_ITEM3 =
new AudioSharingDeviceItem(
TEST_DEVICE_NAME3, TEST_DEVICE_GROUP_ID3, /* isActive= */ false);
+ private static final AudioSharingStopDialogFragment.DialogEventListener EMPTY_EVENT_LISTENER =
+ () -> {};
+ private static final Pair<Integer, Object> TEST_EVENT_DATA = Pair.create(1, 1);
+ private static final Pair<Integer, Object>[] TEST_EVENT_DATA_LIST =
+ new Pair[] {TEST_EVENT_DATA};
@Mock private CachedBluetoothDevice mCachedDevice1;
@Mock private CachedBluetoothDevice mCachedDevice2;
@Mock private BluetoothDevice mDevice1;
@Mock private BluetoothDevice mDevice2;
+ private FakeFeatureFactory mFeatureFactory;
private Fragment mParent;
private AudioSharingStopDialogFragment mFragment;
- private ShadowBluetoothAdapter mShadowBluetoothAdapter;
@Before
public void setUp() {
@@ -92,12 +106,14 @@
latestAlertDialog.dismiss();
ShadowAlertDialogCompat.reset();
}
- mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- mShadowBluetoothAdapter.setEnabled(true);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+ shadowBluetoothAdapter.setEnabled(true);
+ shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
when(mCachedDevice1.getName()).thenReturn(TEST_DEVICE_NAME1);
when(mCachedDevice1.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID1);
when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
@@ -111,9 +127,20 @@
}
@Test
+ public void getMetricsCategory_correctValue() {
+ assertThat(mFragment.getMetricsCategory())
+ .isEqualTo(SettingsEnums.DIALOG_STOP_AUDIO_SHARING);
+ }
+
+ @Test
public void onCreateDialog_flagOff_dialogNotExist() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- mFragment.show(mParent, ImmutableList.of(), mCachedDevice1, () -> {});
+ AudioSharingStopDialogFragment.show(
+ mParent,
+ ImmutableList.of(),
+ mCachedDevice1,
+ EMPTY_EVENT_LISTENER,
+ TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNull();
@@ -122,12 +149,18 @@
@Test
public void onCreateDialog_oneDeviceInSharing_showDialogWithCorrectMessage() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- mFragment.show(mParent, ImmutableList.of(TEST_DEVICE_ITEM2), mCachedDevice1, () -> {});
+ AudioSharingStopDialogFragment.show(
+ mParent,
+ ImmutableList.of(TEST_DEVICE_ITEM2),
+ mCachedDevice1,
+ EMPTY_EVENT_LISTENER,
+ TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNotNull();
assertThat(dialog.isShowing()).isTrue();
TextView view = dialog.findViewById(R.id.description_text);
+ assertThat(view).isNotNull();
assertThat(view.getText().toString())
.isEqualTo(
mParent.getString(
@@ -137,16 +170,18 @@
@Test
public void onCreateDialog_twoDeviceInSharing_showDialogWithCorrectMessage() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- mFragment.show(
+ AudioSharingStopDialogFragment.show(
mParent,
ImmutableList.of(TEST_DEVICE_ITEM2, TEST_DEVICE_ITEM3),
mCachedDevice1,
- () -> {});
+ EMPTY_EVENT_LISTENER,
+ TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNotNull();
assertThat(dialog.isShowing()).isTrue();
TextView view = dialog.findViewById(R.id.description_text);
+ assertThat(view).isNotNull();
assertThat(view.getText().toString())
.isEqualTo(
mParent.getString(
@@ -158,57 +193,99 @@
@Test
public void onCreateDialog_dialogIsShowingForSameDevice_updateDialog() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- mFragment.show(mParent, ImmutableList.of(), mCachedDevice1, () -> {});
+ AudioSharingStopDialogFragment.show(
+ mParent,
+ ImmutableList.of(),
+ mCachedDevice1,
+ EMPTY_EVENT_LISTENER,
+ TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNotNull();
assertThat(dialog.isShowing()).isTrue();
TextView view = dialog.findViewById(R.id.description_text);
+ assertThat(view).isNotNull();
assertThat(view.getText().toString())
.isEqualTo(mParent.getString(R.string.audio_sharing_stop_dialog_with_more_content));
// Update the content
AtomicBoolean isStopBtnClicked = new AtomicBoolean(false);
- mFragment.show(
- mParent, ImmutableList.of(), mCachedDevice1, () -> isStopBtnClicked.set(true));
+ AudioSharingStopDialogFragment.show(
+ mParent,
+ ImmutableList.of(),
+ mCachedDevice1,
+ () -> isStopBtnClicked.set(true),
+ TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNotNull();
assertThat(dialog.isShowing()).isTrue();
+ verify(mFeatureFactory.metricsFeatureProvider, times(0))
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_AUTO_DISMISS),
+ eq(SettingsEnums.DIALOG_STOP_AUDIO_SHARING));
- dialog.findViewById(android.R.id.button1).performClick();
+ View btnView = dialog.findViewById(android.R.id.button1);
+ assertThat(btnView).isNotNull();
+ btnView.performClick();
shadowMainLooper().idle();
assertThat(dialog.isShowing()).isFalse();
assertThat(isStopBtnClicked.get()).isTrue();
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_POSITIVE_BTN_CLICKED),
+ eq(TEST_EVENT_DATA));
}
@Test
public void onCreateDialog_dialogIsShowingForNewDevice_showNewDialog() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- mFragment.show(mParent, ImmutableList.of(), mCachedDevice1, () -> {});
+ AudioSharingStopDialogFragment.show(
+ mParent,
+ ImmutableList.of(),
+ mCachedDevice1,
+ EMPTY_EVENT_LISTENER,
+ TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNotNull();
assertThat(dialog.isShowing()).isTrue();
TextView view = dialog.findViewById(R.id.description_text);
+ assertThat(view).isNotNull();
assertThat(view.getText().toString())
.isEqualTo(mParent.getString(R.string.audio_sharing_stop_dialog_with_more_content));
TextView title = dialog.findViewById(R.id.title_text);
+ assertThat(title).isNotNull();
assertThat(title.getText().toString())
.isEqualTo(
mParent.getString(
R.string.audio_sharing_stop_dialog_title, TEST_DEVICE_NAME1));
// Show new dialog
- mFragment.show(mParent, ImmutableList.of(), mCachedDevice2, () -> {});
+ AudioSharingStopDialogFragment.show(
+ mParent,
+ ImmutableList.of(),
+ mCachedDevice2,
+ EMPTY_EVENT_LISTENER,
+ TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNotNull();
assertThat(dialog.isShowing()).isTrue();
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_AUTO_DISMISS),
+ eq(SettingsEnums.DIALOG_STOP_AUDIO_SHARING));
+
view = dialog.findViewById(R.id.description_text);
+ assertThat(view).isNotNull();
assertThat(view.getText().toString())
.isEqualTo(mParent.getString(R.string.audio_sharing_stop_dialog_with_more_content));
title = dialog.findViewById(R.id.title_text);
+ assertThat(title).isNotNull();
assertThat(title.getText().toString())
.isEqualTo(
mParent.getString(
@@ -218,25 +295,60 @@
@Test
public void onCreateDialog_clickCancel_dialogDismiss() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- mFragment.show(mParent, ImmutableList.of(), mCachedDevice1, () -> {});
+ AudioSharingStopDialogFragment.show(
+ mParent,
+ ImmutableList.of(),
+ mCachedDevice1,
+ EMPTY_EVENT_LISTENER,
+ TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- dialog.findViewById(android.R.id.button2).performClick();
+ assertThat(dialog).isNotNull();
+ View btnView = dialog.findViewById(android.R.id.button2);
+ assertThat(btnView).isNotNull();
+ btnView.performClick();
shadowMainLooper().idle();
assertThat(dialog.isShowing()).isFalse();
+ verify(mFeatureFactory.metricsFeatureProvider, times(0))
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_AUTO_DISMISS),
+ eq(SettingsEnums.DIALOG_STOP_AUDIO_SHARING));
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED),
+ eq(TEST_EVENT_DATA));
}
@Test
public void onCreateDialog_clickShare_callbackTriggered() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
AtomicBoolean isStopBtnClicked = new AtomicBoolean(false);
- mFragment.show(
- mParent, ImmutableList.of(), mCachedDevice1, () -> isStopBtnClicked.set(true));
+ AudioSharingStopDialogFragment.show(
+ mParent,
+ ImmutableList.of(),
+ mCachedDevice1,
+ () -> isStopBtnClicked.set(true),
+ TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- dialog.findViewById(android.R.id.button1).performClick();
+ assertThat(dialog).isNotNull();
+ View btnView = dialog.findViewById(android.R.id.button1);
+ assertThat(btnView).isNotNull();
+ btnView.performClick();
shadowMainLooper().idle();
assertThat(dialog.isShowing()).isFalse();
assertThat(isStopBtnClicked.get()).isTrue();
+ verify(mFeatureFactory.metricsFeatureProvider, times(0))
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_AUTO_DISMISS),
+ eq(SettingsEnums.DIALOG_STOP_AUDIO_SHARING));
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ any(Context.class),
+ eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_POSITIVE_BTN_CLICKED),
+ eq(TEST_EVENT_DATA));
}
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
index 0ead2d5..8f85feb8 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
@@ -23,6 +23,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -30,6 +31,7 @@
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
+import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcast;
@@ -43,12 +45,17 @@
import android.os.Looper;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.FeatureFlagUtils;
+import android.util.Pair;
import android.widget.CompoundButton;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LifecycleOwner;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.bluetooth.Utils;
+import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settings.testutils.shadow.ShadowThreadUtils;
@@ -65,6 +72,8 @@
import com.android.settingslib.flags.Flags;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.truth.Correspondence;
import org.junit.Before;
import org.junit.Rule;
@@ -77,7 +86,9 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.androidx.fragment.FragmentController;
+import java.util.List;
import java.util.concurrent.Executor;
@RunWith(RobolectricTestRunner.class)
@@ -88,6 +99,18 @@
ShadowThreadUtils.class,
})
public class AudioSharingSwitchBarControllerTest {
+ private static final String TEST_DEVICE_NAME1 = "test1";
+ private static final String TEST_DEVICE_NAME2 = "test2";
+ private static final int TEST_DEVICE_GROUP_ID1 = 1;
+ private static final int TEST_DEVICE_GROUP_ID2 = 2;
+ private static final Correspondence<Fragment, String> TAG_EQUALS =
+ Correspondence.from(
+ (Fragment fragment, String tag) ->
+ fragment instanceof DialogFragment
+ && ((DialogFragment) fragment).getTag() != null
+ && ((DialogFragment) fragment).getTag().equals(tag),
+ "is equal to");
+
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -99,17 +122,19 @@
@Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
@Mock private VolumeControlProfile mVolumeControl;
@Mock private CompoundButton mBtnView;
- @Mock private CachedBluetoothDevice mCachedDevice;
- @Mock private BluetoothDevice mDevice;
+ @Mock private CachedBluetoothDevice mCachedDevice1;
+ @Mock private CachedBluetoothDevice mCachedDevice2;
+ @Mock private BluetoothDevice mDevice1;
+ @Mock private BluetoothDevice mDevice2;
private SettingsMainSwitchBar mSwitchBar;
private AudioSharingSwitchBarController mController;
- private AudioSharingSwitchBarController.OnAudioSharingStateChangedListener mListener;
+ private FakeFeatureFactory mFeatureFactory;
private Lifecycle mLifecycle;
private LifecycleOwner mLifecycleOwner;
private boolean mOnAudioSharingStateChanged;
private boolean mOnAudioSharingServiceConnected;
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
- private LocalBluetoothManager mLocalBluetoothManager;
+ private Fragment mParentFragment;
@Before
public void setUp() {
@@ -122,13 +147,20 @@
mLifecycleOwner = () -> mLifecycle;
mLifecycle = new Lifecycle(mLifecycleOwner);
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
- mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
- when(mLocalBluetoothManager.getProfileManager()).thenReturn(mBtProfileManager);
- when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
- when(mDeviceManager.findDevice(mDevice)).thenReturn(mCachedDevice);
- when(mCachedDevice.getDevice()).thenReturn(mDevice);
- when(mCachedDevice.getGroupId()).thenReturn(1);
- when(mCachedDevice.getName()).thenReturn("test");
+ LocalBluetoothManager localBluetoothManager = Utils.getLocalBtManager(mContext);
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
+ when(localBluetoothManager.getProfileManager()).thenReturn(mBtProfileManager);
+ when(localBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
+ when(mDeviceManager.findDevice(mDevice1)).thenReturn(mCachedDevice1);
+ when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
+ when(mCachedDevice1.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID1);
+ when(mCachedDevice1.getName()).thenReturn(TEST_DEVICE_NAME1);
+ when(mCachedDevice1.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(false);
+ when(mDeviceManager.findDevice(mDevice2)).thenReturn(mCachedDevice2);
+ when(mCachedDevice2.getDevice()).thenReturn(mDevice2);
+ when(mCachedDevice2.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID2);
+ when(mCachedDevice2.getName()).thenReturn(TEST_DEVICE_NAME2);
+ when(mCachedDevice2.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(true);
when(mBtProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
when(mBtProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
when(mBtProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControl);
@@ -153,7 +185,7 @@
mSwitchBar.setDisabledByAdmin(mock(RestrictedLockUtils.EnforcedAdmin.class));
mOnAudioSharingStateChanged = false;
mOnAudioSharingServiceConnected = false;
- mListener =
+ AudioSharingSwitchBarController.OnAudioSharingStateChangedListener listener =
new AudioSharingSwitchBarController.OnAudioSharingStateChangedListener() {
@Override
public void onAudioSharingStateChanged() {
@@ -165,7 +197,14 @@
mOnAudioSharingServiceConnected = true;
}
};
- mController = new AudioSharingSwitchBarController(mContext, mSwitchBar, mListener);
+ mController = new AudioSharingSwitchBarController(mContext, mSwitchBar, listener);
+ mParentFragment = new Fragment();
+ FragmentController.setupFragment(
+ mParentFragment,
+ FragmentActivity.class,
+ 0 /* containerViewId */,
+ null /* bundle */);
+ mController.init(mParentFragment);
}
@Test
@@ -356,7 +395,7 @@
when(mBtnView.isEnabled()).thenReturn(true);
when(mAssistant.getDevicesMatchingConnectionStates(
new int[] {BluetoothProfile.STATE_CONNECTED}))
- .thenReturn(ImmutableList.of(mDevice));
+ .thenReturn(ImmutableList.of(mDevice1));
doNothing().when(mBroadcast).startPrivateBroadcast();
mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
verify(mBroadcast).startPrivateBroadcast();
@@ -380,4 +419,50 @@
mController.onCheckedChanged(mBtnView, /* isChecked= */ false);
verify(mBroadcast).stopBroadcast(1);
}
+
+ @Test
+ public void onPlaybackStarted_showJoinAudioSharingDialog() {
+ FeatureFlagUtils.setEnabled(
+ mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
+ when(mBtnView.isEnabled()).thenReturn(true);
+ when(mAssistant.getDevicesMatchingConnectionStates(
+ new int[] {BluetoothProfile.STATE_CONNECTED}))
+ .thenReturn(ImmutableList.of(mDevice2, mDevice1));
+ doNothing().when(mBroadcast).startPrivateBroadcast();
+ mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
+ verify(mBroadcast).startPrivateBroadcast();
+ mController.mBroadcastCallback.onPlaybackStarted(0, 0);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
+
+ List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+ assertThat(childFragments)
+ .comparingElementsUsing(TAG_EQUALS)
+ .containsExactly(AudioSharingDialogFragment.tag());
+
+ AudioSharingDialogFragment fragment =
+ (AudioSharingDialogFragment) Iterables.getOnlyElement(childFragments);
+ Pair<Integer, Object>[] eventData = fragment.getEventData();
+ assertThat(eventData)
+ .asList()
+ .containsExactly(
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_SOURCE_PAGE_ID.ordinal(),
+ SettingsEnums.AUDIO_SHARING_SETTINGS),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_PAGE_ID.ordinal(),
+ SettingsEnums.DIALOG_AUDIO_SHARING_ADD_DEVICE),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_USER_TRIGGERED.ordinal(), 0),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_DEVICE_COUNT_IN_SHARING
+ .ordinal(),
+ 1),
+ Pair.create(
+ AudioSharingUtils.MetricKey.METRIC_KEY_CANDIDATE_DEVICE_COUNT
+ .ordinal(),
+ 1));
+ }
}
diff --git a/tests/robotests/src/com/android/settings/development/BackgroundProcessLimitPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/BackgroundProcessLimitPreferenceControllerTest.java
new file mode 100644
index 0000000..d51547e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/development/BackgroundProcessLimitPreferenceControllerTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.development;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.IActivityManager;
+import android.content.Context;
+import android.os.RemoteException;
+
+import androidx.preference.ListPreference;
+import androidx.preference.PreferenceScreen;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class BackgroundProcessLimitPreferenceControllerTest {
+
+ @Mock
+ private IActivityManager mActivityManager;
+ @Mock
+ private ListPreference mPreference;
+ @Mock
+ private PreferenceScreen mScreen;
+
+ /**
+ * 0: Standard limit
+ * 1: No Background processes
+ * 2: At most 1 process
+ * 3: At most 2 processes
+ * 4: At most 3 processes
+ * 5: At most 4 processes
+ */
+ private String[] mListValues;
+ private String[] mListSummaries;
+ private Context mContext;
+ private BackgroundProcessLimitPreferenceController mController;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mListValues = mContext.getResources()
+ .getStringArray(com.android.settingslib.R.array.app_process_limit_values);
+ mListSummaries = mContext.getResources()
+ .getStringArray(com.android.settingslib.R.array.app_process_limit_entries);
+ mController = spy(new BackgroundProcessLimitPreferenceController(mContext));
+ doReturn(mActivityManager).when(mController).getActivityManagerService();
+ when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
+ mController.displayPreference(mScreen);
+ }
+
+ @Test
+ public void onPreferenceChange_noBackgroundProcessSet_shouldSetToNoBackgroundProcess()
+ throws RemoteException {
+ mController.onPreferenceChange(mPreference, mListValues[1]);
+
+ verify(mActivityManager).setProcessLimit(Integer.valueOf(mListValues[1]));
+ }
+
+ @Test
+ public void onPreferenceChange_1ProcessSet_shouldSetTo1BackgroundProcess()
+ throws RemoteException {
+ mController.onPreferenceChange(mPreference, mListValues[2]);
+
+ verify(mActivityManager).setProcessLimit(Integer.valueOf(mListValues[2]));
+ }
+
+ @Test
+ public void updateState_noBackgroundProcessSet_shouldSetPreferenceToNoBackgroundProcess()
+ throws RemoteException {
+ when(mActivityManager.getProcessLimit()).thenReturn(Integer.valueOf(mListValues[1]));
+
+ mController.updateState(mPreference);
+
+ verify(mPreference).setValue(mListValues[1]);
+ verify(mPreference).setSummary(mListSummaries[1]);
+ }
+
+ @Test
+ public void updateState_1ProcessSet_shouldSetPreference1BackgroundProcess()
+ throws RemoteException {
+ when(mActivityManager.getProcessLimit()).thenReturn(Integer.valueOf(mListValues[2]));
+
+ mController.updateState(mPreference);
+
+ verify(mPreference).setValue(mListValues[2]);
+ verify(mPreference).setSummary(mListSummaries[2]);
+ }
+
+ @Test
+ public void updateState_veryHighLimit_shouldDefaultToStandardLimit() throws RemoteException {
+ when(mActivityManager.getProcessLimit()).thenReturn(Integer.MAX_VALUE);
+
+ mController.updateState(mPreference);
+
+ verify(mPreference).setValue(mListValues[0]);
+ verify(mPreference).setSummary(mListSummaries[0]);
+ }
+
+ @Test
+ public void onDeveloperOptionsSwitchDisabled_shouldDisableAndResetPreference()
+ throws RemoteException {
+ mController.onDeveloperOptionsSwitchDisabled();
+
+ verify(mPreference).setEnabled(false);
+ verify(mActivityManager).setProcessLimit(-1);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceControllerTest.java
new file mode 100644
index 0000000..c1c4d61
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceControllerTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.AutomaticZenRule;
+import android.content.Context;
+import android.service.notification.ZenModeConfig;
+
+import androidx.preference.TwoStatePreference;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Calendar;
+
+@RunWith(RobolectricTestRunner.class)
+public class ZenModeExitAtAlarmPreferenceControllerTest {
+ private Context mContext;
+ @Mock
+ private ZenModesBackend mBackend;
+
+ private ZenModeExitAtAlarmPreferenceController mPrefController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = ApplicationProvider.getApplicationContext();
+ mPrefController = new ZenModeExitAtAlarmPreferenceController(mContext, "exit_at_alarm",
+ mBackend);
+ }
+
+ @Test
+ public void testUpdateState() {
+ TwoStatePreference preference = mock(TwoStatePreference.class);
+
+ // previously: don't exit at alarm
+ ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo();
+ scheduleInfo.days = new int[] { Calendar.MONDAY };
+ scheduleInfo.startHour = 1;
+ scheduleInfo.endHour = 2;
+ scheduleInfo.exitAtAlarm = false;
+
+ ZenMode mode = new ZenMode("id",
+ new AutomaticZenRule.Builder("name",
+ ZenModeConfig.toScheduleConditionId(scheduleInfo)).build(),
+ true); // is active
+
+ // need to call updateZenMode for the first call
+ mPrefController.updateZenMode(preference, mode);
+ verify(preference).setChecked(false);
+
+ // Now update state after changing exitAtAlarm
+ scheduleInfo.exitAtAlarm = true;
+ mode.getRule().setConditionId(ZenModeConfig.toScheduleConditionId(scheduleInfo));
+
+ // now can just call updateState
+ mPrefController.updateState(preference, mode);
+ verify(preference).setChecked(true);
+ }
+
+ @Test
+ public void testOnPreferenceChange() {
+ TwoStatePreference preference = mock(TwoStatePreference.class);
+
+ // previously: exit at alarm
+ ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo();
+ scheduleInfo.days = new int[] { Calendar.MONDAY };
+ scheduleInfo.startHour = 1;
+ scheduleInfo.endHour = 2;
+ scheduleInfo.exitAtAlarm = true;
+
+ ZenMode mode = new ZenMode("id",
+ new AutomaticZenRule.Builder("name",
+ ZenModeConfig.toScheduleConditionId(scheduleInfo)).build(),
+ true); // is active
+ mPrefController.updateZenMode(preference, mode);
+
+ // turn off exit at alarm
+ mPrefController.onPreferenceChange(preference, false);
+ ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
+ verify(mBackend).updateMode(captor.capture());
+ ZenModeConfig.ScheduleInfo newSchedule = ZenModeConfig.tryParseScheduleConditionId(
+ captor.getValue().getRule().getConditionId());
+ assertThat(newSchedule.exitAtAlarm).isFalse();
+
+ // other properties remain the same
+ assertThat(newSchedule.startHour).isEqualTo(1);
+ assertThat(newSchedule.endHour).isEqualTo(2);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java
new file mode 100644
index 0000000..c0fbe15
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AutomaticZenRule;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.preference.PreferenceScreen;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settingslib.widget.LayoutPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class ZenModeIconPickerListPreferenceControllerTest {
+
+ private static final ZenMode ZEN_MODE = new ZenMode(
+ "mode_id",
+ new AutomaticZenRule.Builder("mode name", Uri.parse("mode")).build(),
+ /* isActive= */ false);
+
+ private ZenModesBackend mBackend;
+ private ZenModeIconPickerListPreferenceController mController;
+ private PreferenceScreen mPreferenceScreen;
+ private RecyclerView mRecyclerView;
+
+ @Before
+ public void setUp() {
+ Context context = RuntimeEnvironment.getApplication();
+ mBackend = mock(ZenModesBackend.class);
+
+ DashboardFragment fragment = mock(DashboardFragment.class);
+ mController = new ZenModeIconPickerListPreferenceController(
+ RuntimeEnvironment.getApplication(), "icon_list", fragment, mBackend);
+
+ mRecyclerView = new RecyclerView(context);
+ mRecyclerView.setId(R.id.icon_list);
+ LayoutPreference layoutPreference = new LayoutPreference(context, mRecyclerView);
+ mPreferenceScreen = mock(PreferenceScreen.class);
+ when(mPreferenceScreen.findPreference(eq("icon_list"))).thenReturn(layoutPreference);
+ }
+
+ @Test
+ public void displayPreference_loadsIcons() {
+ mController.displayPreference(mPreferenceScreen);
+
+ assertThat(mRecyclerView.getAdapter()).isNotNull();
+ assertThat(mRecyclerView.getAdapter().getItemCount()).isEqualTo(20);
+ }
+
+ @Test
+ public void selectIcon_updatesMode() {
+ mController.setZenMode(ZEN_MODE);
+
+ mController.onIconSelected(R.drawable.ic_android);
+
+ ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
+ verify(mBackend).updateMode(captor.capture());
+ assertThat(captor.getValue().getRule().getIconResId()).isEqualTo(R.drawable.ic_android);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java
new file mode 100644
index 0000000..7cf327c
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AutomaticZenRule;
+import android.app.Flags;
+import android.content.Context;
+import android.net.Uri;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.service.notification.ZenModeConfig;
+import android.view.ViewGroup;
+import android.widget.ToggleButton;
+
+import androidx.fragment.app.Fragment;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+
+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;
+
+import java.util.Calendar;
+
+@RunWith(RobolectricTestRunner.class)
+public class ZenModeSetSchedulePreferenceControllerTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
+ @Mock
+ private ZenModesBackend mBackend;
+ private Context mContext;
+
+ @Mock
+ private Fragment mParent;
+ @Mock
+ private Calendar mCalendar;
+ @Mock
+ private ViewGroup mDaysContainer;
+ @Mock
+ private ToggleButton mDay0, mDay1, mDay2, mDay3, mDay4, mDay5, mDay6;
+
+ private ZenModeSetSchedulePreferenceController mPrefController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = ApplicationProvider.getApplicationContext();
+ mPrefController = new ZenModeSetSchedulePreferenceController(mContext, mParent, "schedule",
+ mBackend);
+ setupMockDayContainer();
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void updateScheduleRule_updatesConditionAndTriggerDescription() {
+ ZenMode mode = new ZenMode("id",
+ new AutomaticZenRule.Builder("name", Uri.parse("condition")).build(),
+ true); // is active
+
+ ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo();
+ scheduleInfo.days = new int[] { Calendar.MONDAY };
+ scheduleInfo.startHour = 1;
+ scheduleInfo.endHour = 2;
+ ZenMode out = mPrefController.updateScheduleMode(scheduleInfo).apply(mode);
+
+ assertThat(out.getRule().getConditionId())
+ .isEqualTo(ZenModeConfig.toScheduleConditionId(scheduleInfo));
+ assertThat(out.getRule().getTriggerDescription()).isNotEmpty();
+ }
+
+ @Test
+ public void testUpdateScheduleDays() {
+ // Confirm that adding/subtracting/etc days works as expected
+ // starting from null: no days set
+ ZenModeConfig.ScheduleInfo schedule = new ZenModeConfig.ScheduleInfo();
+
+ // Unset a day that's already unset: nothing should change
+ assertThat(ZenModeSetSchedulePreferenceController.updateScheduleDays(schedule,
+ Calendar.TUESDAY, false)).isFalse();
+ // not explicitly checking whether schedule.days is still null here, as we don't necessarily
+ // want to require nullness as distinct from an empty list of days.
+
+ // set a few new days
+ assertThat(ZenModeSetSchedulePreferenceController.updateScheduleDays(schedule,
+ Calendar.MONDAY, true)).isTrue();
+ assertThat(ZenModeSetSchedulePreferenceController.updateScheduleDays(schedule,
+ Calendar.FRIDAY, true)).isTrue();
+ assertThat(schedule.days).hasLength(2);
+ assertThat(schedule.days).asList().containsExactly(Calendar.MONDAY, Calendar.FRIDAY);
+
+ // remove an existing day to make sure that works
+ assertThat(ZenModeSetSchedulePreferenceController.updateScheduleDays(schedule,
+ Calendar.MONDAY, false)).isTrue();
+ assertThat(schedule.days).hasLength(1);
+ assertThat(schedule.days).asList().containsExactly(Calendar.FRIDAY);
+ }
+
+ @Test
+ public void testSetupDayToggles_daysOfWeekOrder() {
+ // Confirm that days are correctly associated with the actual day of the week independent
+ // of when the first day of the week is for the given calendar.
+ ZenModeConfig.ScheduleInfo schedule = new ZenModeConfig.ScheduleInfo();
+ schedule.days = new int[] { Calendar.SUNDAY, Calendar.TUESDAY, Calendar.FRIDAY };
+ schedule.startHour = 1;
+ schedule.endHour = 5;
+
+ // Start mCalendar on Wednesday, arbitrarily
+ when(mCalendar.getFirstDayOfWeek()).thenReturn(Calendar.WEDNESDAY);
+
+ // Setup the day toggles
+ mPrefController.setupDayToggles(mDaysContainer, schedule, mCalendar);
+
+ // we should see toggle 0 associated with the first day of the week, etc.
+ // in this week order, schedule turns on friday (2), sunday (4), tuesday (6) so those
+ // should be checked while everything else should not be checked.
+ verify(mDay0).setChecked(false); // weds
+ verify(mDay1).setChecked(false); // thurs
+ verify(mDay2).setChecked(true); // fri
+ verify(mDay3).setChecked(false); // sat
+ verify(mDay4).setChecked(true); // sun
+ verify(mDay5).setChecked(false); // mon
+ verify(mDay6).setChecked(true); // tues
+ }
+
+ private void setupMockDayContainer() {
+ // associate each index (regardless of associated day of the week) with the appropriate
+ // res id in the days container
+ when(mDaysContainer.findViewById(R.id.day0)).thenReturn(mDay0);
+ when(mDaysContainer.findViewById(R.id.day1)).thenReturn(mDay1);
+ when(mDaysContainer.findViewById(R.id.day2)).thenReturn(mDay2);
+ when(mDaysContainer.findViewById(R.id.day3)).thenReturn(mDay3);
+ when(mDaysContainer.findViewById(R.id.day4)).thenReturn(mDay4);
+ when(mDaysContainer.findViewById(R.id.day5)).thenReturn(mDay5);
+ when(mDaysContainer.findViewById(R.id.day6)).thenReturn(mDay6);
+ }
+}
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 7dcec1c..91de4ea 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java
@@ -17,6 +17,7 @@
package com.android.settings.notification.modes;
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
+import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
@@ -53,6 +54,8 @@
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
+import java.util.Calendar;
+
@RunWith(RobolectricTestRunner.class)
public class ZenModeSetTriggerLinkPreferenceControllerTest {
@Rule
@@ -167,4 +170,29 @@
captor.getValue().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)).isEqualTo(
ZenModeSetCalendarFragment.class.getName());
}
+
+ @Test
+ public void testRuleLink_schedule() {
+ ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo();
+ scheduleInfo.days = new int[] { Calendar.MONDAY, Calendar.TUESDAY, Calendar.THURSDAY };
+ scheduleInfo.startHour = 1;
+ scheduleInfo.endHour = 15;
+ ZenMode mode = new ZenMode("id", new AutomaticZenRule.Builder("name",
+ ZenModeConfig.toScheduleConditionId(scheduleInfo))
+ .setType(TYPE_SCHEDULE_TIME)
+ .setTriggerDescription("some schedule")
+ .build(),
+ true); // is active
+ mPrefController.updateZenMode(mPrefCategory, mode);
+
+ verify(mPreference).setTitle(R.string.zen_mode_set_schedule_link);
+ verify(mPreference).setSummary(mode.getRule().getTriggerDescription());
+
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mPreference).setIntent(captor.capture());
+ // Destination as written into the intent by SubSettingLauncher
+ assertThat(
+ captor.getValue().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)).isEqualTo(
+ ZenModeSetScheduleFragment.class.getName());
+ }
}
diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockTypeDialogFragmentTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockTypeDialogFragmentTest.java
index 68f8ed7..24418bf 100644
--- a/tests/robotests/src/com/android/settings/password/ChooseLockTypeDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/password/ChooseLockTypeDialogFragmentTest.java
@@ -37,7 +37,6 @@
import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@@ -47,7 +46,6 @@
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowAlertDialogCompat.class, ShadowLockPatternUtils.class})
-@Ignore("b/342667939")
public class ChooseLockTypeDialogFragmentTest {
private Context mContext;
diff --git a/tests/spa_unit/src/com/android/settings/network/ConnectivityRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/ConnectivityRepositoryTest.kt
new file mode 100644
index 0000000..170b84d
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/ConnectivityRepositoryTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.network
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.android.settingslib.spa.testutils.toListWithTimeout
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class ConnectivityRepositoryTest {
+
+ private var networkCallback: NetworkCallback? = null
+
+ private val mockConnectivityManager = mock<ConnectivityManager> {
+ on { registerDefaultNetworkCallback(any()) } doAnswer {
+ networkCallback = it.arguments[0] as NetworkCallback
+ }
+ }
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ on { getSystemService(ConnectivityManager::class.java) } doReturn mockConnectivityManager
+ }
+
+ private val connectivityRepository = ConnectivityRepository(context)
+
+ @Test
+ fun networkCapabilitiesFlow_activeNetworkIsNull_noCrash() = runBlocking {
+ mockConnectivityManager.stub {
+ on { activeNetwork } doReturn null
+ on { getNetworkCapabilities(null) } doReturn null
+ }
+
+ val networkCapabilities =
+ connectivityRepository.networkCapabilitiesFlow().firstWithTimeoutOrNull()!!
+
+ assertThat(networkCapabilities.transportTypes).isEmpty()
+ }
+
+ @Test
+ fun networkCapabilitiesFlow_getInitialValue() = runBlocking {
+ val expectedNetworkCapabilities = NetworkCapabilities.Builder().apply {
+ addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ }.build()
+ mockConnectivityManager.stub {
+ on { getNetworkCapabilities(null) } doReturn expectedNetworkCapabilities
+ }
+
+ val actualNetworkCapabilities =
+ connectivityRepository.networkCapabilitiesFlow().firstWithTimeoutOrNull()!!
+
+ assertThat(actualNetworkCapabilities).isSameInstanceAs(expectedNetworkCapabilities)
+ }
+
+ @Test
+ fun networkCapabilitiesFlow_getUpdatedValue() = runBlocking {
+ val expectedNetworkCapabilities = NetworkCapabilities.Builder().apply {
+ addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ }.build()
+
+ val deferredList = async {
+ connectivityRepository.networkCapabilitiesFlow().toListWithTimeout()
+ }
+ delay(100)
+ networkCallback?.onCapabilitiesChanged(mock<Network>(), expectedNetworkCapabilities)
+
+ assertThat(deferredList.await().last()).isSameInstanceAs(expectedNetworkCapabilities)
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/InternetPreferenceRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/InternetPreferenceRepositoryTest.kt
new file mode 100644
index 0000000..4cd65e7
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/InternetPreferenceRepositoryTest.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.network
+
+import android.content.Context
+import android.net.NetworkCapabilities
+import android.net.wifi.WifiManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.android.settings.wifi.WifiSummaryRepository
+import com.android.settings.wifi.repository.WifiRepository
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class InternetPreferenceRepositoryTest {
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val mockConnectivityRepository = mock<ConnectivityRepository>()
+ private val mockWifiSummaryRepository = mock<WifiSummaryRepository>()
+ private val mockWifiRepository = mock<WifiRepository>()
+ private val airplaneModeOnFlow = MutableStateFlow(false)
+
+ private val repository = InternetPreferenceRepository(
+ context = context,
+ connectivityRepository = mockConnectivityRepository,
+ wifiSummaryRepository = mockWifiSummaryRepository,
+ wifiRepository = mockWifiRepository,
+ airplaneModeOnFlow = airplaneModeOnFlow,
+ )
+
+ @Test
+ fun summaryFlow_wifi() = runBlocking {
+ val wifiNetworkCapabilities = NetworkCapabilities.Builder().apply {
+ addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ }.build()
+ mockConnectivityRepository.stub {
+ on { networkCapabilitiesFlow() } doReturn flowOf(wifiNetworkCapabilities)
+ }
+ mockWifiSummaryRepository.stub {
+ on { summaryFlow() } doReturn flowOf(SUMMARY)
+ }
+
+ val summary = repository.summaryFlow().firstWithTimeoutOrNull()
+
+ assertThat(summary).isEqualTo(SUMMARY)
+ }
+
+ @Test
+ fun summaryFlow_airplaneModeOnAndWifiOn() = runBlocking {
+ mockConnectivityRepository.stub {
+ on { networkCapabilitiesFlow() } doReturn flowOf(NetworkCapabilities())
+ }
+ airplaneModeOnFlow.value = true
+ mockWifiRepository.stub {
+ on { wifiStateFlow() } doReturn flowOf(WifiManager.WIFI_STATE_ENABLED)
+ }
+
+ val summary = repository.summaryFlow().firstWithTimeoutOrNull()
+
+ assertThat(summary).isEqualTo(context.getString(R.string.networks_available))
+ }
+
+ @Test
+ fun summaryFlow_airplaneModeOnAndWifiOff() = runBlocking {
+ mockConnectivityRepository.stub {
+ on { networkCapabilitiesFlow() } doReturn flowOf(NetworkCapabilities())
+ }
+ airplaneModeOnFlow.value = true
+ mockWifiRepository.stub {
+ on { wifiStateFlow() } doReturn flowOf(WifiManager.WIFI_STATE_DISABLED)
+ }
+
+ val summary = repository.summaryFlow().firstWithTimeoutOrNull()
+
+ assertThat(summary).isEqualTo(context.getString(R.string.condition_airplane_title))
+ }
+
+ @Test
+ fun summaryFlow_airplaneModeOff() = runBlocking {
+ mockConnectivityRepository.stub {
+ on { networkCapabilitiesFlow() } doReturn flowOf(NetworkCapabilities())
+ }
+ airplaneModeOnFlow.value = false
+ mockWifiRepository.stub {
+ on { wifiStateFlow() } doReturn flowOf(WifiManager.WIFI_STATE_DISABLED)
+ }
+
+ val summary = repository.summaryFlow().firstWithTimeoutOrNull()
+
+ assertThat(summary).isEqualTo(context.getString(R.string.networks_available))
+ }
+
+ private companion object {
+ const val SUMMARY = "Summary"
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/wifi/repository/WifiRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/wifi/repository/WifiRepositoryTest.kt
new file mode 100644
index 0000000..dae3617
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/wifi/repository/WifiRepositoryTest.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.wifi.repository
+
+import android.content.Context
+import android.content.Intent
+import android.net.wifi.WifiManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class WifiRepositoryTest {
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val mockWifiStateChangedActionFlow = flowOf(Intent().apply {
+ putExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_ENABLED)
+ })
+
+ private val repository = WifiRepository(context, mockWifiStateChangedActionFlow)
+
+ @Test
+ fun wifiStateFlow() = runBlocking {
+ val wifiState = repository.wifiStateFlow().firstWithTimeoutOrNull()
+
+ assertThat(wifiState).isEqualTo(WifiManager.WIFI_STATE_ENABLED)
+ }
+}
diff --git a/tests/unit/src/com/android/settings/network/ResetNetworkOperationBuilderTest.java b/tests/unit/src/com/android/settings/network/ResetNetworkOperationBuilderTest.java
index 6213f8e..5f54406 100644
--- a/tests/unit/src/com/android/settings/network/ResetNetworkOperationBuilderTest.java
+++ b/tests/unit/src/com/android/settings/network/ResetNetworkOperationBuilderTest.java
@@ -188,7 +188,7 @@
doThrow(new IllegalArgumentException()).when(mContentProvider).call(
anyString(), anyString(), anyString(), any());
- mBuilder.restartPhoneProcess();
+ mBuilder.restartPhoneProcess().build().run();
}
@Test
@@ -196,12 +196,12 @@
doThrow(new IllegalArgumentException()).when(mContentProvider).call(
anyString(), anyString(), anyString(), any());
- mBuilder.restartRild();
+ mBuilder.restartRild().build().run();
}
@Test
public void restartPhoneProcess_withTelephonyContentProvider_shouldCallRestartPhoneProcess() {
- mBuilder.restartPhoneProcess();
+ mBuilder.restartPhoneProcess().build().run();
verify(mContentProvider).call(
eq(mBuilder.getResetTelephonyContentProviderAuthority()),
@@ -212,7 +212,7 @@
@Test
public void restartRild_withTelephonyContentProvider_shouldCallRestartRild() {
- mBuilder.restartRild();
+ mBuilder.restartRild().build().run();
verify(mContentProvider).call(
eq(mBuilder.getResetTelephonyContentProviderAuthority()),