Snap for 13204655 from 67b21fb238 to 25Q2-release

Change-Id: I14e3b84b0d79c7a1aa38226e732171a517a0a95b
This commit is contained in:
Android Build Coastguard Worker
2025-03-12 16:19:02 -07:00
73 changed files with 2054 additions and 266 deletions

View File

@@ -139,6 +139,7 @@ android_library {
"aconfig_settings_flags", "aconfig_settings_flags",
"aconfig_settingslib_flags", "aconfig_settingslib_flags",
"android.app.flags-aconfig", "android.app.flags-aconfig",
"android.app.supervision.flags-aconfig",
"android.provider.flags-aconfig", "android.provider.flags-aconfig",
"android.security.flags-aconfig", "android.security.flags-aconfig",
"android.view.contentprotection.flags-aconfig", "android.view.contentprotection.flags-aconfig",

View File

@@ -2817,6 +2817,15 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".supervision.ConfirmSupervisionCredentialsActivity"
android:exported="true"
android:featureFlag="android.app.supervision.flags.supervision_manager_apis">
<intent-filter>
<action android:name="android.app.supervision.action.CONFIRM_SUPERVISION_CREDENTIALS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".SetupRedactionInterstitial" <activity android:name=".SetupRedactionInterstitial"
android:enabled="false" android:enabled="false"
android:exported="true" android:exported="true"

View File

@@ -0,0 +1,12 @@
package: "com.android.settings.flags"
container: "system_ext"
flag {
name: "settings_search_result_deep_link_in_same_task"
namespace: "desktop_personalization"
description: "Show the search result deep link in the same task (same window)"
bug: "381127948"
metadata {
purpose: PURPOSE_BUGFIX
}
}

View File

@@ -122,20 +122,38 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:visibility="gone"/> android:visibility="gone"/>
<SeekBar <LinearLayout
android:id="@+id/input_setting_keys_value_custom_slider" android:id="@+id/input_setting_keys_custom_seekbar_layout"
android:paddingStart="8dp" android:orientation="horizontal"
android:paddingEnd="36dp"
android:min="1"
android:max="50"
android:progressBackgroundTint="@color/input_dialog_slider_progress_background"
android:progressTint="@color/input_dialog_slider_progress"
android:thumbTint="@color/input_dialog_slider_progress"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp" android:gravity="center_vertical"
android:visibility="gone"/> android:paddingEnd="36dp">
<ImageView
android:layout_marginTop="8dp"
android:layout_width="16dp"
android:layout_height="16dp"
android:tint="@androidprv:color/materialColorPrimary"
android:importantForAccessibility="no"
android:src="@drawable/ic_remove_24dp" />
<SeekBar
android:id="@+id/input_setting_keys_value_custom_slider"
style="@android:style/Widget.Material.SeekBar"
android:min="1"
android:max="50"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginTop="8dp" />
<ImageView
android:layout_marginTop="8dp"
android:layout_width="16dp"
android:layout_height="16dp"
android:tint="@androidprv:color/materialColorPrimary"
android:importantForAccessibility="no"
android:src="@drawable/ic_add_24dp" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</RadioGroup> </RadioGroup>

View File

@@ -94,8 +94,5 @@
<!-- Volume seekbar colors --> <!-- Volume seekbar colors -->
<color name="seekbar_thumb_tint_color">@android:color/system_accent1_100</color> <color name="seekbar_thumb_tint_color">@android:color/system_accent1_100</color>
<color name="seekbar_progress_tint_color">@android:color/system_accent1_100</color> <color name="seekbar_progress_tint_color">@android:color/system_accent1_100</color>
<!-- Keyboard a11y input dialog -->
<color name="input_dialog_slider_progress">@android:color/system_on_primary_container_dark</color>
</resources> </resources>

View File

@@ -1059,6 +1059,20 @@
<item>either_charging_or_docked</item> <item>either_charging_or_docked</item>
</string-array> </string-array>
<string-array name="when_to_start_hubmode_entries" translatable="false">
<item>@string/when_to_show_hubmode_never</item>
<item>@string/when_to_show_hubmode_charging</item>
<item>@string/when_to_show_hubmode_charging_and_upright</item>
<item>@string/when_to_show_hubmode_docked</item>
</string-array>
<string-array name="when_to_start_hubmode_values" translatable="false">
<item>never</item>
<item>while_charging</item>
<item>while_charging_and_upright</item>
<item>while_docked</item>
</string-array>
<string-array name="zen_mode_contacts_calls_entries" translatable="false"> <string-array name="zen_mode_contacts_calls_entries" translatable="false">
<item>@string/zen_mode_from_anyone</item> <item>@string/zen_mode_from_anyone</item>
<item>@string/zen_mode_from_contacts</item> <item>@string/zen_mode_from_contacts</item>

View File

@@ -64,8 +64,8 @@
<color name="homepage_sound_background">@color/homepage_pink_bg</color> <color name="homepage_sound_background">@color/homepage_pink_bg</color>
<color name="homepage_modes_foreground">@color/homepage_pink_fg</color> <color name="homepage_modes_foreground">@color/homepage_pink_fg</color>
<color name="homepage_modes_background">@color/homepage_pink_bg</color> <color name="homepage_modes_background">@color/homepage_pink_bg</color>
<color name="homepage_hub_mode_foreground">@color/homepage_pink_fg</color> <color name="homepage_hub_mode_foreground">@color/homepage_orange_fg</color>
<color name="homepage_hub_mode_background">@color/homepage_pink_bg</color> <color name="homepage_hub_mode_background">@color/homepage_orange_bg</color>
<color name="homepage_display_foreground">@color/homepage_orange_fg</color> <color name="homepage_display_foreground">@color/homepage_orange_fg</color>
<color name="homepage_display_background">@color/homepage_orange_bg</color> <color name="homepage_display_background">@color/homepage_orange_bg</color>
<color name="homepage_wallpaper_foreground">@color/homepage_orange_fg</color> <color name="homepage_wallpaper_foreground">@color/homepage_orange_fg</color>
@@ -275,8 +275,4 @@
<color name="seekbar_thumb_tint_color">@android:color/system_accent1_800</color> <color name="seekbar_thumb_tint_color">@android:color/system_accent1_800</color>
<color name="seekbar_progress_tint_color">@android:color/system_accent1_800</color> <color name="seekbar_progress_tint_color">@android:color/system_accent1_800</color>
<color name="seekbar_progress_background_tint_color">@android:color/system_neutral2_50</color> <color name="seekbar_progress_background_tint_color">@android:color/system_neutral2_50</color>
<!-- Keyboard a11y input dialog -->
<color name="input_dialog_slider_progress">@android:color/system_on_primary_dark</color>
<color name="input_dialog_slider_progress_background">@android:color/system_outline_light</color>
</resources> </resources>

View File

@@ -810,13 +810,23 @@
<!-- Allowed packages to show the confirmation dialog for a system locale suggestion --> <!-- Allowed packages to show the confirmation dialog for a system locale suggestion -->
<string-array name="allowed_packages_for_locale_confirmation_diallog" translatable="false"/> <string-array name="allowed_packages_for_locale_confirmation_diallog" translatable="false"/>
<!-- Array of text reading preview layouts. Must contain at least 1 layout --> <!-- Array of text reading preview layouts. Must contain at least 1 layout.
Add content descriptions in the config_text_reading_preview_content_descriptions together
if adding more sample layouts here -->
<array name="config_text_reading_preview_samples"> <array name="config_text_reading_preview_samples">
<item>@layout/accessibility_text_reading_preview_app_grid</item> <item>@layout/accessibility_text_reading_preview_app_grid</item>
<item>@layout/screen_zoom_preview_1</item> <item>@layout/screen_zoom_preview_1</item>
<item>@layout/accessibility_text_reading_preview_mail_content</item> <item>@layout/accessibility_text_reading_preview_mail_content</item>
</array> </array>
<!-- Array of text reading preview layouts' content descriptions.
The order should be the same as the layouts in config_text_reading_preview_samples -->
<array name="config_text_reading_preview_content_descriptions">
<item>@string/preview_pager_home_content_description</item>
<item>@string/preview_pager_message_content_description</item>
<item>@string/preview_pager_email_content_description</item>
</array>
<!-- Package responsible for updating Mainline Modules --> <!-- Package responsible for updating Mainline Modules -->
<string name="config_mainline_module_update_package" translatable="false">com.android.vending</string> <string name="config_mainline_module_update_package" translatable="false">com.android.vending</string>

View File

@@ -78,6 +78,12 @@
<!-- Content description for preview pager. [CHAR LIMIT=NONE] --> <!-- Content description for preview pager. [CHAR LIMIT=NONE] -->
<string name="preview_pager_content_description">Preview</string> <string name="preview_pager_content_description">Preview</string>
<!-- Content description for home screen preview. [CHAR LIMIT=NONE] -->
<string name="preview_pager_home_content_description">Home screen preview</string>
<!-- Content description for message preview. [CHAR LIMIT=NONE] -->
<string name="preview_pager_message_content_description">Message preview</string>
<!-- Content description for email preview. [CHAR LIMIT=NONE] -->
<string name="preview_pager_email_content_description">Email preview</string>
<!-- Content description for qrcode image. [CHAR LIMIT=none]--> <!-- Content description for qrcode image. [CHAR LIMIT=none]-->
<string name="qr_code_content_description">QR code</string> <string name="qr_code_content_description">QR code</string>
<!-- Previous button for preview pager. [CHAR LIMIT=NONE] --> <!-- Previous button for preview pager. [CHAR LIMIT=NONE] -->
@@ -497,7 +503,7 @@
<string name="link_locale_picker_footer_learn_more" translatable="false">https://support.google.com/android?p=per_language_app_settings</string> <string name="link_locale_picker_footer_learn_more" translatable="false">https://support.google.com/android?p=per_language_app_settings</string>
<!-- Title for asking to change system region or not. [CHAR LIMIT=50]--> <!-- Title for asking to change system region or not. [CHAR LIMIT=50]-->
<string name="title_change_system_region">Change region to %s ?</string> <string name="title_change_system_region">Change region to %s?</string>
<!-- The content of a confirmation dialog indicating the impact when the user change the system region. [CHAR LIMIT=NONE]--> <!-- The content of a confirmation dialog indicating the impact when the user change the system region. [CHAR LIMIT=NONE]-->
<string name="desc_notice_device_region_change">Your device will keep %s as a system language</string> <string name="desc_notice_device_region_change">Your device will keep %s as a system language</string>
@@ -506,7 +512,7 @@
<string name="desc_notice_device_region_change_for_preferred_language">%1$s will replace %2$s in your preferred languages</string> <string name="desc_notice_device_region_change_for_preferred_language">%1$s will replace %2$s in your preferred languages</string>
<!-- Title for asking to change system locale or not. [CHAR LIMIT=50]--> <!-- Title for asking to change system locale or not. [CHAR LIMIT=50]-->
<string name="title_change_system_locale">Change system language to %s ?</string> <string name="title_change_system_locale">Change system language to %s?</string>
<!-- The title of a confirmation dialog to indicate adding a system locale. [CHAR LIMIT=50]--> <!-- The title of a confirmation dialog to indicate adding a system locale. [CHAR LIMIT=50]-->
<string name="title_system_locale_addition">Add %s to preferred languages?</string> <string name="title_system_locale_addition">Add %s to preferred languages?</string>
@@ -547,7 +553,7 @@
<!-- Category for more language settings. [CHAR LIMIT=NONE]--> <!-- Category for more language settings. [CHAR LIMIT=NONE]-->
<string name="more_language_settings_category">More language settings</string> <string name="more_language_settings_category">More language settings</string>
<!-- Title for asking to change system locale region or not. [CHAR LIMIT=50]--> <!-- Title for asking to change system locale region or not. [CHAR LIMIT=50]-->
<string name="title_change_system_locale_region">Change region to <xliff:g id="system_language_region" example="Canada">%1$s</xliff:g> ?</string> <string name="title_change_system_locale_region">Change region to <xliff:g id="system_language_region" example="Canada">%1$s</xliff:g>?</string>
<!-- Message for asking to change system locale region or not. [CHAR LIMIT=50]--> <!-- Message for asking to change system locale region or not. [CHAR LIMIT=50]-->
<string name="body_change_system_locale_region">Your device will keep <xliff:g id="system_language" example="English">%1$s</xliff:g> as a system language</string> <string name="body_change_system_locale_region">Your device will keep <xliff:g id="system_language" example="English">%1$s</xliff:g> as a system language</string>
<!-- Description for the numbering system language. [CHAR LIMIT=NONE]--> <!-- Description for the numbering system language. [CHAR LIMIT=NONE]-->
@@ -833,6 +839,8 @@
<string name="security_settings_face_preference_summary">Face added</string> <string name="security_settings_face_preference_summary">Face added</string>
<!-- Message shown in summary field when Face Unlock is not set up. [CHAR LIMIT=54] --> <!-- Message shown in summary field when Face Unlock is not set up. [CHAR LIMIT=54] -->
<string name="security_settings_face_preference_summary_none">Setup needed</string> <string name="security_settings_face_preference_summary_none">Setup needed</string>
<!-- Message shown in summary field when Face Unlock is not set up. [CHAR LIMIT=54] -->
<string name="security_settings_face_preference_summary_none_new">Add face</string>
<!-- Title shown for menu item that launches face settings or enrollment. [CHAR LIMIT=32] --> <!-- Title shown for menu item that launches face settings or enrollment. [CHAR LIMIT=32] -->
<string name="security_settings_face_preference_title_new">Face</string> <string name="security_settings_face_preference_title_new">Face</string>
<!-- Title shown for menu item that launches face settings or enrollment, for work profile. [CHAR LIMIT=50] --> <!-- Title shown for menu item that launches face settings or enrollment, for work profile. [CHAR LIMIT=50] -->
@@ -1006,6 +1014,8 @@
}</string> }</string>
<!-- message shown in summary field when no fingerprints are registered --> <!-- message shown in summary field when no fingerprints are registered -->
<string name="security_settings_fingerprint_preference_summary_none">Setup needed</string> <string name="security_settings_fingerprint_preference_summary_none">Setup needed</string>
<!-- message shown in summary field when no fingerprints are registered -->
<string name="security_settings_fingerprint_preference_summary_none_new">Add fingerprint</string>
<!-- Introduction title shown in fingerprint enrollment to introduce the fingerprint feature [CHAR LIMIT=29] --> <!-- Introduction title shown in fingerprint enrollment to introduce the fingerprint feature [CHAR LIMIT=29] -->
<string name="security_settings_fingerprint_enroll_introduction_title">Set up your fingerprint</string> <string name="security_settings_fingerprint_enroll_introduction_title">Set up your fingerprint</string>
<!-- Introduction title shown in fingerprint enrollment when asking for parental consent for fingerprint unlock [CHAR LIMIT=29] --> <!-- Introduction title shown in fingerprint enrollment when asking for parental consent for fingerprint unlock [CHAR LIMIT=29] -->
@@ -1771,6 +1781,8 @@
<!-- Summary for "Configure lockscreen" when lock screen is off [CHAR LIMIT=45] --> <!-- Summary for "Configure lockscreen" when lock screen is off [CHAR LIMIT=45] -->
<string name="unlock_set_unlock_mode_off">None</string> <string name="unlock_set_unlock_mode_off">None</string>
<!-- Summary for "Configure lockscreen" when lock screen is off [CHAR LIMIT=45] -->
<string name="unlock_set_unlock_mode_off_new">Add PIN, pattern, password, or swipe</string>
<!-- Summary for "Configure lockscreen" when security is disabled [CHAR LIMIT=45] --> <!-- Summary for "Configure lockscreen" when security is disabled [CHAR LIMIT=45] -->
<string name="unlock_set_unlock_mode_none">Swipe</string> <string name="unlock_set_unlock_mode_none">Swipe</string>
<!-- Summary for "Configure lockscreen" when security pattern is enabled [CHAR LIMIT=45] --> <!-- Summary for "Configure lockscreen" when security pattern is enabled [CHAR LIMIT=45] -->
@@ -3655,6 +3667,21 @@
<string name="communal_settings_title">Communal</string> <string name="communal_settings_title">Communal</string>
<!-- Summary of the communal settings under Settings > Communal [CHAR LIMIT=50] --> <!-- Summary of the communal settings under Settings > Communal [CHAR LIMIT=50] -->
<string name="communal_settings_summary">Communal settings</string> <string name="communal_settings_summary">Communal settings</string>
<!-- Title of Hub mode category [CHAR LIMIT=30] -->
<string name="hub_mode_category_title">Hub mode</string>
<!-- Title of the "widgets on lockscreen" settings page. [CHAR LIMIT=NONE] -->
<string name="widgets_on_lockscreen_title">Widgets on lock screen</string>
<!-- Title of a setting to control when to automatically show widgets on the lockscren. [CHAR LIMIT=NONE] -->
<string name="when_to_auto_show_hubmode_title">When to automatically show</string>
<!-- Summary for when to automatically show hub mode (widgets on lockscreen): never [CHAR LIMIT=100] -->
<string name="when_to_show_hubmode_never">Never</string>
<!-- Summary for when to automatically show hub mode (widgets on lockscreen): charging [CHAR LIMIT=100] -->
<string name="when_to_show_hubmode_charging">While charging</string>
<!-- Summary for when to automatically show hub mode (widgets on lockscreen): charging and upright [CHAR LIMIT=100] -->
<string name="when_to_show_hubmode_charging_and_upright">While charging and upright</string>
<!-- Summary for when to automatically show hub mode (widgets on lockscreen): docked [CHAR LIMIT=100] -->
<string name="when_to_show_hubmode_docked">While docked</string>
<!-- _satellite_setting_preference_layout --> <!-- _satellite_setting_preference_layout -->
<!-- _satellite_setting_preference_layout screen title--> <!-- _satellite_setting_preference_layout screen title-->
@@ -3897,6 +3924,18 @@
<!-- Label for bluetooth tether checkbox [CHAR LIMIT=25]--> <!-- Label for bluetooth tether checkbox [CHAR LIMIT=25]-->
<string name="bluetooth_tether_checkbox_text">Bluetooth tethering</string> <string name="bluetooth_tether_checkbox_text">Bluetooth tethering</string>
<!-- Ethernet settings-->
<!-- Label for ethernet IP address [CHAR LIMIT=NONE]-->
<string name="ethernet_ip_address">IP address</string>
<!-- Label for ethernet MAC address [CHAR LIMIT=NONE]-->
<string name="ethernet_mac_address_title">MAC address</string>
<!-- Label for ethernet transfer speed [CHAR LIMIT=NONE]-->
<string name="tx_ethernet_speed">Transit link speed</string>
<!-- Label for ethernet receive speed [CHAR LIMIT=NONE]-->
<string name="rx_ethernet_speed">Receive link speed</string>
<!-- Label for ethernet network usage [CHAR LIMIT=NONE]-->
<string name="ethernet_network_usage">Network usage</string>
<!-- Ethernet Tethering settings--> <!-- Ethernet Tethering settings-->
<!-- Label for ethernet tether checkbox [CHAR LIMIT=NONE]--> <!-- Label for ethernet tether checkbox [CHAR LIMIT=NONE]-->
<string name="ethernet_tether_checkbox_text">Ethernet tethering</string> <string name="ethernet_tether_checkbox_text">Ethernet tethering</string>
@@ -5691,6 +5730,9 @@
<string name="accessibility_autoclick_longer_desc">Longer</string> <string name="accessibility_autoclick_longer_desc">Longer</string>
<!-- Description for the seekbar that adjust auto click time. [CHAR_LIMIT=NONE] --> <!-- Description for the seekbar that adjust auto click time. [CHAR_LIMIT=NONE] -->
<string name="accessibility_autoclick_seekbar_desc">Auto click time</string> <string name="accessibility_autoclick_seekbar_desc">Auto click time</string>
<!-- Title for the toggle button that turns on/off the autoclick setting. [CHAR_LIMIT=NONE] -->
<!-- TODO(b/394683600): Update string to translatable once approved by UXW. -->
<string name="accessibility_autoclick_main_switch_title" translatable="false">Use autoclick</string>
<!-- Title for the toggle button that turns on/off the autoclick shortcut. [CHAR_LIMIT=NONE] --> <!-- Title for the toggle button that turns on/off the autoclick shortcut. [CHAR_LIMIT=NONE] -->
<!-- TODO(b/394683600): Update string to translatable once approved by UXW. --> <!-- TODO(b/394683600): Update string to translatable once approved by UXW. -->
<string name="accessibility_autoclick_shortcut_title" translatable="false">Autoclick shortcut</string> <string name="accessibility_autoclick_shortcut_title" translatable="false">Autoclick shortcut</string>
@@ -14309,5 +14351,7 @@ Data usage charges may apply.</string>
<!-- Title for web content filters browser category allow all sites option [CHAR LIMIT=60] --> <!-- Title for web content filters browser category allow all sites option [CHAR LIMIT=60] -->
<string name="supervision_web_content_filters_browser_allow_all_sites_title">Allow all sites</string> <string name="supervision_web_content_filters_browser_allow_all_sites_title">Allow all sites</string>
<!-- Generic content description that is attached to the preview illustration at the top of an Accessibility feature toggle page. [CHAR LIMIT=NONE] --> <!-- Generic content description that is attached to the preview illustration at the top of an Accessibility feature toggle page. [CHAR LIMIT=NONE] -->
<!-- Title for supervision PIN verification screen [CHAR LIMIT=60] -->
<string name="supervision_full_screen_pin_verification_title">Enter supervision PIN</string>
<string name="accessibility_illustration_content_description"><xliff:g id="feature" example="Select to Speak">%1$s</xliff:g> animation</string> <string name="accessibility_illustration_content_description"><xliff:g id="feature" example="Select to Speak">%1$s</xliff:g> animation</string>
</resources> </resources>

View File

@@ -30,6 +30,11 @@
settings:searchable="false" settings:searchable="false"
settings:lottie_rawRes="@drawable/accessibility_dwell"/> settings:lottie_rawRes="@drawable/accessibility_dwell"/>
<com.android.settingslib.widget.MainSwitchPreference
android:key="accessibility_autoclick_main_switch"
android:title="@string/accessibility_autoclick_main_switch_title"
settings:controller="com.android.settings.accessibility.ToggleAutoclickMainSwitchPreferenceController"/>
<com.android.settings.accessibility.ShortcutPreference <com.android.settings.accessibility.ShortcutPreference
android:key="autoclick_shortcut_preference" android:key="autoclick_shortcut_preference"
android:title="@string/accessibility_autoclick_shortcut_title" android:title="@string/accessibility_autoclick_shortcut_title"

View File

@@ -88,6 +88,12 @@
android:persistent="false" android:persistent="false"
android:title="@string/accessibility_text_reading_options_title" android:title="@string/accessibility_text_reading_options_title"
settings:controller="com.android.settings.accessibility.TextReadingFragmentForDisplaySettingsController"/> settings:controller="com.android.settings.accessibility.TextReadingFragmentForDisplaySettingsController"/>
<Preference
android:key="widgets_on_lockscreen"
android:title="@string/widgets_on_lockscreen_title"
android:fragment="com.android.settings.communal.WidgetsOnLockscreenFragment"
settings:controller="com.android.settings.display.WidgetsOnLockscreenPreferenceController"/>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory

View File

@@ -24,4 +24,36 @@
android:selectable="false" android:selectable="false"
android:order="-10000"/> android:order="-10000"/>
<ListPreference
android:key="metered"
android:icon="@drawable/ic_attach_money_black_24dp"
android:title="@string/wifi_metered_title"
android:entries="@array/wifi_metered_entries"
android:entryValues="@array/wifi_metered_values"/>
<!-- Network Details -->
<PreferenceCategory
android:key="ip_details_category">
<Preference
android:key="ethernet_ip_address"
android:title="@string/ethernet_ip_address"
android:selectable="false"
settings:enableCopying="true"/>
<Preference
android:key="ethernet_mac_address"
android:title="@string/ethernet_mac_address_title"
android:selectable="false"
settings:enableCopying="true"/>
<Preference
android:key="ethernet_tx_link_speed"
android:title="@string/tx_ethernet_speed"
android:selectable="false"
settings:enableCopying="true"/>
<Preference
android:key="ethernet_rx_link_speed"
android:title="@string/rx_ethernet_speed"
android:selectable="false"
settings:enableCopying="true"/>
</PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View File

@@ -27,7 +27,7 @@
android:order="10" android:order="10"
settings:controller="com.android.settings.inputmethod.MousePointerAccelerationPreferenceController" /> settings:controller="com.android.settings.inputmethod.MousePointerAccelerationPreferenceController" />
<com.android.settings.widget.SeekBarPreference <com.android.settings.widget.LabeledSeekBarPreference
android:key="pointer_speed" android:key="pointer_speed"
android:title="@string/pointer_speed" android:title="@string/pointer_speed"
android:order="20" android:order="20"
@@ -50,32 +50,24 @@
android:summary="@string/accessibility_pointer_and_touchpad_summary" android:summary="@string/accessibility_pointer_and_touchpad_summary"
settings:searchable="true"/> settings:searchable="true"/>
<PreferenceCategory <SwitchPreferenceCompat
android:key="pointer_category" android:key="mouse_reverse_vertical_scrolling"
android:persistent="false" android:order="52"
android:order="51" android:title="@string/mouse_reverse_vertical_scrolling"
android:title="@string/mouse_scrolling_category_title"> android:summary="@string/mouse_reverse_vertical_scrolling_summary"
settings:controller="com.android.settings.inputmethod.MouseReverseVerticalScrollingPreferenceController" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="mouse_reverse_vertical_scrolling" android:order="55"
android:order="52" android:key="mouse_scrolling_acceleration"
android:title="@string/mouse_reverse_vertical_scrolling" android:title="@string/mouse_scrolling_acceleration"
android:summary="@string/mouse_reverse_vertical_scrolling_summary" settings:controller="com.android.settings.inputmethod.MouseScrollingAccelerationPreferenceController" />
settings:controller="com.android.settings.inputmethod.MouseReverseVerticalScrollingPreferenceController" />
<SwitchPreferenceCompat <com.android.settings.widget.LabeledSeekBarPreference
android:order="55" android:key="mouse_scrolling_speed"
android:key="mouse_scrolling_acceleration" android:title="@string/mouse_scrolling_speed"
android:title="@string/mouse_scrolling_acceleration" android:order="60"
settings:controller="com.android.settings.inputmethod.MouseScrollingAccelerationPreferenceController" /> android:selectable="false"
settings:controller="com.android.settings.inputmethod.MouseScrollingSpeedPreferenceController"/>
<com.android.settings.widget.SeekBarPreference
android:key="mouse_scrolling_speed"
android:title="@string/mouse_scrolling_speed"
android:order="60"
android:selectable="false"
settings:controller="com.android.settings.inputmethod.MouseScrollingSpeedPreferenceController"/>
</PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View File

@@ -61,7 +61,7 @@
settings:controller="com.android.settings.inputmethod.TouchpadAccelerationPreferenceController" settings:controller="com.android.settings.inputmethod.TouchpadAccelerationPreferenceController"
android:order="38"/> android:order="38"/>
<com.android.settings.widget.SeekBarPreference <com.android.settings.widget.LabeledSeekBarPreference
android:key="touchpad_pointer_speed" android:key="touchpad_pointer_speed"
android:title="@string/trackpad_pointer_speed" android:title="@string/trackpad_pointer_speed"
android:order="40" android:order="40"

View File

@@ -0,0 +1,19 @@
<!--
~ Copyright (C) 2025 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"
android:title="@string/when_to_auto_show_hubmode_title" />

View File

@@ -0,0 +1,29 @@
<!--
~ Copyright (C) 2025 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="widgets_on_lockscreen"
android:title="@string/widgets_on_lockscreen_title">
<Preference
android:key="when_to_start"
android:title="@string/when_to_auto_show_hubmode_title"
android:fragment="com.android.settings.communal.WhenToStartHubPicker"
settings:controller="com.android.settings.communal.WhenToStartHubPreferenceController" />
</PreferenceScreen>

View File

@@ -104,11 +104,13 @@ class TextReadingPreviewController extends BasePreferenceController implements
final boolean isLayoutRtl = final boolean isLayoutRtl =
origConfig.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; origConfig.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
final int[] previewSamples = getPreviewSampleLayouts(mContext); final int[] previewSamples = getPreviewSampleLayouts(mContext);
final int[] previewContentDescriptions = getPreviewSampleContentDescriptions(mContext);
final PreviewPagerAdapter pagerAdapter = new PreviewPagerAdapter(mContext, isLayoutRtl, final PreviewPagerAdapter pagerAdapter = new PreviewPagerAdapter(mContext, isLayoutRtl,
previewSamples, createConfig(origConfig)); previewSamples, createConfig(origConfig));
mPreviewPreference.setPreviewAdapter(pagerAdapter); mPreviewPreference.setPreviewAdapter(pagerAdapter);
mPreviewPreference.setCurrentItem( mPreviewPreference.setCurrentItem(
isLayoutRtl ? previewSamples.length - 1 : FRAME_INITIAL_INDEX); isLayoutRtl ? previewSamples.length - 1 : FRAME_INITIAL_INDEX);
mPreviewPreference.setContentDescription(previewContentDescriptions);
final int initialPagerIndex = final int initialPagerIndex =
mLastFontProgress * mDisplaySizeData.getValues().size() + mLastDisplayProgress; mLastFontProgress * mDisplaySizeData.getValues().size() + mLastDisplayProgress;
@@ -188,6 +190,20 @@ class TextReadingPreviewController extends BasePreferenceController implements
return previewSamples; return previewSamples;
} }
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
static int[] getPreviewSampleContentDescriptions(Context context) {
TypedArray typedArray = context.getResources().obtainTypedArray(
R.array.config_text_reading_preview_content_descriptions);
int previewCount = typedArray.length();
int[] previewContentDescriptions = new int[previewCount];
for (int i = 0; i < previewCount; i++) {
previewContentDescriptions[i] =
typedArray.getResourceId(i, R.string.preview_pager_content_description);
}
typedArray.recycle();
return previewContentDescriptions;
}
private int getPagerIndex() { private int getPagerIndex() {
final int displayDataSize = mDisplaySizeData.getValues().size(); final int displayDataSize = mDisplaySizeData.getValues().size();
final int fontSizeProgress = mFontSizePreference.getProgress(); final int fontSizeProgress = mFontSizePreference.getProgress();

View File

@@ -43,28 +43,11 @@ public class TextReadingPreviewPreference extends Preference {
private int mCurrentItem; private int mCurrentItem;
private int mLastLayerIndex; private int mLastLayerIndex;
private PreviewPagerAdapter mPreviewAdapter; private PreviewPagerAdapter mPreviewAdapter;
private int[] mContentDescriptions;
private int mLayoutMinHorizontalPadding = 0; private int mLayoutMinHorizontalPadding = 0;
private int mBackgroundMinHorizontalPadding = 0; private int mBackgroundMinHorizontalPadding = 0;
private final ViewPager.OnPageChangeListener mPageChangeListener =
new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int i, float v, int i1) {
// Do nothing
}
@Override
public void onPageSelected(int i) {
mCurrentItem = i;
}
@Override
public void onPageScrollStateChanged(int i) {
// Do nothing
}
};
TextReadingPreviewPreference(Context context) { TextReadingPreviewPreference(Context context) {
super(context); super(context);
init(); init();
@@ -95,7 +78,23 @@ public class TextReadingPreviewPreference extends Preference {
adjustPaddings(previewLayout, backgroundView); adjustPaddings(previewLayout, backgroundView);
final ViewPager viewPager = (ViewPager) holder.findViewById(R.id.preview_pager); final ViewPager viewPager = (ViewPager) holder.findViewById(R.id.preview_pager);
viewPager.addOnPageChangeListener(mPageChangeListener); viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int i, float v, int i1) {
// Do nothing
}
@Override
public void onPageSelected(int i) {
mCurrentItem = i;
viewPager.setContentDescription(getContext().getString(mContentDescriptions[i]));
}
@Override
public void onPageScrollStateChanged(int i) {
// Do nothing
}
});
final DotsPageIndicator pageIndicator = final DotsPageIndicator pageIndicator =
(DotsPageIndicator) holder.findViewById(R.id.page_indicator); (DotsPageIndicator) holder.findViewById(R.id.page_indicator);
updateAdapterIfNeeded(viewPager, pageIndicator, mPreviewAdapter); updateAdapterIfNeeded(viewPager, pageIndicator, mPreviewAdapter);
@@ -122,6 +121,10 @@ public class TextReadingPreviewPreference extends Preference {
viewPager.setCurrentItem(getCurrentItem() + 1)); viewPager.setCurrentItem(getCurrentItem() + 1));
nextButton.setContentDescription(getContext().getString( nextButton.setContentDescription(getContext().getString(
R.string.preview_pager_next_button)); R.string.preview_pager_next_button));
// Initialize the content description since the OnPageChangeListener#onPageSelected won't
// be called during setup.
viewPager.setContentDescription(getContext().getString(mContentDescriptions[0]));
} }
@Override @Override
@@ -170,6 +173,10 @@ public class TextReadingPreviewPreference extends Preference {
); );
} }
void setContentDescription(int[] stringIds) {
mContentDescriptions = stringIds;
}
void setPreviewAdapter(PreviewPagerAdapter previewAdapter) { void setPreviewAdapter(PreviewPagerAdapter previewAdapter) {
if (previewAdapter != mPreviewAdapter) { if (previewAdapter != mPreviewAdapter) {
mPreviewAdapter = previewAdapter; mPreviewAdapter = previewAdapter;

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2025 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.accessibility;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
import android.content.ContentResolver;
import android.content.Context;
import android.provider.Settings;
import androidx.annotation.NonNull;
import com.android.server.accessibility.Flags;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
/** The controller to handle main switch to turn on or turn off accessibility autoclick. */
public class ToggleAutoclickMainSwitchPreferenceController
extends TogglePreferenceController {
private final ContentResolver mContentResolver;
public ToggleAutoclickMainSwitchPreferenceController(
@NonNull Context context, @NonNull String preferenceKey) {
super(context, preferenceKey);
mContentResolver = context.getContentResolver();
}
@Override
public int getAvailabilityStatus() {
return Flags.enableAutoclickIndicator() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
}
@Override
public boolean isChecked() {
return Settings.Secure.getInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, OFF) == ON;
}
@Override
public boolean setChecked(boolean isChecked) {
Settings.Secure.putInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
isChecked ? ON : OFF);
return true;
}
@Override
public int getSliceHighlightMenuRes() {
return R.string.menu_key_system;
}
}

View File

@@ -0,0 +1,5 @@
# Bug component: 970984
# Large Screen Experiences App Compat
gracielawputri@google.com
mcarli@google.com
mariiasand@google.com

View File

@@ -30,7 +30,7 @@ class BiometricSettingsProvider : ContentProvider() {
} }
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int { override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
throw UnsupportedOperationException("query operation not supported currently.") throw UnsupportedOperationException("delete operation not supported currently.")
} }
override fun getType(uri: Uri): String? { override fun getType(uri: Uri): String? {

View File

@@ -31,6 +31,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.supervision.SupervisionRestrictionsHelper;
/** /**
* Utilities for things at the cross-section of biometrics and parental controls. For example, * Utilities for things at the cross-section of biometrics and parental controls. For example,
@@ -59,12 +60,7 @@ public class ParentalControlsUtils {
UserManager.DISALLOW_BIOMETRIC, userHandle); UserManager.DISALLOW_BIOMETRIC, userHandle);
} }
final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); return parentConsentRequiredInternal(context, modality, userHandle);
final SupervisionManager sm =
android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()
? context.getSystemService(SupervisionManager.class)
: null;
return parentConsentRequiredInternal(dpm, sm, modality, userHandle);
} }
/** /**
@@ -74,18 +70,22 @@ public class ParentalControlsUtils {
@Nullable @Nullable
@VisibleForTesting @VisibleForTesting
static RestrictedLockUtils.EnforcedAdmin parentConsentRequiredInternal( static RestrictedLockUtils.EnforcedAdmin parentConsentRequiredInternal(
@NonNull DevicePolicyManager dpm, @NonNull Context context,
@Nullable SupervisionManager sm,
@BiometricAuthenticator.Modality int modality, @BiometricAuthenticator.Modality int modality,
@NonNull UserHandle userHandle) { @NonNull UserHandle userHandle) {
final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
final SupervisionManager sm =
android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()
? context.getSystemService(SupervisionManager.class)
: null;
if (!ParentalControlsUtilsInternal.parentConsentRequired( if (!ParentalControlsUtilsInternal.parentConsentRequired(
dpm, sm, modality, userHandle)) { dpm, sm, modality, userHandle)) {
return null; return null;
} }
if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) { if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) {
// Supervision doesn't necessarily have have an admin component. return SupervisionRestrictionsHelper.createEnforcedAdmin(
return new RestrictedLockUtils.EnforcedAdmin( context, UserManager.DISALLOW_BIOMETRIC, userHandle);
/* component= */ null, UserManager.DISALLOW_BIOMETRIC, userHandle);
} else { } else {
final ComponentName cn = final ComponentName cn =
ParentalControlsUtilsInternal.getSupervisionComponentName(dpm, userHandle); ParentalControlsUtilsInternal.getSupervisionComponentName(dpm, userHandle);

View File

@@ -70,6 +70,7 @@ public class FaceSettings extends DashboardFragment {
private static final String TAG = "FaceSettings"; private static final String TAG = "FaceSettings";
private static final String KEY_TOKEN = "hw_auth_token"; private static final String KEY_TOKEN = "hw_auth_token";
private static final String KEY_CONFIRMING_PASSWORD = "confirming_password";
private static final String KEY_RE_ENROLL_FACE = "re_enroll_face_unlock"; private static final String KEY_RE_ENROLL_FACE = "re_enroll_face_unlock";
private static final String KEY_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED = private static final String KEY_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED =
"biometrics_successfully_authenticated"; "biometrics_successfully_authenticated";
@@ -163,6 +164,7 @@ public class FaceSettings extends DashboardFragment {
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putByteArray(KEY_TOKEN, mToken); outState.putByteArray(KEY_TOKEN, mToken);
outState.putBoolean(KEY_CONFIRMING_PASSWORD, mConfirmingPassword);
} }
@Override @Override
@@ -273,6 +275,7 @@ public class FaceSettings extends DashboardFragment {
if (savedInstanceState != null) { if (savedInstanceState != null) {
mToken = savedInstanceState.getByteArray(KEY_TOKEN); mToken = savedInstanceState.getByteArray(KEY_TOKEN);
mConfirmingPassword = savedInstanceState.getBoolean(KEY_CONFIRMING_PASSWORD);
} }
if (Flags.biometricsOnboardingEducation()) { if (Flags.biometricsOnboardingEducation()) {

View File

@@ -98,11 +98,14 @@ public class FaceStatusUtils {
return mContext.getString( return mContext.getString(
com.android.settingslib.widget.restricted.R.string.disabled_by_admin); com.android.settingslib.widget.restricted.R.string.disabled_by_admin);
} else { } else {
int summaryNoneResId = Flags.biometricsOnboardingEducation()
? R.string.security_settings_face_preference_summary_none_new
: R.string.security_settings_face_preference_summary_none;
return mContext.getResources() return mContext.getResources()
.getString( .getString(
hasEnrolled() hasEnrolled()
? R.string.security_settings_face_preference_summary ? R.string.security_settings_face_preference_summary
: R.string.security_settings_face_preference_summary_none); : summaryNoneResId);
} }
} }

View File

@@ -108,7 +108,9 @@ public class FingerprintStatusUtils {
R.string.security_settings_fingerprint_preference_summary); R.string.security_settings_fingerprint_preference_summary);
} else { } else {
return mContext.getString( return mContext.getString(
R.string.security_settings_fingerprint_preference_summary_none); Flags.biometricsOnboardingEducation()
? R.string.security_settings_fingerprint_preference_summary_none_new
: R.string.security_settings_fingerprint_preference_summary_none);
} }
} }

View File

@@ -39,15 +39,14 @@ public class CommunalPreferenceController extends BasePreferenceController {
* Returns whether communal preferences are available. * Returns whether communal preferences are available.
*/ */
public static boolean isAvailable(Context context) { public static boolean isAvailable(Context context) {
if (com.android.systemui.Flags.glanceableHubV2()) {
return false;
}
if (!Utils.canCurrentUserDream(context)) { if (!Utils.canCurrentUserDream(context)) {
return false; return false;
} }
if (context.getResources().getBoolean(R.bool.config_show_communal_settings)) { return context.getResources().getBoolean(R.bool.config_show_communal_settings);
return true;
}
return com.android.systemui.Flags.glanceableHubV2()
&& context.getResources().getBoolean(R.bool.config_show_communal_settings_mobile);
} }
} }

View File

@@ -0,0 +1,179 @@
/*
* Copyright (C) 2025 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.communal;
import static android.provider.Settings.Secure.GLANCEABLE_HUB_START_CHARGING;
import static android.provider.Settings.Secure.GLANCEABLE_HUB_START_CHARGING_UPRIGHT;
import static android.provider.Settings.Secure.GLANCEABLE_HUB_START_DOCKED;
import static android.provider.Settings.Secure.GLANCEABLE_HUB_START_NEVER;
import static android.provider.Settings.Secure.WHEN_TO_START_GLANCEABLE_HUB;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settings.R;
import com.android.settings.widget.RadioButtonPickerFragment;
import com.android.settingslib.widget.CandidateInfo;
import java.util.ArrayList;
import java.util.List;
/**
* Fragment that provides radio buttons to allow the user to choose when the hub should auto-start.
*/
public class WhenToStartHubPicker extends RadioButtonPickerFragment {
private static final String TAG = "WhenToStartHubPicker";
private static final String SHOW_WHILE_CHARGING = "while_charging";
private static final String SHOW_WHILE_DOCKED = "while_docked";
private static final String SHOW_WHILE_CHARGING_AND_UPRIGHT = "while_charging_and_upright";
private static final String SHOW_NEVER = "never";
private Context mContext;
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
mContext = context;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.when_to_start_hubmode_settings;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.WHEN_TO_SHOW_WIDGETS_ON_LOCKSCREEN;
}
@Override
protected List<? extends CandidateInfo> getCandidates() {
final List<WhenToStartHubCandidateInfo> candidates = new ArrayList<>();
final String[] entries = entries();
final String[] values = keys();
if (entries == null || entries.length <= 0) return candidates;
if (values == null || values.length != entries.length) {
throw new IllegalArgumentException("Entries and values must be of the same length.");
}
for (int i = 0; i < entries.length; i++) {
candidates.add(new WhenToStartHubCandidateInfo(entries[i], values[i]));
}
return candidates;
}
private String[] entries() {
return getResources().getStringArray(R.array.when_to_start_hubmode_entries);
}
private String[] keys() {
return getResources().getStringArray(R.array.when_to_start_hubmode_values);
}
@Override
protected String getDefaultKey() {
final int defaultValue = mContext.getResources().getInteger(
com.android.internal.R.integer.config_whenToStartHubModeDefault);
final int setting = Settings.Secure.getInt(
mContext.getContentResolver(), WHEN_TO_START_GLANCEABLE_HUB, defaultValue);
return getKeyFromSetting(setting);
}
@Override
protected boolean setDefaultKey(String key) {
Settings.Secure.putInt(
mContext.getContentResolver(),
WHEN_TO_START_GLANCEABLE_HUB,
getSettingFromPrefKey(key));
return true;
}
@Override
protected void onSelectionPerformed(boolean success) {
super.onSelectionPerformed(success);
getActivity().finish();
}
@Settings.Secure.WhenToStartGlanceableHub
private static int getSettingFromPrefKey(String key) {
switch (key) {
case SHOW_WHILE_CHARGING:
return GLANCEABLE_HUB_START_CHARGING;
case SHOW_WHILE_DOCKED:
return GLANCEABLE_HUB_START_DOCKED;
case SHOW_WHILE_CHARGING_AND_UPRIGHT:
return GLANCEABLE_HUB_START_CHARGING_UPRIGHT;
case SHOW_NEVER:
default:
return GLANCEABLE_HUB_START_NEVER;
}
}
private static String getKeyFromSetting(@Settings.Secure.WhenToStartGlanceableHub int setting) {
switch (setting) {
case GLANCEABLE_HUB_START_CHARGING:
return SHOW_WHILE_CHARGING;
case GLANCEABLE_HUB_START_DOCKED:
return SHOW_WHILE_DOCKED;
case GLANCEABLE_HUB_START_CHARGING_UPRIGHT:
return SHOW_WHILE_CHARGING_AND_UPRIGHT;
case GLANCEABLE_HUB_START_NEVER:
default:
return SHOW_NEVER;
}
}
private static final class WhenToStartHubCandidateInfo extends CandidateInfo {
private final String mName;
private final String mKey;
WhenToStartHubCandidateInfo(String title, String value) {
super(true);
mName = title;
mKey = value;
}
@Override
public CharSequence loadLabel() {
return mName;
}
@Override
@Nullable
public Drawable loadIcon() {
return null;
}
@Override
public String getKey() {
return mKey;
}
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (C) 2025 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.communal;
import static android.provider.Settings.Secure.GLANCEABLE_HUB_START_CHARGING;
import static android.provider.Settings.Secure.GLANCEABLE_HUB_START_CHARGING_UPRIGHT;
import static android.provider.Settings.Secure.GLANCEABLE_HUB_START_DOCKED;
import static android.provider.Settings.Secure.GLANCEABLE_HUB_START_NEVER;
import static android.provider.Settings.Secure.WHEN_TO_START_GLANCEABLE_HUB;
import android.annotation.StringRes;
import android.content.Context;
import android.provider.Settings;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
/**
* A preference controller that is responsible for showing the "when to auto start hub" setting in
* hub settings.
*/
public class WhenToStartHubPreferenceController extends BasePreferenceController implements
PreferenceControllerMixin {
public WhenToStartHubPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
preference.setSummary(getSummaryResId());
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public CharSequence getSummary() {
return mContext.getString(getSummaryResId());
}
@StringRes
private int getSummaryResId() {
final int setting = Settings.Secure.getInt(
mContext.getContentResolver(),
WHEN_TO_START_GLANCEABLE_HUB,
GLANCEABLE_HUB_START_NEVER);
switch (setting) {
case GLANCEABLE_HUB_START_CHARGING:
return R.string.when_to_show_hubmode_charging;
case GLANCEABLE_HUB_START_DOCKED:
return R.string.when_to_show_hubmode_docked;
case GLANCEABLE_HUB_START_CHARGING_UPRIGHT:
return R.string.when_to_show_hubmode_charging_and_upright;
case GLANCEABLE_HUB_START_NEVER:
default:
return R.string.when_to_show_hubmode_never;
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2025 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.communal;
import android.app.settings.SettingsEnums;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
/**
* Fragment that contains settings related to communal hub.
*/
@SearchIndexable
public class WidgetsOnLockscreenFragment extends DashboardFragment {
private static final String TAG = "WidgetsOnLockscreenFragment";
@Override
public int getMetricsCategory() {
return SettingsEnums.WIDGETS_ON_LOCK_SCREEN;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.widgets_on_lockscreen_settings;
}
@Override
protected String getLogTag() {
return TAG;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.widgets_on_lockscreen_settings);
}

View File

@@ -26,9 +26,14 @@ import android.bluetooth.BluetoothLeBroadcastAssistant;
import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.ContentObserver;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -87,6 +92,7 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro
@Nullable private final LocalBluetoothProfileManager mProfileManager; @Nullable private final LocalBluetoothProfileManager mProfileManager;
@Nullable private final LocalBluetoothLeBroadcast mBroadcast; @Nullable private final LocalBluetoothLeBroadcast mBroadcast;
@Nullable private final LocalBluetoothLeBroadcastAssistant mAssistant; @Nullable private final LocalBluetoothLeBroadcastAssistant mAssistant;
@Nullable private final ContentResolver mContentResolver;
private final Executor mExecutor; private final Executor mExecutor;
private final MetricsFeatureProvider mMetricsFeatureProvider; private final MetricsFeatureProvider mMetricsFeatureProvider;
@Nullable private PreferenceGroup mPreferenceGroup; @Nullable private PreferenceGroup mPreferenceGroup;
@@ -187,6 +193,17 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro
@NonNull BluetoothLeBroadcastReceiveState state) {} @NonNull BluetoothLeBroadcastReceiveState state) {}
}; };
@VisibleForTesting
ContentObserver mSettingsObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange) {
Log.d(TAG, "onChange, primary group id has been changed, refresh list");
if (mBluetoothDeviceUpdater != null) {
mBluetoothDeviceUpdater.refreshPreference();
}
}
};
public AudioSharingDevicePreferenceController(Context context) { public AudioSharingDevicePreferenceController(Context context) {
super(context, KEY); super(context, KEY);
mBtManager = Utils.getLocalBtManager(mContext); mBtManager = Utils.getLocalBtManager(mContext);
@@ -198,6 +215,7 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro
mProfileManager == null mProfileManager == null
? null ? null
: mProfileManager.getLeAudioBroadcastAssistantProfile(); : mProfileManager.getLeAudioBroadcastAssistantProfile();
mContentResolver = context.getContentResolver();
mExecutor = Executors.newSingleThreadExecutor(); mExecutor = Executors.newSingleThreadExecutor();
mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
} }
@@ -217,6 +235,7 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro
if (mEventManager == null if (mEventManager == null
|| mAssistant == null || mAssistant == null
|| mDialogHandler == null || mDialogHandler == null
|| mContentResolver == null
|| mBluetoothDeviceUpdater == null) { || mBluetoothDeviceUpdater == null) {
Log.d(TAG, "Skip onStart(), profile is not ready."); Log.d(TAG, "Skip onStart(), profile is not ready.");
return; return;
@@ -225,6 +244,10 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro
mEventManager.registerCallback(this); mEventManager.registerCallback(this);
mAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback); mAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
mDialogHandler.registerCallbacks(mExecutor); mDialogHandler.registerCallbacks(mExecutor);
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(BluetoothUtils.getPrimaryGroupIdUriForBroadcast()),
false,
mSettingsObserver);
mBluetoothDeviceUpdater.registerCallback(); mBluetoothDeviceUpdater.registerCallback();
mBluetoothDeviceUpdater.refreshPreference(); mBluetoothDeviceUpdater.refreshPreference();
mIsAudioModeOngoingCall.set(isAudioModeOngoingCall(mContext)); mIsAudioModeOngoingCall.set(isAudioModeOngoingCall(mContext));
@@ -245,6 +268,7 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro
if (mEventManager == null if (mEventManager == null
|| mAssistant == null || mAssistant == null
|| mDialogHandler == null || mDialogHandler == null
|| mContentResolver == null
|| mBluetoothDeviceUpdater == null) { || mBluetoothDeviceUpdater == null) {
Log.d(TAG, "Skip onStop(), profile is not ready."); Log.d(TAG, "Skip onStop(), profile is not ready.");
return; return;
@@ -253,6 +277,7 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro
mEventManager.unregisterCallback(this); mEventManager.unregisterCallback(this);
mAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback); mAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
mDialogHandler.unregisterCallbacks(); mDialogHandler.unregisterCallbacks();
mContentResolver.unregisterContentObserver(mSettingsObserver);
mBluetoothDeviceUpdater.unregisterCallback(); mBluetoothDeviceUpdater.unregisterCallback();
}); });
} }

View File

@@ -40,6 +40,7 @@ import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.widget.EntityHeaderController; import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.flags.Flags;
import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.LayoutPreference; import com.android.settingslib.widget.LayoutPreference;
@@ -88,8 +89,10 @@ public class AudioStreamHeaderController extends BasePreferenceController
var localSourceState = getLocalSourceState(state); var localSourceState = getLocalSourceState(state);
if (localSourceState == STREAMING) { if (localSourceState == STREAMING) {
updateSummary(); updateSummary();
mAudioStreamsHelper.startMediaService( if (!Flags.audioStreamMediaServiceByReceiveState()) {
mContext, mBroadcastId, mBroadcastName); mAudioStreamsHelper.startMediaService(
mContext, mBroadcastId, mBroadcastName);
}
} else if (mHysteresisModeFixAvailable && localSourceState == PAUSED) { } else if (mHysteresisModeFixAvailable && localSourceState == PAUSED) {
// if source paused, only update the summary // if source paused, only update the summary
updateSummary(); updateSummary();

View File

@@ -16,9 +16,13 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams; package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_PRIVATE_BROADCAST_RECEIVE_DATA;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
import static java.util.Collections.emptyList;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
@@ -56,11 +60,14 @@ import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState;
import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.PrivateBroadcastReceiveData;
import com.android.settingslib.bluetooth.VolumeControlProfile; import com.android.settingslib.bluetooth.VolumeControlProfile;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.flags.Flags;
import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.utils.ThreadUtils;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
public class AudioStreamMediaService extends Service { public class AudioStreamMediaService extends Service {
@@ -103,7 +110,7 @@ public class AudioStreamMediaService extends Service {
private final PlaybackState.Builder mPlayStateHysteresisBuilder = private final PlaybackState.Builder mPlayStateHysteresisBuilder =
new PlaybackState.Builder() new PlaybackState.Builder()
.setState( .setState(
PlaybackState.STATE_STOPPED, PlaybackState.STATE_PAUSED,
STATIC_PLAYBACK_POSITION, STATIC_PLAYBACK_POSITION,
ZERO_PLAYBACK_SPEED) ZERO_PLAYBACK_SPEED)
.addCustomAction( .addCustomAction(
@@ -122,7 +129,9 @@ public class AudioStreamMediaService extends Service {
private int mLatestPositiveVolume = 25; private int mLatestPositiveVolume = 25;
private boolean mHysteresisModeFixAvailable; private boolean mHysteresisModeFixAvailable;
private int mBroadcastId; private int mBroadcastId;
@Nullable private Map<BluetoothDevice, LocalBluetoothLeBroadcastSourceState> mStateByDevice; @VisibleForTesting
@Nullable
Map<BluetoothDevice, LocalBluetoothLeBroadcastSourceState> mStateByDevice;
@Nullable private LocalBluetoothManager mLocalBtManager; @Nullable private LocalBluetoothManager mLocalBtManager;
@Nullable private AudioStreamsHelper mAudioStreamsHelper; @Nullable private AudioStreamsHelper mAudioStreamsHelper;
@Nullable private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant; @Nullable private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
@@ -236,6 +245,19 @@ public class AudioStreamMediaService extends Service {
stopSelf(); stopSelf();
return START_NOT_STICKY; return START_NOT_STICKY;
} }
// TODO(b/398700619): Remove hasExtra check when feasible.
if (Flags.audioStreamMediaServiceByReceiveState() && intent.hasExtra(
EXTRA_PRIVATE_BROADCAST_RECEIVE_DATA)) {
PrivateBroadcastReceiveData data = intent.getParcelableExtra(
EXTRA_PRIVATE_BROADCAST_RECEIVE_DATA, PrivateBroadcastReceiveData.class);
if (data == null || !PrivateBroadcastReceiveData.Companion.isValid(data)) {
Log.w(TAG, "Data is null or invalid. Service will not start.");
stopSelf();
return START_NOT_STICKY;
}
getHandler().post(() -> handleIntentData(data));
return START_NOT_STICKY;
}
getHandler().post(() -> { getHandler().post(() -> {
mBroadcastId = intent.getIntExtra(BROADCAST_ID, -1); mBroadcastId = intent.getIntExtra(BROADCAST_ID, -1);
if (mBroadcastId == -1) { if (mBroadcastId == -1) {
@@ -258,6 +280,78 @@ public class AudioStreamMediaService extends Service {
return START_NOT_STICKY; return START_NOT_STICKY;
} }
private void handleIntentData(PrivateBroadcastReceiveData data) {
int broadcastId = data.getBroadcastId();
BluetoothDevice device = data.getSink();
int sourceId = data.getSourceId();
var state = data.getState();
String programInfo = data.getProgramInfo();
// Service not running yet.
if (mBroadcastId == 0) {
Log.d(TAG, "handleIntentData(): sending " + data + " to handleInitialSetup()");
handleInitialSetup(broadcastId, device, state, sourceId, programInfo);
return;
}
// Service running with a different broadcast id, most likely staled. We have a new
// broadcast Id to handle.
if (mBroadcastId != broadcastId) {
Log.d(TAG, "handleIntentData(): sending " + data + " to handleNewBroadcastId()");
handleNewBroadcastId(broadcastId, device, state, sourceId, programInfo);
return;
}
// Service running with the same broadcast Id, we have new device joining or a state update.
if (mStateByDevice != null && (!mStateByDevice.containsKey(device) || mStateByDevice.get(
device) != state)) {
Log.d(TAG, "handleIntentData(): sending " + data + " to handleNewDeviceOrState()");
handleNewDeviceOrState(device, state, sourceId, programInfo);
}
Log.d(TAG, "handleIntentData(): nothing to update.");
}
private void handleInitialSetup(int broadcastId, BluetoothDevice device,
LocalBluetoothLeBroadcastSourceState state, int sourceId, String programInfo) {
if (state == DECRYPTION_FAILED) {
Log.d(TAG, "handleInitialSetup() : decryption failed. Service will not start.");
stopSelf();
return;
}
mBroadcastId = broadcastId;
mStateByDevice = new HashMap<>();
mStateByDevice.put(device, state);
MediaSession.Token token = getOrCreateLocalMediaSession(
getBroadcastName(device, sourceId, programInfo));
startForeground(NOTIFICATION_ID, buildNotification(token));
}
private void handleNewBroadcastId(int broadcastId, BluetoothDevice device,
LocalBluetoothLeBroadcastSourceState state, int sourceId, String programInfo) {
if (state == DECRYPTION_FAILED) {
Log.d(TAG, "handleNewBroadcastId() : decryption failed. Ignore.");
return;
}
mBroadcastId = broadcastId;
mStateByDevice = new HashMap<>();
mStateByDevice.put(device, state);
updateMediaSessionAndNotify(device, sourceId, programInfo);
}
private void handleNewDeviceOrState(BluetoothDevice device,
LocalBluetoothLeBroadcastSourceState state, int sourceId, String programInfo) {
if (mStateByDevice != null) {
mStateByDevice.put(device, state);
}
if (getDeviceInValidState().isEmpty()) {
Log.d(TAG, "handleNewDeviceOrState() : no device is in valid state. Stop service.");
stopSelf();
return;
}
updateMediaSessionAndNotify(device, sourceId, programInfo);
}
private MediaSession.Token getOrCreateLocalMediaSession(String title) { private MediaSession.Token getOrCreateLocalMediaSession(String title) {
if (mLocalSession != null) { if (mLocalSession != null) {
return mLocalSession.getSessionToken(); return mLocalSession.getSessionToken();
@@ -288,7 +382,8 @@ public class AudioStreamMediaService extends Service {
} }
private String getDeviceName() { private String getDeviceName() {
if (mStateByDevice == null || mStateByDevice.isEmpty() || mLocalBtManager == null) { List<BluetoothDevice> validDevices = getDeviceInValidState();
if (validDevices.isEmpty() || mLocalBtManager == null) {
return DEFAULT_DEVICE_NAME; return DEFAULT_DEVICE_NAME;
} }
@@ -297,8 +392,7 @@ public class AudioStreamMediaService extends Service {
return DEFAULT_DEVICE_NAME; return DEFAULT_DEVICE_NAME;
} }
CachedBluetoothDevice device = manager.findDevice( CachedBluetoothDevice device = manager.findDevice(validDevices.getFirst());
mStateByDevice.keySet().iterator().next());
return device != null ? device.getName() : DEFAULT_DEVICE_NAME; return device != null ? device.getName() : DEFAULT_DEVICE_NAME;
} }
@@ -320,6 +414,47 @@ public class AudioStreamMediaService extends Service {
return notificationBuilder.build(); return notificationBuilder.build();
} }
private void updateMediaSessionAndNotify(BluetoothDevice device, int sourceId,
String programInfo) {
if (mNotificationManager == null || mLocalSession == null) {
Log.w(TAG, "mNotificationManager or mLocalSession is null, ignore update.");
return;
}
mLocalSession.setMetadata(new MediaMetadata.Builder().putString(
MediaMetadata.METADATA_KEY_TITLE,
getBroadcastName(device, sourceId, programInfo)).putLong(
MediaMetadata.METADATA_KEY_DURATION, STATIC_PLAYBACK_DURATION).build());
mLocalSession.setPlaybackState(getPlaybackState());
mNotificationManager.notify(NOTIFICATION_ID,
buildNotification(mLocalSession.getSessionToken()));
}
private String getBroadcastName(BluetoothDevice sink, int sourceId, String programInfo) {
if (mLeBroadcastAssistant == null || sink == null) {
return programInfo;
}
var metadata = mLeBroadcastAssistant.getSourceMetadata(sink, sourceId);
if (metadata == null || metadata.getBroadcastId() != mBroadcastId
|| metadata.getBroadcastName() == null || metadata.getBroadcastName().isEmpty()) {
Log.d(TAG, "getBroadcastName(): source metadata not found, using programInfo: "
+ programInfo);
return programInfo;
}
return metadata.getBroadcastName();
}
private List<BluetoothDevice> getDeviceInValidState() {
if (mStateByDevice == null || mStateByDevice.isEmpty()) {
Log.w(TAG, "getDeviceInValidState() : mStateByDevice is null or empty!");
return emptyList();
}
if (Flags.audioStreamMediaServiceByReceiveState()) {
return mStateByDevice.entrySet().stream().filter(
entry -> entry.getValue() != DECRYPTION_FAILED).map(Map.Entry::getKey).toList();
}
return mStateByDevice.keySet().stream().toList();
}
@Nullable @Nullable
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
@@ -342,6 +477,9 @@ public class AudioStreamMediaService extends Service {
@Override @Override
public void onReceiveStateChanged( public void onReceiveStateChanged(
BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) { BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) {
if (Flags.audioStreamMediaServiceByReceiveState()) {
return;
}
super.onReceiveStateChanged(sink, sourceId, state); super.onReceiveStateChanged(sink, sourceId, state);
if (!mHysteresisModeFixAvailable || mStateByDevice == null if (!mHysteresisModeFixAvailable || mStateByDevice == null
|| !mStateByDevice.containsKey(sink)) { || !mStateByDevice.containsKey(sink)) {
@@ -383,23 +521,21 @@ public class AudioStreamMediaService extends Service {
@Override @Override
public void onDeviceVolumeChanged( public void onDeviceVolumeChanged(
@NonNull BluetoothDevice device, @IntRange(from = -255, to = 255) int volume) { @NonNull BluetoothDevice device, @IntRange(from = -255, to = 255) int volume) {
if (mStateByDevice == null || mStateByDevice.isEmpty()) { if (!getDeviceInValidState().contains(device)) {
Log.w(TAG, "active device or device has source is null!"); Log.w(TAG, "onDeviceVolumeChanged() : device not in valid state list");
return; return;
} }
Log.d( Log.d(
TAG, TAG,
"onDeviceVolumeChanged() bluetoothDevice : " + device + " volume: " + volume); "onDeviceVolumeChanged() bluetoothDevice : " + device + " volume: " + volume);
if (mStateByDevice.containsKey(device)) { if (volume == 0) {
if (volume == 0) { mIsMuted = true;
mIsMuted = true; } else {
} else { mIsMuted = false;
mIsMuted = false; mLatestPositiveVolume = volume;
mLatestPositiveVolume = volume; }
} if (mLocalSession != null) {
if (mLocalSession != null) { mLocalSession.setPlaybackState(getPlaybackState());
mLocalSession.setPlaybackState(getPlaybackState());
}
} }
} }
} }
@@ -426,7 +562,7 @@ public class AudioStreamMediaService extends Service {
&& mStateByDevice != null) { && mStateByDevice != null) {
mStateByDevice.remove(cachedDevice.getDevice()); mStateByDevice.remove(cachedDevice.getDevice());
} }
if (mStateByDevice == null || mStateByDevice.isEmpty()) { if (getDeviceInValidState().isEmpty()) {
Log.d(TAG, "onProfileConnectionStateChanged() : stopSelf"); Log.d(TAG, "onProfileConnectionStateChanged() : stopSelf");
stopSelf(); stopSelf();
} }
@@ -484,11 +620,7 @@ public class AudioStreamMediaService extends Service {
} }
private void handleOnPlay() { private void handleOnPlay() {
if (mStateByDevice == null || mStateByDevice.isEmpty()) { getDeviceInValidState().forEach(device -> {
Log.w(TAG, "active device or device has source is null!");
return;
}
mStateByDevice.keySet().forEach(device -> {
Log.d(TAG, "onPlay() setting volume for device : " + device + " volume: " Log.d(TAG, "onPlay() setting volume for device : " + device + " volume: "
+ mLatestPositiveVolume); + mLatestPositiveVolume);
setDeviceVolume(device, mLatestPositiveVolume); setDeviceVolume(device, mLatestPositiveVolume);
@@ -496,11 +628,7 @@ public class AudioStreamMediaService extends Service {
} }
private void handleOnPause() { private void handleOnPause() {
if (mStateByDevice == null || mStateByDevice.isEmpty()) { getDeviceInValidState().forEach(device -> {
Log.w(TAG, "active device or device has source is null!");
return;
}
mStateByDevice.keySet().forEach(device -> {
Log.d(TAG, "onPause() setting volume for device : " + device + " volume: " + 0); Log.d(TAG, "onPause() setting volume for device : " + device + " volume: " + 0);
setDeviceVolume(device, /* volume= */ 0); setDeviceVolume(device, /* volume= */ 0);
}); });

View File

@@ -26,6 +26,7 @@ import androidx.preference.Preference;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher; import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment; import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.flags.Flags;
class SourceAddedState extends AudioStreamStateHandler { class SourceAddedState extends AudioStreamStateHandler {
@VisibleForTesting @VisibleForTesting
@@ -55,10 +56,12 @@ class SourceAddedState extends AudioStreamStateHandler {
if (cached != null) { if (cached != null) {
mAudioStreamsRepository.saveMetadata(context, cached); mAudioStreamsRepository.saveMetadata(context, cached);
} }
helper.startMediaService( if (!Flags.audioStreamMediaServiceByReceiveState()) {
context, helper.startMediaService(
preference.getAudioStreamBroadcastId(), context,
String.valueOf(preference.getTitle())); preference.getAudioStreamBroadcastId(),
String.valueOf(preference.getTitle()));
}
mMetricsFeatureProvider.action( mMetricsFeatureProvider.action(
preference.getContext(), preference.getContext(),
SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED, SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED,

View File

@@ -81,9 +81,11 @@ public class ExternalDisplaySizePreference extends AccessibilitySeekBarPreferenc
implements SeekBar.OnSeekBarChangeListener { implements SeekBar.OnSeekBarChangeListener {
private static final long MIN_COMMIT_INTERVAL_MS = 800; private static final long MIN_COMMIT_INTERVAL_MS = 800;
private static final long CHANGE_BY_BUTTON_DELAY_MS = 300; private static final long CHANGE_BY_BUTTON_DELAY_MS = 300;
private static final long CHANGE_BY_SEEKBAR_DELAY_MS = 100;
private final DisplaySizeData mDisplaySizeData; private final DisplaySizeData mDisplaySizeData;
private int mLastDisplayProgress; private int mLastDisplayProgress;
private long mLastCommitTime; private long mLastCommitTime;
private boolean mSeekByTouch;
ExternalDisplaySizePreferenceStateHandler(DisplaySizeData displaySizeData) { ExternalDisplaySizePreferenceStateHandler(DisplaySizeData displaySizeData) {
mDisplaySizeData = displaySizeData; mDisplaySizeData = displaySizeData;
} }
@@ -99,8 +101,7 @@ public class ExternalDisplaySizePreference extends AccessibilitySeekBarPreferenc
mLastCommitTime = SystemClock.elapsedRealtime(); mLastCommitTime = SystemClock.elapsedRealtime();
} }
private void postCommitDelayed() { private void postCommitDelayed(long commitDelayMs) {
var commitDelayMs = CHANGE_BY_BUTTON_DELAY_MS;
if (SystemClock.elapsedRealtime() - mLastCommitTime < MIN_COMMIT_INTERVAL_MS) { if (SystemClock.elapsedRealtime() - mLastCommitTime < MIN_COMMIT_INTERVAL_MS) {
commitDelayMs += MIN_COMMIT_INTERVAL_MS; commitDelayMs += MIN_COMMIT_INTERVAL_MS;
} }
@@ -112,13 +113,18 @@ public class ExternalDisplaySizePreference extends AccessibilitySeekBarPreferenc
@Override @Override
public void onProgressChanged(@NonNull SeekBar seekBar, int i, boolean b) { public void onProgressChanged(@NonNull SeekBar seekBar, int i, boolean b) {
postCommitDelayed(); if (!mSeekByTouch) postCommitDelayed(CHANGE_BY_BUTTON_DELAY_MS);
} }
@Override @Override
public void onStartTrackingTouch(@NonNull SeekBar seekBar) {} public void onStartTrackingTouch(@NonNull SeekBar seekBar) {
mSeekByTouch = true;
}
@Override @Override
public void onStopTrackingTouch(@NonNull SeekBar seekBar) {} public void onStopTrackingTouch(@NonNull SeekBar seekBar) {
mSeekByTouch = false;
postCommitDelayed(CHANGE_BY_SEEKBAR_DELAY_MS);
}
} }
} }

View File

@@ -104,3 +104,9 @@ const val KEY_RING_VOLUME = "separate_ring_volume"
/** Contract key for the "Remove animation" setting. */ /** Contract key for the "Remove animation" setting. */
const val KEY_REMOVE_ANIMATION = "remove_animation" const val KEY_REMOVE_ANIMATION = "remove_animation"
/** Contract key for the "Pin media player. */
const val KEY_PIN_MEDIA_PLAYER = "pin_media_player"
/** Contract key for the "Show media on lock screen. */
const val KEY_SHOW_MEDIA_ON_LOCK_SCREEN = "show_media_on_lock_screen"

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2025 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.display;
import android.content.Context;
import android.os.UserHandle;
import android.os.UserManager;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
/** Controls the "widgets on lock screen" preferences (under "Display & touch"). */
public class WidgetsOnLockscreenPreferenceController extends BasePreferenceController {
public WidgetsOnLockscreenPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return isAvailable(mContext) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
/**
* Returns whether "widgets on lock screen" preferences are available.
*/
public static boolean isAvailable(Context context) {
if (!isMainUser(context)) {
return false;
}
return com.android.systemui.Flags.glanceableHubV2()
&& (context.getResources().getBoolean(R.bool.config_show_communal_settings)
|| context.getResources().getBoolean(
R.bool.config_show_communal_settings_mobile));
}
private static boolean isMainUser(Context context) {
final UserManager userManager = context.getSystemService(UserManager.class);
return userManager.getUserInfo(UserHandle.myUserId()).isMain();
}
}

View File

@@ -122,6 +122,8 @@ public abstract class KeyboardAccessibilityKeysDialogFragment extends DialogFrag
R.id.input_setting_keys_value_custom); R.id.input_setting_keys_value_custom);
TextView customValueTextView = accessibilityKeyDialog.findViewById( TextView customValueTextView = accessibilityKeyDialog.findViewById(
R.id.input_setting_keys_value_custom_value); R.id.input_setting_keys_value_custom_value);
View seekbarView = accessibilityKeyDialog.findViewById(
R.id.input_setting_keys_custom_seekbar_layout);
SeekBar customProgressBar = accessibilityKeyDialog.findViewById( SeekBar customProgressBar = accessibilityKeyDialog.findViewById(
R.id.input_setting_keys_value_custom_slider); R.id.input_setting_keys_value_custom_slider);
TextView titleTextView = accessibilityKeyDialog.findViewById( TextView titleTextView = accessibilityKeyDialog.findViewById(
@@ -147,7 +149,7 @@ public abstract class KeyboardAccessibilityKeysDialogFragment extends DialogFrag
customValueTextView.setVisibility(isChecked ? View.VISIBLE : View.GONE); customValueTextView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
customValueTextView.setText( customValueTextView.setText(
progressToThresholdInSecond(customProgressBar.getProgress())); progressToThresholdInSecond(customProgressBar.getProgress()));
customProgressBar.setVisibility(isChecked ? View.VISIBLE : View.GONE); seekbarView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
buttonView.setChecked(isChecked); buttonView.setChecked(isChecked);
}); });
cannedValueRadioGroup.setOnCheckedChangeListener( cannedValueRadioGroup.setOnCheckedChangeListener(
@@ -174,14 +176,14 @@ public abstract class KeyboardAccessibilityKeysDialogFragment extends DialogFrag
// setting // setting
initStateBasedOnThreshold(cannedValueRadioGroup, customRadioButton, initStateBasedOnThreshold(cannedValueRadioGroup, customRadioButton,
customValueTextView, customValueTextView,
customProgressBar); customProgressBar, seekbarView);
} else if (customRadioButton.isChecked()) { } else if (customRadioButton.isChecked()) {
cannedValueRadioGroup.clearCheck(); cannedValueRadioGroup.clearCheck();
customRadioButton.setChecked(true); customRadioButton.setChecked(true);
customValueTextView.setVisibility(View.VISIBLE); customValueTextView.setVisibility(View.VISIBLE);
customValueTextView.setText( customValueTextView.setText(
progressToThresholdInSecond(customProgressBar.getProgress())); progressToThresholdInSecond(customProgressBar.getProgress()));
customProgressBar.setVisibility(View.VISIBLE); seekbarView.setVisibility(View.VISIBLE);
} }
}); });
@@ -199,7 +201,7 @@ public abstract class KeyboardAccessibilityKeysDialogFragment extends DialogFrag
private void initStateBasedOnThreshold(RadioGroup cannedValueRadioGroup, private void initStateBasedOnThreshold(RadioGroup cannedValueRadioGroup,
RadioButton customRadioButton, TextView customValueTextView, RadioButton customRadioButton, TextView customValueTextView,
SeekBar customProgressBar) { SeekBar customProgressBar, View seekbarView) {
int inputSettingKeysThreshold = getInputSettingKeysValue(); int inputSettingKeysThreshold = getInputSettingKeysValue();
switch (inputSettingKeysThreshold) { switch (inputSettingKeysThreshold) {
case 600 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_600); case 600 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_600);
@@ -213,5 +215,6 @@ public abstract class KeyboardAccessibilityKeysDialogFragment extends DialogFrag
customRadioButton.setChecked(true); customRadioButton.setChecked(true);
} }
} }
seekbarView.setVisibility(customRadioButton.isChecked() ? View.VISIBLE : View.GONE);
} }
} }

View File

@@ -18,34 +18,68 @@ package com.android.settings.network.ethernet
import android.content.Context import android.content.Context
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.EthernetManager import android.net.EthernetManager
import android.net.EthernetManager.STATE_ABSENT import android.net.EthernetManager.STATE_ABSENT
import android.net.EthernetNetworkManagementException import android.net.EthernetNetworkManagementException
import android.net.EthernetNetworkUpdateRequest import android.net.EthernetNetworkUpdateRequest
import android.net.IpConfiguration import android.net.IpConfiguration
import android.net.LinkProperties
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.OutcomeReceiver import android.os.OutcomeReceiver
import android.util.Log import android.util.Log
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.google.common.annotations.VisibleForTesting
class EthernetInterface(private val context: Context, private val id: String) : class EthernetInterface(private val context: Context, private val id: String) :
EthernetManager.InterfaceStateListener { EthernetManager.InterfaceStateListener {
interface EthernetInterfaceStateListener {
fun interfaceUpdated()
}
private val ethernetManager: EthernetManager? = private val ethernetManager: EthernetManager? =
context.getSystemService(EthernetManager::class.java) context.getSystemService(EthernetManager::class.java)
private val connectivityManager: ConnectivityManager? = private val connectivityManager: ConnectivityManager? =
context.getSystemService(ConnectivityManager::class.java) context.getSystemService(ConnectivityManager::class.java)
private val executor = ContextCompat.getMainExecutor(context) private val executor = ContextCompat.getMainExecutor(context)
private val interfaceListeners = mutableListOf<EthernetInterfaceStateListener>()
private val TAG = "EthernetInterface" private val TAG = "EthernetInterface"
private val networkRequest: NetworkRequest =
NetworkRequest.Builder()
.clearCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
.build()
private var interfaceState = STATE_ABSENT private var interfaceState = STATE_ABSENT
private var ipConfiguration = IpConfiguration() private var ipConfiguration = IpConfiguration()
private var linkProperties = LinkProperties()
fun getInterfaceState() = interfaceState fun getInterfaceState() = interfaceState
fun getId() = id fun getId() = id
fun getConfiguration(): IpConfiguration { fun getConfiguration() = ipConfiguration
return ipConfiguration
fun getLinkProperties() = linkProperties
fun registerListener(listener: EthernetInterfaceStateListener) {
if (interfaceListeners.isEmpty()) {
ethernetManager?.addInterfaceStateListener(ContextCompat.getMainExecutor(context), this)
connectivityManager?.registerNetworkCallback(networkRequest, networkCallback)
}
interfaceListeners.add(listener)
}
fun unregisterListener(listener: EthernetInterfaceStateListener) {
interfaceListeners.remove(listener)
if (interfaceListeners.isEmpty()) {
connectivityManager?.unregisterNetworkCallback(networkCallback)
ethernetManager?.removeInterfaceStateListener(this)
}
} }
fun setConfiguration(ipConfiguration: IpConfiguration) { fun setConfiguration(ipConfiguration: IpConfiguration) {
@@ -67,10 +101,28 @@ class EthernetInterface(private val context: Context, private val id: String) :
) )
} }
private fun notifyListeners() {
for (listener in interfaceListeners) {
listener.interfaceUpdated()
}
}
override fun onInterfaceStateChanged(id: String, state: Int, role: Int, cfg: IpConfiguration?) { override fun onInterfaceStateChanged(id: String, state: Int, role: Int, cfg: IpConfiguration?) {
if (id == this.id) { if (id == this.id) {
ipConfiguration = cfg ?: IpConfiguration() ipConfiguration = cfg ?: IpConfiguration()
interfaceState = state interfaceState = state
notifyListeners()
} }
} }
@VisibleForTesting
val networkCallback =
object : NetworkCallback() {
override fun onLinkPropertiesChanged(network: Network, lp: LinkProperties) {
if (lp.getInterfaceName().equals(id)) {
linkProperties = lp
notifyListeners()
}
}
}
} }

View File

@@ -18,7 +18,14 @@ package com.android.settings.network.ethernet
import android.content.Context import android.content.Context
import android.net.EthernetManager import android.net.EthernetManager
import android.net.IpConfiguration
import android.net.LinkProperties
import android.net.StaticIpConfiguration
import android.widget.ImageView import android.widget.ImageView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import com.android.settings.R import com.android.settings.R
@@ -30,13 +37,25 @@ class EthernetInterfaceDetailsController(
context: Context, context: Context,
private val fragment: PreferenceFragmentCompat, private val fragment: PreferenceFragmentCompat,
private val preferenceId: String, private val preferenceId: String,
) : AbstractPreferenceController(context) { private val lifecycle: Lifecycle,
) :
AbstractPreferenceController(context),
EthernetInterface.EthernetInterfaceStateListener,
LifecycleEventObserver {
private val KEY_HEADER = "ethernet_details" private val KEY_HEADER = "ethernet_details"
private val ethernetManager = context.getSystemService(EthernetManager::class.java) private val ethernetManager = context.getSystemService(EthernetManager::class.java)
private val ethernetInterface = private val ethernetInterface =
EthernetTrackerImpl.getInstance(context).getInterface(preferenceId) EthernetTrackerImpl.getInstance(context).getInterface(preferenceId)
private lateinit var entityHeaderController: EntityHeaderController
private var ipAddressPref: Preference? = null
init {
lifecycle.addObserver(this)
}
override fun isAvailable(): Boolean { override fun isAvailable(): Boolean {
return true return true
} }
@@ -45,10 +64,24 @@ class EthernetInterfaceDetailsController(
return KEY_HEADER return KEY_HEADER
} }
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
when (event) {
Lifecycle.Event.ON_START -> {
ethernetInterface?.registerListener(this)
}
Lifecycle.Event.ON_STOP -> {
ethernetInterface?.unregisterListener(this)
}
else -> {}
}
}
override fun displayPreference(screen: PreferenceScreen) { override fun displayPreference(screen: PreferenceScreen) {
val headerPref: LayoutPreference? = screen.findPreference(KEY_HEADER) val headerPref: LayoutPreference? = screen.findPreference(KEY_HEADER)
val mEntityHeaderController = entityHeaderController =
EntityHeaderController.newInstance( EntityHeaderController.newInstance(
fragment.getActivity(), fragment.getActivity(),
fragment, fragment,
@@ -59,17 +92,49 @@ class EthernetInterfaceDetailsController(
iconView?.setScaleType(ImageView.ScaleType.CENTER_INSIDE) iconView?.setScaleType(ImageView.ScaleType.CENTER_INSIDE)
mEntityHeaderController if (entityHeaderController != null) {
.setLabel("Ethernet") entityHeaderController
.setSummary( .setLabel("Ethernet")
if (ethernetInterface?.getInterfaceState() == EthernetManager.STATE_LINK_UP) { .setSummary(
mContext.getString(R.string.network_connected) if (ethernetInterface?.getInterfaceState() == EthernetManager.STATE_LINK_UP) {
} else { mContext.getString(R.string.network_connected)
mContext.getString(R.string.network_disconnected) } else {
} mContext.getString(R.string.network_disconnected)
) }
.setSecondSummary("") )
.setIcon(mContext.getDrawable(R.drawable.ic_settings_ethernet)) .setSecondSummary("")
.done(true /* rebind */) .setIcon(mContext.getDrawable(R.drawable.ic_settings_ethernet))
.done(true /* rebind */)
}
ipAddressPref = screen.findPreference<Preference>("ethernet_ip_address")
if (ethernetInterface?.getInterfaceState() == EthernetManager.STATE_LINK_UP) {
initializeIpDetails()
}
}
override fun interfaceUpdated() {
entityHeaderController?.setSummary(
if (ethernetInterface?.getInterfaceState() == EthernetManager.STATE_LINK_UP) {
mContext.getString(R.string.network_connected)
} else {
mContext.getString(R.string.network_disconnected)
}
)
initializeIpDetails()
}
private fun initializeIpDetails() {
val ipConfiguration: IpConfiguration? = ethernetInterface?.getConfiguration()
val linkProperties: LinkProperties? = ethernetInterface?.getLinkProperties()
if (ipConfiguration?.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {
val staticIp: StaticIpConfiguration? = ipConfiguration?.getStaticIpConfiguration()
ipAddressPref?.setSummary(staticIp?.getIpAddress().toString())
} else {
val addresses = linkProperties?.getAddresses()
ipAddressPref?.setSummary(addresses?.first().toString())
}
} }
} }

View File

@@ -50,6 +50,13 @@ class EthernetInterfaceDetailsFragment : DashboardFragment() {
override public fun createPreferenceControllers( override public fun createPreferenceControllers(
context: Context context: Context
): List<AbstractPreferenceController> { ): List<AbstractPreferenceController> {
return listOf(EthernetInterfaceDetailsController(context, this, preferenceId ?: "")) return listOf(
EthernetInterfaceDetailsController(
context,
this,
preferenceId ?: "",
getSettingsLifecycle(),
)
)
} }
} }

View File

@@ -0,0 +1,4 @@
jtomljanovic@google.com
elliotsisteron@google.com
deweytyl@google.com
simonjw@google.com

View File

@@ -22,6 +22,7 @@ import android.security.advancedprotection.AdvancedProtectionManager.EXTRA_SUPPO
import android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_CELLULAR_2G import android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_CELLULAR_2G
import android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES import android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES
import android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_WEP import android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_WEP
import android.content.pm.PackageManager
import android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_ENABLE_MTE import android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_ENABLE_MTE
import android.security.advancedprotection.AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION import android.security.advancedprotection.AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION
import android.security.advancedprotection.AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_DISABLED_SETTING import android.security.advancedprotection.AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_DISABLED_SETTING
@@ -37,13 +38,17 @@ import com.android.settingslib.spa.SpaDialogWindowTypeActivity
import com.android.settingslib.spa.widget.dialog.AlertDialogButton import com.android.settingslib.spa.widget.dialog.AlertDialogButton
import com.android.settingslib.spa.widget.dialog.SettingsAlertDialogContent import com.android.settingslib.spa.widget.dialog.SettingsAlertDialogContent
import com.android.settingslib.wifi.WifiUtils.Companion.DIALOG_WINDOW_TYPE import com.android.settingslib.wifi.WifiUtils.Companion.DIALOG_WINDOW_TYPE
import android.security.advancedprotection.AdvancedProtectionManager
class ActionDisabledByAdvancedProtectionDialog : SpaDialogWindowTypeActivity() { class ActionDisabledByAdvancedProtectionDialog : SpaDialogWindowTypeActivity() {
@Composable @Composable
override fun Content() { override fun Content() {
SettingsAlertDialogContent( SettingsAlertDialogContent(
confirmButton = AlertDialogButton(getString(R.string.okay)) { finish() }, confirmButton = AlertDialogButton(getString(R.string.okay)) {
finish()
logDialogShown(learnMoreClicked = false)
},
dismissButton = getSupportButtonIfExists(), dismissButton = getSupportButtonIfExists(),
title = getString(R.string.disabled_by_advanced_protection_title), title = getString(R.string.disabled_by_advanced_protection_title),
icon = { icon = {
@@ -56,8 +61,8 @@ class ActionDisabledByAdvancedProtectionDialog : SpaDialogWindowTypeActivity() {
} }
private fun getDialogMessage(): String { private fun getDialogMessage(): String {
val featureId = intent.getIntExtra(EXTRA_SUPPORT_DIALOG_FEATURE, -1) val featureId = getIntentFeatureId()
val type = intent.getIntExtra(EXTRA_SUPPORT_DIALOG_TYPE, SUPPORT_DIALOG_TYPE_UNKNOWN) val type = getIntentDialogueType()
val messageId = when (type) { val messageId = when (type) {
SUPPORT_DIALOG_TYPE_DISABLED_SETTING -> { SUPPORT_DIALOG_TYPE_DISABLED_SETTING -> {
if (featureIdsWithSettingOn.contains(featureId)) { if (featureIdsWithSettingOn.contains(featureId)) {
@@ -93,6 +98,7 @@ class ActionDisabledByAdvancedProtectionDialog : SpaDialogWindowTypeActivity() {
) { ) {
startActivity(helpIntent) startActivity(helpIntent)
finish() finish()
logDialogShown(learnMoreClicked = true)
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.w(TAG, "Tried to set up help button, but this exception was thrown: ${e.message}") Log.w(TAG, "Tried to set up help button, but this exception was thrown: ${e.message}")
@@ -100,10 +106,29 @@ class ActionDisabledByAdvancedProtectionDialog : SpaDialogWindowTypeActivity() {
return null return null
} }
private fun logDialogShown(learnMoreClicked: Boolean) {
// We should always have this permission, but just in case we don't, we should not log.
if (checkSelfPermission(android.Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE)
!= PackageManager.PERMISSION_GRANTED) {
return
}
this.getSystemService(AdvancedProtectionManager::class.java)
.logDialogShown(getIntentFeatureId(), getIntentDialogueType(), learnMoreClicked)
}
override fun getDialogWindowType(): Int? = if (intent.hasExtra(DIALOG_WINDOW_TYPE)) { override fun getDialogWindowType(): Int? = if (intent.hasExtra(DIALOG_WINDOW_TYPE)) {
intent.getIntExtra(DIALOG_WINDOW_TYPE, WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW) intent.getIntExtra(DIALOG_WINDOW_TYPE, WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW)
} else null } else null
private fun getIntentFeatureId(): Int {
return intent.getIntExtra(EXTRA_SUPPORT_DIALOG_FEATURE, -1)
}
private fun getIntentDialogueType(): Int {
return intent.getIntExtra(EXTRA_SUPPORT_DIALOG_TYPE, SUPPORT_DIALOG_TYPE_UNKNOWN)
}
private companion object { private companion object {
const val TAG = "AdvancedProtectionDlg" const val TAG = "AdvancedProtectionDlg"
val defaultMessageId = R.string.disabled_by_advanced_protection_action_message val defaultMessageId = R.string.disabled_by_advanced_protection_action_message

View File

@@ -178,7 +178,9 @@ public class ScreenLockPreferenceDetailsUtils {
if (!mLockPatternUtils.isSecure(userId)) { if (!mLockPatternUtils.isSecure(userId)) {
if (userId == mProfileChallengeUserId if (userId == mProfileChallengeUserId
|| mLockPatternUtils.isLockScreenDisabled(userId)) { || mLockPatternUtils.isLockScreenDisabled(userId)) {
return R.string.unlock_set_unlock_mode_off; return com.android.settings.flags.Flags.biometricsOnboardingEducation()
? R.string.unlock_set_unlock_mode_off_new
: R.string.unlock_set_unlock_mode_off;
} else { } else {
return R.string.unlock_set_unlock_mode_none; return R.string.unlock_set_unlock_mode_none;
} }

View File

@@ -16,9 +16,12 @@
package com.android.settings.sound package com.android.settings.sound
import android.app.settings.SettingsEnums.ACTION_SHOW_MEDIA_ON_LOCK_SCREEN
import android.content.Context import android.content.Context
import android.provider.Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN import android.provider.Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN
import com.android.settings.R import com.android.settings.R
import com.android.settings.contract.KEY_SHOW_MEDIA_ON_LOCK_SCREEN
import com.android.settings.metrics.PreferenceActionMetricsProvider
import com.android.settingslib.datastore.KeyValueStore import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.KeyValueStoreDelegate import com.android.settingslib.datastore.KeyValueStoreDelegate
import com.android.settingslib.datastore.SettingsSecureStore import com.android.settingslib.datastore.SettingsSecureStore
@@ -32,7 +35,13 @@ class MediaControlsLockscreenSwitchPreference :
KEY, KEY,
R.string.media_controls_lockscreen_title, R.string.media_controls_lockscreen_title,
R.string.media_controls_lockscreen_description, R.string.media_controls_lockscreen_description,
) { ),
PreferenceActionMetricsProvider {
override val preferenceActionMetrics: Int
get() = ACTION_SHOW_MEDIA_ON_LOCK_SCREEN
override fun tags(context: Context) = arrayOf(KEY_SHOW_MEDIA_ON_LOCK_SCREEN)
override val sensitivityLevel override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY get() = SensitivityLevel.NO_SENSITIVITY

View File

@@ -16,30 +16,39 @@
package com.android.settings.sound package com.android.settings.sound
import android.app.settings.SettingsEnums.ACTION_PIN_MEDIA_PLAYER
import android.content.Context import android.content.Context
import android.provider.Settings.Secure.MEDIA_CONTROLS_RESUME import android.provider.Settings.Secure.MEDIA_CONTROLS_RESUME
import com.android.settings.R
import com.android.settings.contract.KEY_PIN_MEDIA_PLAYER
import com.android.settings.metrics.PreferenceActionMetricsProvider
import com.android.settingslib.datastore.KeyValueStore import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.SettingsSecureStore import com.android.settingslib.datastore.SettingsSecureStore
import com.android.settingslib.metadata.ReadWritePermit import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel import com.android.settingslib.metadata.SensitivityLevel
import com.android.settingslib.metadata.SwitchPreference import com.android.settingslib.metadata.SwitchPreference
import com.android.settings.R
// LINT.IfChange // LINT.IfChange
class MediaControlsSwitchPreference( class MediaControlsSwitchPreference(
private val mediaControlsStore: MediaControlsScreen.MediaControlsStore, private val mediaControlsStore: MediaControlsScreen.MediaControlsStore
) : SwitchPreference( ) :
KEY, SwitchPreference(
R.string.media_controls_resume_title, KEY,
R.string.media_controls_resume_description, R.string.media_controls_resume_title,
) { R.string.media_controls_resume_description,
),
PreferenceActionMetricsProvider {
override val sensitivityLevel override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY get() = SensitivityLevel.NO_SENSITIVITY
override val keywords: Int override val keywords: Int
get() = R.string.keywords_media_controls get() = R.string.keywords_media_controls
override val preferenceActionMetrics: Int
get() = ACTION_PIN_MEDIA_PLAYER
override fun tags(context: Context) = arrayOf(KEY_PIN_MEDIA_PLAYER)
override fun getReadPermissions(context: Context) = SettingsSecureStore.getReadPermissions() override fun getReadPermissions(context: Context) = SettingsSecureStore.getReadPermissions()
override fun getWritePermissions(context: Context) = SettingsSecureStore.getWritePermissions() override fun getWritePermissions(context: Context) = SettingsSecureStore.getWritePermissions()

View File

@@ -0,0 +1,5 @@
# Bug component: 970984
# Large Screen Experiences App Compat
gracielawputri@google.com
mcarli@google.com
mariiasand@google.com

View File

@@ -0,0 +1,101 @@
/*
* Copyright (C) 2025 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.supervision
import android.Manifest.permission.USE_BIOMETRIC
import android.app.Activity
import android.content.pm.PackageManager
import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.BiometricPrompt
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback
import android.os.Bundle
import android.os.CancellationSignal
import android.util.Log
import androidx.annotation.RequiresPermission
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.android.settings.R
/**
* Activity for confirming supervision credentials using device credential authentication.
*
* This activity displays an authentication prompt to the user, requiring them to authenticate using
* their device credentials (PIN, pattern, or password). It is specifically designed for verifying
* credentials for supervision purposes.
*
* It returns `Activity.RESULT_OK` if authentication succeeds, and `Activity.RESULT_CANCELED` if
* authentication fails or is canceled by the user.
*
* Usage:
* 1. Start this activity using `startActivityForResult()`.
* 2. Handle the result in `onActivityResult()`.
*
* Permissions:
* - Requires `android.permission.USE_BIOMETRIC`.
*/
class ConfirmSupervisionCredentialsActivity : FragmentActivity() {
private val mAuthenticationCallback =
object : AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
Log.w(TAG, "onAuthenticationError(errorCode=$errorCode, errString=$errString)")
setResult(Activity.RESULT_CANCELED)
finish()
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult?) {
setResult(Activity.RESULT_OK)
finish()
}
override fun onAuthenticationFailed() {
setResult(Activity.RESULT_CANCELED)
finish()
}
}
@RequiresPermission(USE_BIOMETRIC)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// TODO(b/392961554): Check if caller is the SYSTEM_SUPERVISION role holder. Call
// RoleManager#getRoleHolders(SYSTEM_SUPERVISION) and check if getCallingPackage() is in the
// list.
if (checkCallingOrSelfPermission(USE_BIOMETRIC) == PackageManager.PERMISSION_GRANTED) {
showBiometricPrompt()
}
}
@RequiresPermission(USE_BIOMETRIC)
fun showBiometricPrompt() {
// TODO(b/392961554): adapts to new user profile type to trigger PIN verification dialog.
val biometricPrompt =
BiometricPrompt.Builder(this)
.setTitle(getString(R.string.supervision_full_screen_pin_verification_title))
.setConfirmationRequired(true)
.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
.build()
biometricPrompt.authenticate(
CancellationSignal(),
ContextCompat.getMainExecutor(this),
mAuthenticationCallback,
)
}
companion object {
// TODO(b/392961554): remove this tag and use shared tag after http://ag/31997167 is
// submitted.
const val TAG = "SupervisionSettings"
}
}

View File

@@ -56,7 +56,7 @@ class SupervisionDashboardScreen : PreferenceScreenCreator {
override fun getPreferenceHierarchy(context: Context) = override fun getPreferenceHierarchy(context: Context) =
preferenceHierarchy(context, this) { preferenceHierarchy(context, this) {
+SupervisionMainSwitchPreference() +SupervisionMainSwitchPreference(context)
+TitlelessPreferenceGroup(SUPERVISION_DYNAMIC_GROUP_1) += { +TitlelessPreferenceGroup(SUPERVISION_DYNAMIC_GROUP_1) += {
+SupervisionWebContentFiltersScreen.KEY +SupervisionWebContentFiltersScreen.KEY
} }

View File

@@ -15,8 +15,10 @@
*/ */
package com.android.settings.supervision package com.android.settings.supervision
import android.app.Activity
import android.app.supervision.SupervisionManager import android.app.supervision.SupervisionManager
import android.content.Context import android.content.Context
import android.content.Intent
import androidx.preference.Preference import androidx.preference.Preference
import com.android.settings.R import com.android.settings.R
import com.android.settingslib.datastore.KeyValueStore import com.android.settingslib.datastore.KeyValueStore
@@ -32,19 +34,22 @@ import com.android.settingslib.preference.MainSwitchPreferenceBinding
import com.android.settingslib.preference.forEachRecursively import com.android.settingslib.preference.forEachRecursively
/** Main toggle to enable or disable device supervision. */ /** Main toggle to enable or disable device supervision. */
class SupervisionMainSwitchPreference : class SupervisionMainSwitchPreference(context: Context) :
MainSwitchPreference(KEY, R.string.device_supervision_switch_title), MainSwitchPreference(KEY, R.string.device_supervision_switch_title),
PreferenceSummaryProvider, PreferenceSummaryProvider,
MainSwitchPreferenceBinding, MainSwitchPreferenceBinding,
Preference.OnPreferenceChangeListener, Preference.OnPreferenceChangeListener,
PreferenceLifecycleProvider { PreferenceLifecycleProvider {
private val supervisionMainSwitchStorage = SupervisionMainSwitchStorage(context)
private lateinit var lifeCycleContext: PreferenceLifecycleContext
// TODO(b/383568136): Make presence of summary conditional on whether PIN // TODO(b/383568136): Make presence of summary conditional on whether PIN
// has been set up before or not. // has been set up before or not.
override fun getSummary(context: Context): CharSequence? = override fun getSummary(context: Context): CharSequence? =
context.getString(R.string.device_supervision_switch_no_pin_summary) context.getString(R.string.device_supervision_switch_no_pin_summary)
override fun storage(context: Context): KeyValueStore = SupervisionMainSwitchStorage(context) override fun storage(context: Context): KeyValueStore = supervisionMainSwitchStorage
override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) = override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.DISALLOW ReadWritePermit.DISALLOW
@@ -55,26 +60,49 @@ class SupervisionMainSwitchPreference :
override val sensitivityLevel: Int override val sensitivityLevel: Int
get() = SensitivityLevel.HIGH_SENSITIVITY get() = SensitivityLevel.HIGH_SENSITIVITY
override fun onCreate(context: PreferenceLifecycleContext) {
lifeCycleContext = context
}
override fun onResume(context: PreferenceLifecycleContext) {
updateDependentPreferencesEnabledState(
context.findPreference<Preference>(KEY),
supervisionMainSwitchStorage.getBoolean(KEY)!!,
)
}
override fun onActivityResult(
context: PreferenceLifecycleContext,
requestCode: Int,
resultCode: Int,
data: Intent?,
): Boolean {
if (resultCode == Activity.RESULT_OK) {
val mainSwitchPreference =
context.requirePreference<com.android.settingslib.widget.MainSwitchPreference>(KEY)
val newValue = !supervisionMainSwitchStorage.getBoolean(KEY)!!
mainSwitchPreference.setChecked(newValue)
updateDependentPreferencesEnabledState(mainSwitchPreference, newValue)
}
return true
}
override fun bind(preference: Preference, metadata: PreferenceMetadata) { override fun bind(preference: Preference, metadata: PreferenceMetadata) {
super.bind(preference, metadata) super.bind(preference, metadata)
preference.onPreferenceChangeListener = this preference.onPreferenceChangeListener = this
} }
override fun onResume(context: PreferenceLifecycleContext) {
val currentValue = storage(context.applicationContext)?.getBoolean(key) ?: false
updateDependentPreferencesEnabledState(
context.findPreference<Preference>(KEY),
currentValue,
)
}
override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean {
if (newValue !is Boolean) return true if (newValue !is Boolean) return true
updateDependentPreferencesEnabledState(preference, newValue) val intent = Intent(lifeCycleContext, ConfirmSupervisionCredentialsActivity::class.java)
lifeCycleContext.startActivityForResult(
return true intent,
REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
null,
)
return false
} }
private fun updateDependentPreferencesEnabledState( private fun updateDependentPreferencesEnabledState(
@@ -83,9 +111,8 @@ class SupervisionMainSwitchPreference :
) { ) {
preference?.parent?.forEachRecursively { preference?.parent?.forEachRecursively {
if ( if (
it.parent?.key?.toString() == it.parent?.key == SupervisionDashboardScreen.SUPERVISION_DYNAMIC_GROUP_1 ||
SupervisionDashboardScreen.SUPERVISION_DYNAMIC_GROUP_1 || it.key == SupervisionPinManagementScreen.KEY
it.key?.toString() == SupervisionPinManagementScreen.KEY
) { ) {
it.isEnabled = isChecked it.isEnabled = isChecked
} }
@@ -103,7 +130,6 @@ class SupervisionMainSwitchPreference :
as T as T
override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) { override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
// TODO(b/392694561): add PIN protection to main toggle.
if (key == KEY && value is Boolean) { if (key == KEY && value is Boolean) {
val supervisionManager = context.getSystemService(SupervisionManager::class.java) val supervisionManager = context.getSystemService(SupervisionManager::class.java)
supervisionManager?.setSupervisionEnabled(value) supervisionManager?.setSupervisionEnabled(value)
@@ -113,5 +139,6 @@ class SupervisionMainSwitchPreference :
companion object { companion object {
const val KEY = "device_supervision_switch" const val KEY = "device_supervision_switch"
const val REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS = 0
} }
} }

View File

@@ -16,6 +16,8 @@
package com.android.settings.accessibility; package com.android.settings.accessibility;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@@ -76,6 +78,15 @@ public class TextReadingPreviewControllerTest {
mDisplaySizePreference = new AccessibilitySeekBarPreference(mContext, /* attr= */ null); mDisplaySizePreference = new AccessibilitySeekBarPreference(mContext, /* attr= */ null);
} }
@Test
public void numberOfPreviewSamples_numberOfPreviewContentDescription_isEqual() {
int[] previewSamples = TextReadingPreviewController.getPreviewSampleLayouts(mContext);
int[] previewContentDescriptions =
TextReadingPreviewController.getPreviewSampleContentDescriptions(mContext);
assertThat(previewSamples.length).isEqualTo(previewContentDescriptions.length);
}
@Test @Test
public void initPreviewerAdapter_verifyAction() { public void initPreviewerAdapter_verifyAction() {
when(mPreferenceScreen.findPreference(PREVIEW_KEY)).thenReturn(mPreviewPreference); when(mPreferenceScreen.findPreference(PREVIEW_KEY)).thenReturn(mPreviewPreference);

View File

@@ -49,29 +49,48 @@ import org.robolectric.RobolectricTestRunner;
*/ */
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public class TextReadingPreviewPreferenceTest { public class TextReadingPreviewPreferenceTest {
private Context mContext;
private TextReadingPreviewPreference mTextReadingPreviewPreference; private TextReadingPreviewPreference mTextReadingPreviewPreference;
private PreferenceViewHolder mHolder; private PreferenceViewHolder mHolder;
private ViewPager mViewPager; private ViewPager mViewPager;
private PreviewPagerAdapter mPreviewPagerAdapter; private PreviewPagerAdapter mPreviewPagerAdapter;
private int mPreviewSampleCount; private int mPreviewSampleCount;
private int[] mPreviewContentDescriptions;
@Before @Before
public void setUp() { public void setUp() {
final Context context = ApplicationProvider.getApplicationContext(); mContext = ApplicationProvider.getApplicationContext();
final int[] previewSamples = TextReadingPreviewController.getPreviewSampleLayouts(context); mPreviewContentDescriptions =
TextReadingPreviewController.getPreviewSampleContentDescriptions(mContext);
final int[] previewSamples = TextReadingPreviewController.getPreviewSampleLayouts(mContext);
mPreviewSampleCount = previewSamples.length; mPreviewSampleCount = previewSamples.length;
final Configuration[] configurations = createConfigurations(mPreviewSampleCount); final Configuration[] configurations = createConfigurations(mPreviewSampleCount);
mTextReadingPreviewPreference = new TextReadingPreviewPreference(context); mTextReadingPreviewPreference = new TextReadingPreviewPreference(mContext);
mPreviewPagerAdapter = mPreviewPagerAdapter =
spy(new PreviewPagerAdapter(context, /* isLayoutRtl= */ false, spy(new PreviewPagerAdapter(mContext, /* isLayoutRtl= */ false,
previewSamples, configurations)); previewSamples, configurations));
final LayoutInflater inflater = LayoutInflater.from(context); final LayoutInflater inflater = LayoutInflater.from(mContext);
final View view = final View view =
inflater.inflate(mTextReadingPreviewPreference.getLayoutResource(), inflater.inflate(mTextReadingPreviewPreference.getLayoutResource(),
new LinearLayout(context), false); new LinearLayout(mContext), false);
mHolder = PreferenceViewHolder.createInstanceForTests(view); mHolder = PreferenceViewHolder.createInstanceForTests(view);
mViewPager = view.findViewById(R.id.preview_pager); mViewPager = view.findViewById(R.id.preview_pager);
mTextReadingPreviewPreference.setContentDescription(mPreviewContentDescriptions);
}
@Test
public void changePreviewPage_getExpectedContentDescription() {
mTextReadingPreviewPreference.setPreviewAdapter(mPreviewPagerAdapter);
mTextReadingPreviewPreference.onBindViewHolder(mHolder);
// Verify the initial content description
assertThat(mViewPager.getContentDescription().toString())
.isEqualTo(mContext.getString(mPreviewContentDescriptions[0]));
// Change the preview page
mViewPager.setCurrentItem(1);
assertThat(mViewPager.getContentDescription().toString())
.isEqualTo(mContext.getString(mPreviewContentDescriptions[1]));
} }
@Test @Test

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2025 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.accessibility;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.core.BasePreferenceController;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class ToggleAutoclickMainSwitchPreferenceControllerTest {
private static final String PREFERENCE_KEY = "accessibility_autoclick_main_switch";
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private final Context mContext = ApplicationProvider.getApplicationContext();
private ToggleAutoclickMainSwitchPreferenceController mController;
@Before
public void setUp() {
mController = new ToggleAutoclickMainSwitchPreferenceController(mContext, PREFERENCE_KEY);
}
@Test
@EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
public void getAvailabilityStatus_availableWhenFlagOn() {
assertThat(mController.getAvailabilityStatus())
.isEqualTo(BasePreferenceController.AVAILABLE);
}
@Test
public void setChecked_withTrue_shouldUpdateSetting() {
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, OFF);
mController.setChecked(true);
assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, OFF))
.isEqualTo(ON);
}
@Test
public void setChecked_withFalse_shouldUpdateSetting() {
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, ON);
mController.setChecked(false);
assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, OFF))
.isEqualTo(OFF);
}
}

View File

@@ -36,6 +36,9 @@ import android.content.pm.PackageManager;
import android.hardware.face.Face; import android.hardware.face.Face;
import android.hardware.face.FaceManager; import android.hardware.face.FaceManager;
import android.os.UserManager; import android.os.UserManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference; import androidx.preference.Preference;
@@ -50,6 +53,7 @@ import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
@@ -67,6 +71,8 @@ public class FaceStatusPreferenceControllerTest {
private static final String TEST_PREF_KEY = "baz"; private static final String TEST_PREF_KEY = "baz";
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock @Mock
private LockPatternUtils mLockPatternUtils; private LockPatternUtils mLockPatternUtils;
@Mock @Mock
@@ -125,7 +131,8 @@ public class FaceStatusPreferenceControllerTest {
} }
@Test @Test
public void updateState_noFace_shouldShowDefaultSummary() { @DisableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
public void updateState_noFace_flagOff_shouldShowDefaultSummary() {
when(mFaceManager.isHardwareDetected()).thenReturn(true); when(mFaceManager.isHardwareDetected()).thenReturn(true);
mController.updateState(mPreference); mController.updateState(mPreference);
@@ -135,6 +142,18 @@ public class FaceStatusPreferenceControllerTest {
assertThat(mPreference.isVisible()).isTrue(); assertThat(mPreference.isVisible()).isTrue();
} }
@Test
@EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
public void updateState_noFace_flagOn_shouldShowDefaultSummary() {
when(mFaceManager.isHardwareDetected()).thenReturn(true);
mController.updateState(mPreference);
assertThat(mPreference.getSummary()).isEqualTo(
mContext.getString(R.string.security_settings_face_preference_summary_none_new));
assertThat(mPreference.isVisible()).isTrue();
}
@Test @Test
public void updateState_hasFace_shouldShowSummary() { public void updateState_hasFace_shouldShowSummary() {
when(mFaceManager.isHardwareDetected()).thenReturn(true); when(mFaceManager.isHardwareDetected()).thenReturn(true);

View File

@@ -36,6 +36,9 @@ import android.content.pm.PackageManager;
import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager;
import android.os.UserManager; import android.os.UserManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference; import androidx.preference.Preference;
@@ -51,6 +54,7 @@ import com.android.settingslib.utils.StringUtil;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
@@ -66,6 +70,8 @@ import java.util.Collections;
@Config(shadows = {ShadowRestrictedLockUtilsInternal.class}) @Config(shadows = {ShadowRestrictedLockUtilsInternal.class})
public class FingerprintStatusPreferenceControllerTest { public class FingerprintStatusPreferenceControllerTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock @Mock
private LockPatternUtils mLockPatternUtils; private LockPatternUtils mLockPatternUtils;
@Mock @Mock
@@ -125,7 +131,8 @@ public class FingerprintStatusPreferenceControllerTest {
} }
@Test @Test
public void updateState_noFingerprint_shouldShowDefaultSummary() { @DisableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
public void updateState_noFingerprint_flagOff_shouldShowDefaultSummary() {
when(mFingerprintManager.isHardwareDetected()).thenReturn(true); when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
mController.updateState(mPreference); mController.updateState(mPreference);
@@ -135,6 +142,19 @@ public class FingerprintStatusPreferenceControllerTest {
assertThat(mPreference.isVisible()).isTrue(); assertThat(mPreference.isVisible()).isTrue();
} }
@Test
@EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
public void updateState_noFingerprint_flagOn_shouldShowDefaultSummary() {
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
mController.updateState(mPreference);
assertThat(mPreference.getSummary()).isEqualTo(
mContext.getString(
R.string.security_settings_fingerprint_preference_summary_none_new));
assertThat(mPreference.isVisible()).isTrue();
}
@Test @Test
public void updateState_hasFingerprint_shouldShowSummary() { public void updateState_hasFingerprint_shouldShowSummary() {
when(mFingerprintManager.isHardwareDetected()).thenReturn(true); when(mFingerprintManager.isHardwareDetected()).thenReturn(true);

View File

@@ -16,11 +16,8 @@
package com.android.settings.communal; package com.android.settings.communal;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2; import static com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
@@ -66,6 +63,7 @@ public class CommunalPreferenceControllerTest {
} }
@Test @Test
@DisableFlags(FLAG_GLANCEABLE_HUB_V2)
public void isAvailable_communalEnabled_shouldBeTrueForPrimaryUser() { public void isAvailable_communalEnabled_shouldBeTrueForPrimaryUser() {
setCommunalEnabled(true); setCommunalEnabled(true);
mShadowUserManager.setUserForeground(true); mShadowUserManager.setUserForeground(true);
@@ -73,6 +71,7 @@ public class CommunalPreferenceControllerTest {
} }
@Test @Test
@DisableFlags(FLAG_GLANCEABLE_HUB_V2)
public void isAvailable_communalEnabled_shouldBeFalseForSecondaryUser() { public void isAvailable_communalEnabled_shouldBeFalseForSecondaryUser() {
setCommunalEnabled(true); setCommunalEnabled(true);
mShadowUserManager.setUserForeground(false); mShadowUserManager.setUserForeground(false);
@@ -80,6 +79,7 @@ public class CommunalPreferenceControllerTest {
} }
@Test @Test
@DisableFlags(FLAG_GLANCEABLE_HUB_V2)
public void isAvailable_communalDisabled_shouldBeFalseForPrimaryUser() { public void isAvailable_communalDisabled_shouldBeFalseForPrimaryUser() {
setCommunalEnabled(false); setCommunalEnabled(false);
mShadowUserManager.setUserForeground(true); mShadowUserManager.setUserForeground(true);
@@ -88,36 +88,8 @@ public class CommunalPreferenceControllerTest {
@Test @Test
@EnableFlags(FLAG_GLANCEABLE_HUB_V2) @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
public void isAvailable_communalOnMobileEnabled_shouldBeTrueForPrimaryUser() { public void isAvailable_glanceableHubV2Enabled_shouldBeFalseForPrimaryUser() {
setCommunalEnabled(false); setCommunalEnabled(true);
setCommunalOnMobileEnabled(true);
mShadowUserManager.setUserForeground(true);
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
}
@Test
@EnableFlags(FLAG_GLANCEABLE_HUB_V2)
public void isAvailable_communalOnMobileEnabled_shouldBeFalseForSecondaryUser() {
setCommunalEnabled(false);
setCommunalOnMobileEnabled(true);
mShadowUserManager.setUserForeground(false);
assertFalse(mController.isAvailable());
}
@Test
@EnableFlags(FLAG_GLANCEABLE_HUB_V2)
public void isAvailable_communalOnMobileDisabled_shouldBeFalseForPrimaryUser() {
setCommunalEnabled(false);
setCommunalOnMobileEnabled(false);
mShadowUserManager.setUserForeground(true);
assertFalse(mController.isAvailable());
}
@Test
@DisableFlags(FLAG_GLANCEABLE_HUB_V2)
public void isAvailable_glanceableHubV2FlagDisabled_shouldBeFalseForPrimaryUser() {
setCommunalEnabled(false);
setCommunalOnMobileEnabled(true);
mShadowUserManager.setUserForeground(true); mShadowUserManager.setUserForeground(true);
assertFalse(mController.isAvailable()); assertFalse(mController.isAvailable());
} }
@@ -125,9 +97,4 @@ public class CommunalPreferenceControllerTest {
private void setCommunalEnabled(boolean enabled) { private void setCommunalEnabled(boolean enabled) {
SettingsShadowResources.overrideResource(R.bool.config_show_communal_settings, enabled); SettingsShadowResources.overrideResource(R.bool.config_show_communal_settings, enabled);
} }
private void setCommunalOnMobileEnabled(boolean enabled) {
SettingsShadowResources.overrideResource(
R.bool.config_show_communal_settings_mobile, enabled);
}
} }

View File

@@ -45,6 +45,7 @@ import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.BluetoothStatusCodes;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
@@ -79,6 +80,7 @@ import com.android.settings.testutils.shadow.ShadowFragment;
import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.BluetoothEventManager; import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.HeadsetProfile; import com.android.settingslib.bluetooth.HeadsetProfile;
@@ -144,6 +146,7 @@ public class AudioSharingDevicePreferenceControllerTest {
@Mock private LeAudioProfile mLeAudioProfile; @Mock private LeAudioProfile mLeAudioProfile;
@Mock private A2dpProfile mA2dpProfile; @Mock private A2dpProfile mA2dpProfile;
@Mock private HeadsetProfile mHeadsetProfile; @Mock private HeadsetProfile mHeadsetProfile;
@Mock private ContentResolver mContentResolver;
private Context mContext; private Context mContext;
private AudioSharingDevicePreferenceController mController; private AudioSharingDevicePreferenceController mController;
@@ -156,7 +159,7 @@ public class AudioSharingDevicePreferenceControllerTest {
@Before @Before
public void setUp() { public void setUp() {
mContext = ApplicationProvider.getApplicationContext(); mContext = spy(ApplicationProvider.getApplicationContext());
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
mShadowBluetoothAdapter.setEnabled(true); mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
@@ -185,6 +188,7 @@ public class AudioSharingDevicePreferenceControllerTest {
when(mA2dpProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP); when(mA2dpProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP);
when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO); when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO);
when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(true); when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(true);
when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mScreen.getContext()).thenReturn(mContext); when(mScreen.getContext()).thenReturn(mContext);
mPreferenceGroup = spy(new PreferenceCategory(mContext)); mPreferenceGroup = spy(new PreferenceCategory(mContext));
doReturn(mPreferenceManager).when(mPreferenceGroup).getPreferenceManager(); doReturn(mPreferenceManager).when(mPreferenceGroup).getPreferenceManager();
@@ -211,6 +215,12 @@ public class AudioSharingDevicePreferenceControllerTest {
verify(mAssistant, never()) verify(mAssistant, never())
.registerServiceCallBack( .registerServiceCallBack(
any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class)); any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class));
verify(mContentResolver, never())
.registerContentObserver(
Settings.Secure.getUriFor(
BluetoothUtils.getPrimaryGroupIdUriForBroadcast()),
false,
mController.mSettingsObserver);
verify(mBluetoothDeviceUpdater, never()).registerCallback(); verify(mBluetoothDeviceUpdater, never()).registerCallback();
verify(mBluetoothDeviceUpdater, never()).refreshPreference(); verify(mBluetoothDeviceUpdater, never()).refreshPreference();
} }
@@ -224,6 +234,12 @@ public class AudioSharingDevicePreferenceControllerTest {
verify(mAssistant) verify(mAssistant)
.registerServiceCallBack( .registerServiceCallBack(
any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class)); any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class));
verify(mContentResolver)
.registerContentObserver(
Settings.Secure.getUriFor(
BluetoothUtils.getPrimaryGroupIdUriForBroadcast()),
false,
mController.mSettingsObserver);
verify(mBluetoothDeviceUpdater).registerCallback(); verify(mBluetoothDeviceUpdater).registerCallback();
verify(mBluetoothDeviceUpdater).refreshPreference(); verify(mBluetoothDeviceUpdater).refreshPreference();
} }
@@ -236,6 +252,7 @@ public class AudioSharingDevicePreferenceControllerTest {
verify(mDialogHandler, never()).unregisterCallbacks(); verify(mDialogHandler, never()).unregisterCallbacks();
verify(mAssistant, never()) verify(mAssistant, never())
.unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class)); .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
verify(mContentResolver, never()).unregisterContentObserver(mController.mSettingsObserver);
verify(mBluetoothDeviceUpdater, never()).unregisterCallback(); verify(mBluetoothDeviceUpdater, never()).unregisterCallback();
} }
@@ -247,6 +264,7 @@ public class AudioSharingDevicePreferenceControllerTest {
verify(mDialogHandler).unregisterCallbacks(); verify(mDialogHandler).unregisterCallbacks();
verify(mAssistant) verify(mAssistant)
.unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class)); .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
verify(mContentResolver).unregisterContentObserver(mController.mSettingsObserver);
verify(mBluetoothDeviceUpdater).unregisterCallback(); verify(mBluetoothDeviceUpdater).unregisterCallback();
} }
@@ -485,6 +503,12 @@ public class AudioSharingDevicePreferenceControllerTest {
verifyNoInteractions(mDialogHandler); verifyNoInteractions(mDialogHandler);
} }
@Test
public void onFallbackDeviceChanged_updateSummary() {
mController.mSettingsObserver.onChange(true);
verify(mBluetoothDeviceUpdater).refreshPreference();
}
@Test @Test
@EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
public void handleDeviceClickFromIntent_noDevice_doNothing() { public void handleDeviceClickFromIntent_noDevice_doNothing() {

View File

@@ -26,6 +26,9 @@ import static com.android.settingslib.flags.Flags.FLAG_ENABLE_LE_AUDIO_SHARING;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
@@ -39,6 +42,7 @@ import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.BluetoothStatusCodes;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule; import android.platform.test.flag.junit.SetFlagsRule;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
@@ -52,6 +56,7 @@ import com.android.settings.testutils.shadow.ShadowThreadUtils;
import com.android.settings.widget.EntityHeaderController; import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.flags.Flags;
import com.android.settingslib.widget.LayoutPreference; import com.android.settingslib.widget.LayoutPreference;
import org.junit.After; import org.junit.After;
@@ -254,6 +259,7 @@ public class AudioStreamHeaderControllerTest {
} }
@Test @Test
@EnableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE)
public void testCallback_onReceiveStateChanged_updateButton() { public void testCallback_onReceiveStateChanged_updateButton() {
when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean())) when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
.thenReturn(Map.of(BROADCAST_ID, STREAMING)); .thenReturn(Map.of(BROADCAST_ID, STREAMING));
@@ -271,6 +277,7 @@ public class AudioStreamHeaderControllerTest {
verify(mHeaderController, times(2)) verify(mHeaderController, times(2))
.setSummary(mContext.getString(AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY)); .setSummary(mContext.getString(AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY));
verify(mHeaderController, times(2)).done(true); verify(mHeaderController, times(2)).done(true);
verify(mAudioStreamsHelper, never()).startMediaService(any(), anyInt(), anyString());
} }
@Test @Test

View File

@@ -19,6 +19,10 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.BROADCAST_ID; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.BROADCAST_ID;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.DEVICES; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.DEVICES;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.LEAVE_BROADCAST_ACTION; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.LEAVE_BROADCAST_ACTION;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_PRIVATE_BROADCAST_RECEIVE_DATA;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
@@ -57,6 +61,7 @@ import android.os.Looper;
import android.os.RemoteException; import android.os.RemoteException;
import android.platform.test.flag.junit.SetFlagsRule; import android.platform.test.flag.junit.SetFlagsRule;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.view.KeyEvent;
import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper; import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper;
import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.FakeFeatureFactory;
@@ -69,6 +74,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.bluetooth.PrivateBroadcastReceiveData;
import com.android.settingslib.bluetooth.VolumeControlProfile; import com.android.settingslib.bluetooth.VolumeControlProfile;
import com.android.settingslib.flags.Flags; import com.android.settingslib.flags.Flags;
@@ -116,6 +122,8 @@ public class AudioStreamMediaServiceTest {
@Mock private VolumeControlProfile mVolumeControlProfile; @Mock private VolumeControlProfile mVolumeControlProfile;
@Mock private CachedBluetoothDevice mCachedBluetoothDevice; @Mock private CachedBluetoothDevice mCachedBluetoothDevice;
@Mock private BluetoothDevice mDevice; @Mock private BluetoothDevice mDevice;
@Mock
private BluetoothDevice mDevice2;
@Mock private ISession mISession; @Mock private ISession mISession;
@Mock private ISessionController mISessionController; @Mock private ISessionController mISessionController;
@Mock private PackageManager mPackageManager; @Mock private PackageManager mPackageManager;
@@ -240,6 +248,7 @@ public class AudioStreamMediaServiceTest {
@Test @Test
public void onDestroy_flagOn_cleanup() { public void onDestroy_flagOn_cleanup() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
var devices = new ArrayList<BluetoothDevice>(); var devices = new ArrayList<BluetoothDevice>();
devices.add(mDevice); devices.add(mDevice);
@@ -256,8 +265,33 @@ public class AudioStreamMediaServiceTest {
verify(mVolumeControlProfile).unregisterCallback(any()); verify(mVolumeControlProfile).unregisterCallback(any());
} }
@Test
public void byReceiveStateFlagOn_onDestroy_flagOn_cleanup() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING);
mAudioStreamMediaService.onCreate();
mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
mAudioStreamMediaService.onDestroy();
verify(mBluetoothEventManager).unregisterCallback(any());
verify(mLeBroadcastAssistant).unregisterServiceCallBack(any());
verify(mVolumeControlProfile).unregisterCallback(any());
}
@Test
public void byReceiveStateFlagOn_onStartCommand_invalidData_stopSelf() {
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
Intent intent = setupReceiveDataIntent(-1, mDevice, STREAMING);
mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
verify(mAudioStreamMediaService).stopSelf();
}
@Test @Test
public void onStartCommand_noBroadcastId_stopSelf() { public void onStartCommand_noBroadcastId_stopSelf() {
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onStartCommand(new Intent(), /* flags= */ 0, /* startId= */ 0); mAudioStreamMediaService.onStartCommand(new Intent(), /* flags= */ 0, /* startId= */ 0);
verify(mAudioStreamMediaService).stopSelf(); verify(mAudioStreamMediaService).stopSelf();
@@ -265,6 +299,7 @@ public class AudioStreamMediaServiceTest {
@Test @Test
public void onStartCommand_noDevice_stopSelf() { public void onStartCommand_noDevice_stopSelf() {
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
Intent intent = new Intent(); Intent intent = new Intent();
intent.putExtra(BROADCAST_ID, 1); intent.putExtra(BROADCAST_ID, 1);
@@ -273,8 +308,110 @@ public class AudioStreamMediaServiceTest {
verify(mAudioStreamMediaService).stopSelf(); verify(mAudioStreamMediaService).stopSelf();
} }
@Test
public void byReceiveStateFlagOn_onStartCommand_createSessionAndStartForeground() {
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING);
mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
ArgumentCaptor<Notification> notificationCapture = ArgumentCaptor.forClass(
Notification.class);
verify(mAudioStreamMediaService).startForeground(anyInt(), notificationCapture.capture());
var notification = notificationCapture.getValue();
assertThat(notification.getSmallIcon()).isNotNull();
assertThat(notification.isStyle(Notification.MediaStyle.class)).isTrue();
verify(mAudioStreamMediaService, never()).stopSelf();
}
@Test
public void byReceiveStateFlagOn_onStartCommand_decryptionFailed_stopSelf() {
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
Intent intent = setupReceiveDataIntent(1, mDevice, DECRYPTION_FAILED);
mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
verify(mAudioStreamMediaService).stopSelf();
}
@Test
public void byReceiveStateFlagOn_onStartCommand_addDevice() {
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate();
Intent intent1 = setupReceiveDataIntent(1, mDevice, STREAMING);
mAudioStreamMediaService.onStartCommand(intent1, /* flags= */ 0, /* startId= */ 0);
Intent intent2 = setupReceiveDataIntent(1, mDevice2, PAUSED);
mAudioStreamMediaService.onStartCommand(intent2, /* flags= */ 0, /* startId= */ 0);
ArgumentCaptor<Notification> notificationCapture = ArgumentCaptor.forClass(
Notification.class);
verify(mAudioStreamMediaService).startForeground(anyInt(), notificationCapture.capture());
var notification = notificationCapture.getValue();
assertThat(notification.getSmallIcon()).isNotNull();
assertThat(notification.isStyle(Notification.MediaStyle.class)).isTrue();
assertThat(mAudioStreamMediaService.mStateByDevice).isNotNull();
var deviceState = mAudioStreamMediaService.mStateByDevice.get(mDevice);
assertThat(deviceState).isNotNull();
assertThat(deviceState).isEqualTo(STREAMING);
var device2State = mAudioStreamMediaService.mStateByDevice.get(mDevice2);
assertThat(device2State).isNotNull();
assertThat(device2State).isEqualTo(PAUSED);
verify(mAudioStreamMediaService, never()).stopSelf();
verify(mNotificationManager).notify(anyInt(), any());
}
@Test
public void byReceiveStateFlagOn_onStartCommand_updateState() {
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate();
Intent intent1 = setupReceiveDataIntent(1, mDevice, STREAMING);
mAudioStreamMediaService.onStartCommand(intent1, /* flags= */ 0, /* startId= */ 0);
Intent intent2 = setupReceiveDataIntent(1, mDevice, PAUSED);
mAudioStreamMediaService.onStartCommand(intent2, /* flags= */ 0, /* startId= */ 0);
ArgumentCaptor<Notification> notificationCapture = ArgumentCaptor.forClass(
Notification.class);
verify(mAudioStreamMediaService).startForeground(anyInt(), notificationCapture.capture());
var notification = notificationCapture.getValue();
assertThat(notification.getSmallIcon()).isNotNull();
assertThat(notification.isStyle(Notification.MediaStyle.class)).isTrue();
assertThat(mAudioStreamMediaService.mStateByDevice).isNotNull();
var deviceState = mAudioStreamMediaService.mStateByDevice.get(mDevice);
assertThat(deviceState).isNotNull();
assertThat(deviceState).isEqualTo(PAUSED);
verify(mAudioStreamMediaService, never()).stopSelf();
verify(mNotificationManager).notify(anyInt(), any());
}
@Test
public void byReceiveStateFlagOn_onStartCommand_newBroadcastId() {
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate();
Intent intent1 = setupReceiveDataIntent(1, mDevice, STREAMING);
mAudioStreamMediaService.onStartCommand(intent1, /* flags= */ 0, /* startId= */ 0);
Intent intent2 = setupReceiveDataIntent(2, mDevice2, PAUSED);
mAudioStreamMediaService.onStartCommand(intent2, /* flags= */ 0, /* startId= */ 0);
ArgumentCaptor<Notification> notificationCapture = ArgumentCaptor.forClass(
Notification.class);
verify(mAudioStreamMediaService).startForeground(anyInt(), notificationCapture.capture());
var notification = notificationCapture.getValue();
assertThat(notification.getSmallIcon()).isNotNull();
assertThat(notification.isStyle(Notification.MediaStyle.class)).isTrue();
assertThat(mAudioStreamMediaService.mStateByDevice).isNotNull();
var oldDeviceState = mAudioStreamMediaService.mStateByDevice.get(mDevice);
assertThat(oldDeviceState).isNull();
var newDeviceState = mAudioStreamMediaService.mStateByDevice.get(mDevice2);
assertThat(newDeviceState).isEqualTo(PAUSED);
verify(mAudioStreamMediaService, never()).stopSelf();
verify(mNotificationManager).notify(anyInt(), any());
}
@Test @Test
public void onStartCommand_createSessionAndStartForeground() { public void onStartCommand_createSessionAndStartForeground() {
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
var devices = new ArrayList<BluetoothDevice>(); var devices = new ArrayList<BluetoothDevice>();
devices.add(mDevice); devices.add(mDevice);
@@ -317,6 +454,7 @@ public class AudioStreamMediaServiceTest {
@Test @Test
public void assistantCallback_onReceiveStateChanged_connected_doNothing() { public void assistantCallback_onReceiveStateChanged_connected_doNothing() {
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
@@ -338,6 +476,7 @@ public class AudioStreamMediaServiceTest {
@Test @Test
public void assistantCallback_onReceiveStateChanged_hysteresis_updateNotification() { public void assistantCallback_onReceiveStateChanged_hysteresis_updateNotification() {
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
@@ -357,6 +496,7 @@ public class AudioStreamMediaServiceTest {
@Test @Test
public void assistantCallback_onReceiveStateChanged_hysteresis_flagOff_doNothing() { public void assistantCallback_onReceiveStateChanged_hysteresis_flagOff_doNothing() {
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
@@ -386,6 +526,7 @@ public class AudioStreamMediaServiceTest {
@Test @Test
public void bluetoothCallback_onDeviceDisconnect_stopSelf() { public void bluetoothCallback_onDeviceDisconnect_stopSelf() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate(); mAudioStreamMediaService.onCreate();
assertThat(mAudioStreamMediaService.mBluetoothCallback).isNotNull(); assertThat(mAudioStreamMediaService.mBluetoothCallback).isNotNull();
mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0); mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
@@ -398,9 +539,26 @@ public class AudioStreamMediaServiceTest {
verify(mAudioStreamMediaService).stopSelf(); verify(mAudioStreamMediaService).stopSelf();
} }
@Test
public void byReceiveStateFlagOn_bluetoothCallback_onDeviceDisconnect_stopSelf() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate();
assertThat(mAudioStreamMediaService.mBluetoothCallback).isNotNull();
Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING);
mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
mAudioStreamMediaService.mBluetoothCallback.onProfileConnectionStateChanged(
mCachedBluetoothDevice, BluetoothAdapter.STATE_DISCONNECTED,
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
verify(mAudioStreamMediaService).stopSelf();
}
@Test @Test
public void mediaSessionCallback_onPause_setVolume() { public void mediaSessionCallback_onPause_setVolume() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate(); mAudioStreamMediaService.onCreate();
mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0); mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
@@ -415,9 +573,26 @@ public class AudioStreamMediaServiceTest {
eq(1)); eq(1));
} }
@Test
public void byReceiveStateFlagOn_mediaSessionCallback_onPause_setVolume() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate();
Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING);
mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull();
mAudioStreamMediaService.mMediaSessionCallback.onPause();
verify(mVolumeControlProfile).setDeviceVolume(any(), anyInt(), anyBoolean());
verify(mFeatureFactory.metricsFeatureProvider).action(any(),
eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_MUTE_BUTTON_CLICK), eq(1));
}
@Test @Test
public void mediaSessionCallback_onPlay_setVolume() { public void mediaSessionCallback_onPlay_setVolume() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate(); mAudioStreamMediaService.onCreate();
mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0); mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
@@ -432,9 +607,66 @@ public class AudioStreamMediaServiceTest {
eq(0)); eq(0));
} }
@Test
public void byReceiveStateFlagOn_mediaSessionCallback_onPlay_setVolume() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate();
Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING);
mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull();
mAudioStreamMediaService.mMediaSessionCallback.onPlay();
verify(mVolumeControlProfile).setDeviceVolume(any(), anyInt(), anyBoolean());
verify(mFeatureFactory.metricsFeatureProvider).action(any(),
eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_MUTE_BUTTON_CLICK), eq(0));
}
@Test
public void byReceiveStateFlagOn_mediaSessionCallback_onButtonEventPlay_setVolume() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate();
Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING);
mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull();
Intent buttonEvent = new Intent();
buttonEvent.putExtra(Intent.EXTRA_KEY_EVENT,
new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY));
mAudioStreamMediaService.mMediaSessionCallback.onMediaButtonEvent(buttonEvent);
verify(mVolumeControlProfile).setDeviceVolume(any(), anyInt(), anyBoolean());
verify(mFeatureFactory.metricsFeatureProvider).action(any(),
eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_MUTE_BUTTON_CLICK), eq(0));
}
@Test
public void byReceiveStateFlagOn_mediaSessionCallback_onButtonEventPause_setVolume() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate();
Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING);
mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull();
Intent buttonEvent = new Intent();
buttonEvent.putExtra(Intent.EXTRA_KEY_EVENT,
new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PAUSE));
mAudioStreamMediaService.mMediaSessionCallback.onMediaButtonEvent(buttonEvent);
verify(mVolumeControlProfile).setDeviceVolume(any(), anyInt(), anyBoolean());
verify(mFeatureFactory.metricsFeatureProvider).action(any(),
eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_MUTE_BUTTON_CLICK), eq(1));
}
@Test @Test
public void mediaSessionCallback_onCustomAction_leaveBroadcast() { public void mediaSessionCallback_onCustomAction_leaveBroadcast() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate(); mAudioStreamMediaService.onCreate();
mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0); mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
@@ -449,6 +681,23 @@ public class AudioStreamMediaServiceTest {
eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_LEAVE_BUTTON_CLICK)); eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_LEAVE_BUTTON_CLICK));
} }
@Test
public void byReceiveStateFlagOn_mediaSessionCallback_onCustomAction_leaveBroadcast() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate();
Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING);
mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull();
mAudioStreamMediaService.mMediaSessionCallback.onCustomAction(LEAVE_BROADCAST_ACTION,
Bundle.EMPTY);
verify(mAudioStreamsHelper).removeSource(anyInt());
verify(mFeatureFactory.metricsFeatureProvider).action(any(),
eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_LEAVE_BUTTON_CLICK));
}
@Test @Test
public void onBind_returnNull() { public void onBind_returnNull() {
IBinder binder = mAudioStreamMediaService.onBind(new Intent()); IBinder binder = mAudioStreamMediaService.onBind(new Intent());
@@ -466,4 +715,13 @@ public class AudioStreamMediaServiceTest {
intent.putParcelableArrayListExtra(DEVICES, devices); intent.putParcelableArrayListExtra(DEVICES, devices);
return intent; return intent;
} }
private Intent setupReceiveDataIntent(int broadcastId, BluetoothDevice device,
LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState state) {
when(mCachedBluetoothDevice.getDevice()).thenReturn(mDevice);
Intent intent = new Intent();
intent.putExtra(EXTRA_PRIVATE_BROADCAST_RECEIVE_DATA,
new PrivateBroadcastReceiveData(device, 1, broadcastId, "programInfo", state));
return intent;
}
} }

View File

@@ -22,9 +22,12 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Sou
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -33,6 +36,9 @@ import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference; import androidx.preference.Preference;
@@ -43,6 +49,7 @@ import com.android.settings.SettingsActivity;
import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowFragment; import com.android.settings.testutils.shadow.ShadowFragment;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.flags.Flags;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
@@ -62,6 +69,7 @@ import org.robolectric.annotation.Config;
}) })
public class SourceAddedStateTest { public class SourceAddedStateTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final int BROADCAST_ID = 1; private static final int BROADCAST_ID = 1;
private static final String BROADCAST_TITLE = "title"; private static final String BROADCAST_TITLE = "title";
private final Context mContext = ApplicationProvider.getApplicationContext(); private final Context mContext = ApplicationProvider.getApplicationContext();
@@ -105,7 +113,8 @@ public class SourceAddedStateTest {
} }
@Test @Test
public void testPerformAction() { @DisableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE)
public void testPerformAction_startService() {
mInstance.setAudioStreamsRepositoryForTesting(mRepository); mInstance.setAudioStreamsRepositoryForTesting(mRepository);
BluetoothLeBroadcastMetadata mockMetadata = mock(BluetoothLeBroadcastMetadata.class); BluetoothLeBroadcastMetadata mockMetadata = mock(BluetoothLeBroadcastMetadata.class);
when(mRepository.getCachedMetadata(anyInt())).thenReturn(mockMetadata); when(mRepository.getCachedMetadata(anyInt())).thenReturn(mockMetadata);
@@ -124,6 +133,27 @@ public class SourceAddedStateTest {
verify(mHelper).startMediaService(eq(mContext), eq(BROADCAST_ID), eq(BROADCAST_TITLE)); verify(mHelper).startMediaService(eq(mContext), eq(BROADCAST_ID), eq(BROADCAST_TITLE));
} }
@Test
@EnableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE)
public void testPerformAction_skipStartService() {
mInstance.setAudioStreamsRepositoryForTesting(mRepository);
BluetoothLeBroadcastMetadata mockMetadata = mock(BluetoothLeBroadcastMetadata.class);
when(mRepository.getCachedMetadata(anyInt())).thenReturn(mockMetadata);
when(mPreference.getContext()).thenReturn(mContext);
when(mPreference.getSourceOriginForLogging())
.thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS);
mInstance.performAction(mPreference, mController, mHelper);
verify(mRepository).saveMetadata(eq(mContext), eq(mockMetadata));
verify(mFeatureFactory.metricsFeatureProvider)
.action(
eq(mContext),
eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED),
eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal()));
verify(mHelper, never()).startMediaService(any(), anyInt(), anyString());
}
@Test @Test
public void testGetOnClickListener_startSubSettings() { public void testGetOnClickListener_startSubSettings() {
when(mController.getFragment()).thenReturn(mFragment); when(mController.getFragment()).thenReturn(mFragment);

View File

@@ -18,22 +18,30 @@ package com.android.settings.network.ethernet
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.kotlin.mock
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class EthernetInterfaceDetailsControllerTest { class EthernetInterfaceDetailsControllerTest {
private val ethernetInterfaceDetailsFragment = EthernetInterfaceDetailsFragment() private val ethernetInterfaceDetailsFragment = EthernetInterfaceDetailsFragment()
private val lifecycle = mock<Lifecycle>()
private val context: Context = private val context: Context =
object : ContextWrapper(ApplicationProvider.getApplicationContext()) {} object : ContextWrapper(ApplicationProvider.getApplicationContext()) {}
private val ethernetInterfaceDetailsController = private val ethernetInterfaceDetailsController =
EthernetInterfaceDetailsController(context, ethernetInterfaceDetailsFragment, "eth0") EthernetInterfaceDetailsController(
context,
ethernetInterfaceDetailsFragment,
"eth0",
lifecycle,
)
@Test @Test
fun isAvailable_ShouldReturnTrue() { fun isAvailable_ShouldReturnTrue() {

View File

@@ -18,14 +18,19 @@ package com.android.settings.network.ethernet
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.net.ConnectivityManager
import android.net.EthernetManager import android.net.EthernetManager
import android.net.EthernetManager.STATE_ABSENT import android.net.EthernetManager.STATE_ABSENT
import android.net.EthernetManager.STATE_LINK_DOWN import android.net.EthernetManager.STATE_LINK_DOWN
import android.net.EthernetManager.STATE_LINK_UP import android.net.EthernetManager.STATE_LINK_UP
import android.net.IpConfiguration import android.net.IpConfiguration
import android.net.LinkProperties
import android.net.Network
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.kotlin.mock import org.mockito.kotlin.mock
@@ -34,12 +39,15 @@ import org.mockito.kotlin.mock
class EthernetInterfaceTest { class EthernetInterfaceTest {
private val mockEthernetManager = mock<EthernetManager>() private val mockEthernetManager = mock<EthernetManager>()
private val mockConnectivityManager = mock<ConnectivityManager>()
private val mockNetwork = mock<Network>()
private val context: Context = private val context: Context =
object : ContextWrapper(ApplicationProvider.getApplicationContext()) { object : ContextWrapper(ApplicationProvider.getApplicationContext()) {
override fun getSystemService(name: String): Any? = override fun getSystemService(name: String): Any? =
when (name) { when (name) {
Context.ETHERNET_SERVICE -> mockEthernetManager Context.ETHERNET_SERVICE -> mockEthernetManager
Context.CONNECTIVITY_SERVICE -> mockConnectivityManager
else -> super.getSystemService(name) else -> super.getSystemService(name)
} }
} }
@@ -85,4 +93,27 @@ class EthernetInterfaceTest {
IpConfiguration.IpAssignment.UNASSIGNED, IpConfiguration.IpAssignment.UNASSIGNED,
) )
} }
@Test
fun linkPropertiesChanged_shouldUpdate() {
val linkProperties = LinkProperties()
linkProperties.setInterfaceName("eth0")
linkProperties.setUsePrivateDns(true)
ethernetInterface.networkCallback.onLinkPropertiesChanged(mockNetwork, linkProperties)
assertEquals(ethernetInterface.getLinkProperties().getInterfaceName(), "eth0")
assertTrue(ethernetInterface.getLinkProperties().isPrivateDnsActive())
}
@Test
fun linkPropertiesChanged_iddoesnotmatch_shouldNotUpdate() {
val linkProperties = LinkProperties()
linkProperties.setInterfaceName("eth1")
linkProperties.setUsePrivateDns(true)
ethernetInterface.networkCallback.onLinkPropertiesChanged(mockNetwork, linkProperties)
assertFalse(ethernetInterface.getLinkProperties().isPrivateDnsActive())
}
} }

View File

@@ -15,6 +15,7 @@
*/ */
package com.android.settings.supervision package com.android.settings.supervision
import android.app.Activity
import android.app.supervision.flags.Flags import android.app.supervision.flags.Flags
import android.content.Context import android.content.Context
import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.DisableFlags
@@ -24,6 +25,7 @@ import androidx.fragment.app.testing.FragmentScenario
import androidx.preference.Preference import androidx.preference.Preference
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.supervision.SupervisionMainSwitchPreference.Companion.REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS
import com.android.settingslib.widget.MainSwitchPreference import com.android.settingslib.widget.MainSwitchPreference
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.Rule import org.junit.Rule
@@ -57,7 +59,7 @@ class SupervisionDashboardScreenTest {
@Test @Test
@EnableFlags(Flags.FLAG_ENABLE_SUPERVISION_SETTINGS_SCREEN) @EnableFlags(Flags.FLAG_ENABLE_SUPERVISION_SETTINGS_SCREEN)
fun toggleMainSwitch_disablesChildPreferences() { fun toggleMainSwitch_pinVerificationSucceeded_enablesChildPreferences() {
FragmentScenario.launchInContainer(preferenceScreenCreator.fragmentClass()).onFragment { FragmentScenario.launchInContainer(preferenceScreenCreator.fragmentClass()).onFragment {
fragment -> fragment ->
val mainSwitchPreference = val mainSwitchPreference =
@@ -68,8 +70,38 @@ class SupervisionDashboardScreenTest {
assertThat(childPreference.isEnabled).isFalse() assertThat(childPreference.isEnabled).isFalse()
mainSwitchPreference.performClick() mainSwitchPreference.performClick()
// Pretend the PIN verification succeeded.
fragment.onActivityResult(
requestCode = REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
resultCode = Activity.RESULT_OK,
data = null,
)
assertThat(childPreference.isEnabled).isTrue() assertThat(childPreference.isEnabled).isTrue()
} }
} }
@Test
@EnableFlags(Flags.FLAG_ENABLE_SUPERVISION_SETTINGS_SCREEN)
fun toggleMainSwitch_pinVerificationFailed_childPreferencesRemainDisabled() {
FragmentScenario.launchInContainer(preferenceScreenCreator.fragmentClass()).onFragment {
fragment ->
val mainSwitchPreference =
fragment.findPreference<MainSwitchPreference>(SupervisionMainSwitchPreference.KEY)!!
val childPreference =
fragment.findPreference<Preference>(SupervisionPinManagementScreen.KEY)!!
assertThat(childPreference.isEnabled).isFalse()
mainSwitchPreference.performClick()
// Pretend the PIN verification failed.
fragment.onActivityResult(
requestCode = REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
resultCode = Activity.RESULT_CANCELED,
data = null,
)
assertThat(childPreference.isEnabled).isFalse()
}
}
} }

View File

@@ -15,25 +15,33 @@
*/ */
package com.android.settings.supervision package com.android.settings.supervision
import android.app.Activity
import android.app.supervision.SupervisionManager import android.app.supervision.SupervisionManager
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.content.Intent
import androidx.preference.Preference
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.supervision.SupervisionMainSwitchPreference.Companion.REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS
import com.android.settingslib.metadata.PreferenceLifecycleContext
import com.android.settingslib.preference.createAndBindWidget import com.android.settingslib.preference.createAndBindWidget
import com.android.settingslib.widget.MainSwitchPreference import com.android.settingslib.widget.MainSwitchPreference
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doReturn import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.stub import org.mockito.kotlin.stub
import org.mockito.kotlin.verify import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class SupervisionMainSwitchPreferenceTest { class SupervisionMainSwitchPreferenceTest {
private val preference = SupervisionMainSwitchPreference() private val mockLifeCycleContext = mock<PreferenceLifecycleContext>()
private val mockSupervisionManager = mock<SupervisionManager>() private val mockSupervisionManager = mock<SupervisionManager>()
private val appContext: Context = ApplicationProvider.getApplicationContext() private val appContext: Context = ApplicationProvider.getApplicationContext()
@@ -46,6 +54,13 @@ class SupervisionMainSwitchPreferenceTest {
} }
} }
private val preference = SupervisionMainSwitchPreference(context)
@Before
fun setUp() {
preference.onCreate(mockLifeCycleContext)
}
@Test @Test
fun checked_supervisionEnabled_returnTrue() { fun checked_supervisionEnabled_returnTrue() {
setSupervisionEnabled(true) setSupervisionEnabled(true)
@@ -61,7 +76,7 @@ class SupervisionMainSwitchPreferenceTest {
} }
@Test @Test
fun toggleOn() { fun toggleOn_triggersPinVerification() {
setSupervisionEnabled(false) setSupervisionEnabled(false)
val widget = getMainSwitchPreference() val widget = getMainSwitchPreference()
@@ -69,26 +84,90 @@ class SupervisionMainSwitchPreferenceTest {
widget.performClick() widget.performClick()
verifyConfirmSupervisionCredentialsActivityStarted()
assertThat(widget.isChecked).isFalse()
verify(mockSupervisionManager, never()).setSupervisionEnabled(false)
}
@Test
fun toggleOn_pinVerificationSucceeded_supervisionEnabled() {
setSupervisionEnabled(false)
val widget = getMainSwitchPreference()
assertThat(widget.isChecked).isFalse()
preference.onActivityResult(
mockLifeCycleContext,
REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
Activity.RESULT_OK,
null,
)
assertThat(widget.isChecked).isTrue() assertThat(widget.isChecked).isTrue()
verify(mockSupervisionManager).setSupervisionEnabled(true) verify(mockSupervisionManager).setSupervisionEnabled(true)
} }
@Test @Test
fun toggleOff() { fun toggleOff_pinVerificationSucceeded_supervisionDisabled() {
setSupervisionEnabled(true) setSupervisionEnabled(true)
val widget = getMainSwitchPreference() val widget = getMainSwitchPreference()
assertThat(widget.isChecked).isTrue() assertThat(widget.isChecked).isTrue()
widget.performClick() preference.onActivityResult(
mockLifeCycleContext,
REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
Activity.RESULT_OK,
null,
)
assertThat(widget.isChecked).isFalse() assertThat(widget.isChecked).isFalse()
verify(mockSupervisionManager).setSupervisionEnabled(false) verify(mockSupervisionManager).setSupervisionEnabled(false)
} }
private fun getMainSwitchPreference(): MainSwitchPreference = @Test
preference.createAndBindWidget(context) fun toggleOff_pinVerificationFailed_supervisionNotEnabled() {
setSupervisionEnabled(true)
val widget = getMainSwitchPreference()
assertThat(widget.isChecked).isTrue()
preference.onActivityResult(
mockLifeCycleContext,
REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
Activity.RESULT_CANCELED,
null,
)
assertThat(widget.isChecked).isTrue()
verify(mockSupervisionManager, never()).setSupervisionEnabled(true)
}
private fun setSupervisionEnabled(enabled: Boolean) = private fun setSupervisionEnabled(enabled: Boolean) =
mockSupervisionManager.stub { on { isSupervisionEnabled } doReturn enabled } mockSupervisionManager.stub { on { isSupervisionEnabled } doReturn enabled }
private fun getMainSwitchPreference(): MainSwitchPreference {
val widget: MainSwitchPreference = preference.createAndBindWidget(context)
mockLifeCycleContext.stub {
on { findPreference<Preference>(SupervisionMainSwitchPreference.KEY) } doReturn widget
on {
requirePreference<MainSwitchPreference>(SupervisionMainSwitchPreference.KEY)
} doReturn widget
}
return widget
}
private fun verifyConfirmSupervisionCredentialsActivityStarted() {
val intentCaptor = argumentCaptor<Intent>()
verify(mockLifeCycleContext)
.startActivityForResult(
intentCaptor.capture(),
eq(REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS),
eq(null),
)
assertThat(intentCaptor.allValues.size).isEqualTo(1)
assertThat(intentCaptor.firstValue.component?.className)
.isEqualTo(ConfirmSupervisionCredentialsActivity::class.java.name)
}
} }

View File

@@ -0,0 +1,5 @@
# Bug component: 970984
# Large Screen Experiences App Compat
gracielawputri@google.com
mcarli@google.com
mariiasand@google.com

View File

@@ -0,0 +1,5 @@
# Bug component: 970984
# Large Screen Experiences App Compat
gracielawputri@google.com
mcarli@google.com
mariiasand@google.com

View File

@@ -27,13 +27,12 @@ import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.app.supervision.SupervisionManager; import android.app.supervision.SupervisionManager;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricAuthenticator;
import android.os.UserHandle; import android.os.UserHandle;
@@ -44,6 +43,7 @@ import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils;
@@ -53,22 +53,29 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class ParentalControlsUtilsTest { public class ParentalControlsUtilsTest {
@Rule public final CheckFlagsRule checkFlags = DeviceFlagsValueProvider.createCheckFlagsRule(); @Rule
public final CheckFlagsRule checkFlags = DeviceFlagsValueProvider.createCheckFlagsRule();
@Rule
public final MockitoRule mocks = MockitoJUnit.rule();
@Mock private Context mContext; private Context mContext;
@Mock private DevicePolicyManager mDpm; @Mock
@Mock private SupervisionManager mSm; private DevicePolicyManager mDpm;
@Mock
private SupervisionManager mSm;
private ComponentName mSupervisionComponentName = new ComponentName("pkg", "cls"); private final ComponentName mSupervisionComponent = new ComponentName("pkg", "cls");
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); mContext = spy(ApplicationProvider.getApplicationContext());
when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class)); when(mContext.getSystemService(DevicePolicyManager.class)).thenReturn(mDpm);
when(mContext.getSystemService(SupervisionManager.class)).thenReturn(mSm);
} }
/** /**
@@ -85,7 +92,7 @@ public class ParentalControlsUtilsTest {
.thenReturn(keyguardDisabledFlags); .thenReturn(keyguardDisabledFlags);
return ParentalControlsUtils.parentConsentRequiredInternal( return ParentalControlsUtils.parentConsentRequiredInternal(
mDpm, mSm, modality, new UserHandle(UserHandle.myUserId())); mContext, modality, new UserHandle(UserHandle.myUserId()));
} }
/** /**
@@ -97,11 +104,13 @@ public class ParentalControlsUtilsTest {
boolean supervisionEnabled, boolean supervisionEnabled,
@BiometricAuthenticator.Modality int modality, @BiometricAuthenticator.Modality int modality,
int keyguardDisabledFlags) { int keyguardDisabledFlags) {
when(mSm.isSupervisionEnabledForUser(anyInt())).thenReturn(supervisionEnabled);
when(mDpm.getKeyguardDisabledFeatures(eq(null))).thenReturn(keyguardDisabledFlags); when(mDpm.getKeyguardDisabledFeatures(eq(null))).thenReturn(keyguardDisabledFlags);
when(mSm.isSupervisionEnabledForUser(anyInt())).thenReturn(supervisionEnabled);
when(mSm.getActiveSupervisionAppPackage()).thenReturn(
supervisionEnabled ? mSupervisionComponent.getPackageName() : null);
return ParentalControlsUtils.parentConsentRequiredInternal( return ParentalControlsUtils.parentConsentRequiredInternal(
mDpm, mSm, modality, new UserHandle(UserHandle.myUserId())); mContext, modality, new UserHandle(UserHandle.myUserId()));
} }
@Test @Test
@@ -115,11 +124,11 @@ public class ParentalControlsUtilsTest {
for (int i = 0; i < tests.length; i++) { for (int i = 0; i < tests.length; i++) {
RestrictedLockUtils.EnforcedAdmin admin = getEnforcedAdminForCombination( RestrictedLockUtils.EnforcedAdmin admin = getEnforcedAdminForCombination(
mSupervisionComponentName, tests[i][0] /* modality */, mSupervisionComponent, tests[i][0] /* modality */,
tests[i][1] /* keyguardDisableFlags */); tests[i][1] /* keyguardDisableFlags */);
assertNotNull(admin); assertNotNull(admin);
assertEquals(UserManager.DISALLOW_BIOMETRIC, admin.enforcedRestriction); assertEquals(UserManager.DISALLOW_BIOMETRIC, admin.enforcedRestriction);
assertEquals(mSupervisionComponentName, admin.component); assertEquals(mSupervisionComponent, admin.component);
} }
} }

View File

@@ -198,6 +198,7 @@ public class CombinedBiometricStatusUtilsTest {
@EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS) @EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS)
public void getDisabledAdmin_whenFingerprintDisabled_whenFaceDisabled_returnsRestrictions() { public void getDisabledAdmin_whenFingerprintDisabled_whenFaceDisabled_returnsRestrictions() {
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true); when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null)) when(mDevicePolicyManager.getKeyguardDisabledFeatures(null))
.thenReturn(KEYGUARD_DISABLE_FACE | KEYGUARD_DISABLE_FINGERPRINT); .thenReturn(KEYGUARD_DISABLE_FACE | KEYGUARD_DISABLE_FINGERPRINT);

View File

@@ -155,6 +155,7 @@ public class FaceStatusUtilsTest {
@EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS) @EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS)
public void getDisabledAdmin_whenFaceDisabled_returnsRestriction() { public void getDisabledAdmin_whenFaceDisabled_returnsRestriction() {
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true); when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null)) when(mDevicePolicyManager.getKeyguardDisabledFeatures(null))
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE); .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE);
@@ -172,7 +173,8 @@ public class FaceStatusUtilsTest {
} }
@Test @Test
public void getSummary_whenNotEnrolled_returnsSummaryNone() { @DisableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
public void getSummary_whenNotEnrolled_flagOff_returnsSummaryNone() {
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false); when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
assertThat(mFaceStatusUtils.getSummary()) assertThat(mFaceStatusUtils.getSummary())
@@ -181,6 +183,17 @@ public class FaceStatusUtilsTest {
"security_settings_face_preference_summary_none")); "security_settings_face_preference_summary_none"));
} }
@Test
@EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
public void getSummary_whenNotEnrolled_flagOn_returnsSummaryNone() {
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
assertThat(mFaceStatusUtils.getSummary())
.isEqualTo(ResourcesUtils.getResourcesString(
mApplicationContext,
"security_settings_face_preference_summary_none_new"));
}
@Test @Test
public void getSummary_whenEnrolled_returnsSummary() { public void getSummary_whenEnrolled_returnsSummary() {
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true); when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);

View File

@@ -161,6 +161,7 @@ public class FingerprintStatusUtilsTest {
@EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS) @EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS)
public void getDisabledAdmin_whenFingerprintDisabled_returnsRestriction() { public void getDisabledAdmin_whenFingerprintDisabled_returnsRestriction() {
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true); when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null)) when(mDevicePolicyManager.getKeyguardDisabledFeatures(null))
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
@@ -179,7 +180,8 @@ public class FingerprintStatusUtilsTest {
} }
@Test @Test
public void getSummary_whenNotEnrolled_returnsSummaryNone() { @DisableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
public void getSummary_whenNotEnrolled_flagOff_returnsSummaryNone() {
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(false); when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
assertThat(mFingerprintStatusUtils.getSummary()) assertThat(mFingerprintStatusUtils.getSummary())
@@ -188,6 +190,17 @@ public class FingerprintStatusUtilsTest {
"security_settings_fingerprint_preference_summary_none")); "security_settings_fingerprint_preference_summary_none"));
} }
@Test
@EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
public void getSummary_whenNotEnrolled_flagOn_returnsSummaryNone() {
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
assertThat(mFingerprintStatusUtils.getSummary())
.isEqualTo(ResourcesUtils.getResourcesString(
mApplicationContext,
"security_settings_fingerprint_preference_summary_none_new"));
}
@Test @Test
public void getSummary_whenEnrolled_returnsSummary() { public void getSummary_whenEnrolled_returnsSummary() {
final int enrolledFingerprintsCount = 2; final int enrolledFingerprintsCount = 2;

View File

@@ -187,7 +187,7 @@ public class FaceSafetySourceTest {
assertSafetySourceDisabledDataSetWithSingularSummary( assertSafetySourceDisabledDataSetWithSingularSummary(
"security_settings_face_preference_title_new", "security_settings_face_preference_title_new",
"security_settings_face_preference_summary_none"); "security_settings_face_preference_summary_none_new");
} }
@Test @Test
@@ -195,6 +195,7 @@ public class FaceSafetySourceTest {
@EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS) @EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS)
public void setSafetySourceData_withFaceNotEnrolled_whenSupervisionIsOn_setsData() { public void setSafetySourceData_withFaceNotEnrolled_whenSupervisionIsOn_setsData() {
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true); when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
when(mFaceManager.isHardwareDetected()).thenReturn(true); when(mFaceManager.isHardwareDetected()).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false); when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
@@ -205,7 +206,7 @@ public class FaceSafetySourceTest {
assertSafetySourceDisabledDataSetWithSingularSummary( assertSafetySourceDisabledDataSetWithSingularSummary(
"security_settings_face_preference_title_new", "security_settings_face_preference_title_new",
"security_settings_face_preference_summary_none"); "security_settings_face_preference_summary_none_new");
} }
@Test @Test
@@ -220,7 +221,7 @@ public class FaceSafetySourceTest {
assertSafetySourceEnabledDataSetWithSingularSummary( assertSafetySourceEnabledDataSetWithSingularSummary(
"security_settings_face_preference_title_new", "security_settings_face_preference_title_new",
"security_settings_face_preference_summary_none", "security_settings_face_preference_summary_none_new",
FaceEnrollIntroductionInternal.class.getName()); FaceEnrollIntroductionInternal.class.getName());
} }
@@ -248,6 +249,7 @@ public class FaceSafetySourceTest {
@EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS) @EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS)
public void setSafetySourceData_withFaceEnrolled_whenSupervisionIsOn_setsData() { public void setSafetySourceData_withFaceEnrolled_whenSupervisionIsOn_setsData() {
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true); when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
when(mFaceManager.isHardwareDetected()).thenReturn(true); when(mFaceManager.isHardwareDetected()).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true); when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);

View File

@@ -203,7 +203,7 @@ public class FingerprintSafetySourceTest {
assertSafetySourceDisabledDataSetWithSingularSummary( assertSafetySourceDisabledDataSetWithSingularSummary(
"security_settings_fingerprint", "security_settings_fingerprint",
"security_settings_fingerprint_preference_summary_none"); "security_settings_fingerprint_preference_summary_none_new");
} }
@Test @Test
@@ -211,6 +211,7 @@ public class FingerprintSafetySourceTest {
@EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS) @EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS)
public void setSafetySourceData_withFingerprintNotEnrolled_whenSupervisionIsOn_setsData() { public void setSafetySourceData_withFingerprintNotEnrolled_whenSupervisionIsOn_setsData() {
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true); when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
when(mFingerprintManager.isHardwareDetected()).thenReturn(true); when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false); when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false);
@@ -222,7 +223,7 @@ public class FingerprintSafetySourceTest {
assertSafetySourceDisabledDataSetWithSingularSummary( assertSafetySourceDisabledDataSetWithSingularSummary(
"security_settings_fingerprint", "security_settings_fingerprint",
"security_settings_fingerprint_preference_summary_none"); "security_settings_fingerprint_preference_summary_none_new");
} }
@Test @Test
@@ -238,7 +239,7 @@ public class FingerprintSafetySourceTest {
assertSafetySourceEnabledDataSetWithSingularSummary( assertSafetySourceEnabledDataSetWithSingularSummary(
"security_settings_fingerprint", "security_settings_fingerprint",
"security_settings_fingerprint_preference_summary_none", "security_settings_fingerprint_preference_summary_none_new",
FingerprintSettings.class.getName()); FingerprintSettings.class.getName());
} }
@@ -272,6 +273,7 @@ public class FingerprintSafetySourceTest {
public void setSafetySourceData_withFingerprintsEnrolled_whenSupervisionIsOn_setsData() { public void setSafetySourceData_withFingerprintsEnrolled_whenSupervisionIsOn_setsData() {
int enrolledFingerprintsCount = 2; int enrolledFingerprintsCount = 2;
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true); when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
when(mFingerprintManager.isHardwareDetected()).thenReturn(true); when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(true); when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(true);

View File

@@ -0,0 +1 @@
include /src/com/android/settings/safetycenter/OWNERS

View File

@@ -32,10 +32,13 @@ import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.UserManager; import android.os.UserManager;
import android.os.storage.StorageManager; import android.os.storage.StorageManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -64,6 +67,8 @@ public class ScreenLockPreferenceDetailsUtilsTest {
private static final int SOURCE_METRICS_CATEGORY = 10; private static final int SOURCE_METRICS_CATEGORY = 10;
private static final int USER_ID = 11; private static final int USER_ID = 11;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Rule @Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock @Mock
@@ -118,7 +123,8 @@ public class ScreenLockPreferenceDetailsUtilsTest {
} }
@Test @Test
public void getSummary_unsecureAndDisabledPattern_shouldReturnUnlockModeOff() { @DisableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
public void getSummary_unsecureAndDisabledPattern_flagOff_shouldReturnUnlockModeOff() {
final String summary = prepareString("unlock_set_unlock_mode_off", "unlockModeOff"); final String summary = prepareString("unlock_set_unlock_mode_off", "unlockModeOff");
when(mLockPatternUtils.isSecure(USER_ID)).thenReturn(false); when(mLockPatternUtils.isSecure(USER_ID)).thenReturn(false);
@@ -127,6 +133,17 @@ public class ScreenLockPreferenceDetailsUtilsTest {
assertThat(mScreenLockPreferenceDetailsUtils.getSummary(USER_ID)).isEqualTo(summary); assertThat(mScreenLockPreferenceDetailsUtils.getSummary(USER_ID)).isEqualTo(summary);
} }
@Test
@EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
public void getSummary_unsecureAndDisabledPattern_flagOn_shouldReturnUnlockModeOff() {
final String summary = prepareString("unlock_set_unlock_mode_off_new", "unlockModeOff");
when(mLockPatternUtils.isSecure(USER_ID)).thenReturn(false);
when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true);
assertThat(mScreenLockPreferenceDetailsUtils.getSummary(USER_ID)).isEqualTo(summary);
}
@Test @Test
public void getSummary_unsecurePattern_shouldReturnUnlockModeNone() { public void getSummary_unsecurePattern_shouldReturnUnlockModeNone() {
final String summary = final String summary =