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_settingslib_flags",
"android.app.flags-aconfig",
"android.app.supervision.flags-aconfig",
"android.provider.flags-aconfig",
"android.security.flags-aconfig",
"android.view.contentprotection.flags-aconfig",

View File

@@ -2817,6 +2817,15 @@
</intent-filter>
</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"
android:enabled="false"
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_marginTop="8dp"
android:visibility="gone"/>
<SeekBar
android:id="@+id/input_setting_keys_value_custom_slider"
android:paddingStart="8dp"
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"
<LinearLayout
android:id="@+id/input_setting_keys_custom_seekbar_layout"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginTop="8dp"
android:visibility="gone"/>
android:layout_gravity="center_horizontal"
android:gravity="center_vertical"
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>
</RadioGroup>

View File

@@ -94,8 +94,5 @@
<!-- Volume seekbar colors -->
<color name="seekbar_thumb_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>

View File

@@ -1059,6 +1059,20 @@
<item>either_charging_or_docked</item>
</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">
<item>@string/zen_mode_from_anyone</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_modes_foreground">@color/homepage_pink_fg</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_background">@color/homepage_pink_bg</color>
<color name="homepage_hub_mode_foreground">@color/homepage_orange_fg</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_background">@color/homepage_orange_bg</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_progress_tint_color">@android:color/system_accent1_800</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>

View File

@@ -810,13 +810,23 @@
<!-- Allowed packages to show the confirmation dialog for a system locale suggestion -->
<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">
<item>@layout/accessibility_text_reading_preview_app_grid</item>
<item>@layout/screen_zoom_preview_1</item>
<item>@layout/accessibility_text_reading_preview_mail_content</item>
</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 -->
<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] -->
<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]-->
<string name="qr_code_content_description">QR code</string>
<!-- 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>
<!-- 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]-->
<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>
<!-- 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]-->
<string name="title_system_locale_addition">Add %s to preferred languages?</string>
@@ -547,7 +553,7 @@
<!-- Category for more language settings. [CHAR LIMIT=NONE]-->
<string name="more_language_settings_category">More language settings</string>
<!-- 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]-->
<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]-->
@@ -833,6 +839,8 @@
<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] -->
<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] -->
<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] -->
@@ -1006,6 +1014,8 @@
}</string>
<!-- message shown in summary field when no fingerprints are registered -->
<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] -->
<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] -->
@@ -1771,6 +1781,8 @@
<!-- Summary for "Configure lockscreen" when lock screen is off [CHAR LIMIT=45] -->
<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] -->
<string name="unlock_set_unlock_mode_none">Swipe</string>
<!-- Summary for "Configure lockscreen" when security pattern is enabled [CHAR LIMIT=45] -->
@@ -3655,6 +3667,21 @@
<string name="communal_settings_title">Communal</string>
<!-- Summary of the communal settings under Settings > Communal [CHAR LIMIT=50] -->
<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 screen title-->
@@ -3897,6 +3924,18 @@
<!-- Label for bluetooth tether checkbox [CHAR LIMIT=25]-->
<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-->
<!-- Label for ethernet tether checkbox [CHAR LIMIT=NONE]-->
<string name="ethernet_tether_checkbox_text">Ethernet tethering</string>
@@ -5691,6 +5730,9 @@
<string name="accessibility_autoclick_longer_desc">Longer</string>
<!-- Description for the seekbar that adjust auto click time. [CHAR_LIMIT=NONE] -->
<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] -->
<!-- TODO(b/394683600): Update string to translatable once approved by UXW. -->
<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] -->
<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] -->
<!-- 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>
</resources>

View File

@@ -30,6 +30,11 @@
settings:searchable="false"
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
android:key="autoclick_shortcut_preference"
android:title="@string/accessibility_autoclick_shortcut_title"

View File

@@ -88,6 +88,12 @@
android:persistent="false"
android:title="@string/accessibility_text_reading_options_title"
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

View File

@@ -24,4 +24,36 @@
android:selectable="false"
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>

View File

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

View File

@@ -61,7 +61,7 @@
settings:controller="com.android.settings.inputmethod.TouchpadAccelerationPreferenceController"
android:order="38"/>
<com.android.settings.widget.SeekBarPreference
<com.android.settings.widget.LabeledSeekBarPreference
android:key="touchpad_pointer_speed"
android:title="@string/trackpad_pointer_speed"
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 =
origConfig.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
final int[] previewSamples = getPreviewSampleLayouts(mContext);
final int[] previewContentDescriptions = getPreviewSampleContentDescriptions(mContext);
final PreviewPagerAdapter pagerAdapter = new PreviewPagerAdapter(mContext, isLayoutRtl,
previewSamples, createConfig(origConfig));
mPreviewPreference.setPreviewAdapter(pagerAdapter);
mPreviewPreference.setCurrentItem(
isLayoutRtl ? previewSamples.length - 1 : FRAME_INITIAL_INDEX);
mPreviewPreference.setContentDescription(previewContentDescriptions);
final int initialPagerIndex =
mLastFontProgress * mDisplaySizeData.getValues().size() + mLastDisplayProgress;
@@ -188,6 +190,20 @@ class TextReadingPreviewController extends BasePreferenceController implements
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() {
final int displayDataSize = mDisplaySizeData.getValues().size();
final int fontSizeProgress = mFontSizePreference.getProgress();

View File

@@ -43,28 +43,11 @@ public class TextReadingPreviewPreference extends Preference {
private int mCurrentItem;
private int mLastLayerIndex;
private PreviewPagerAdapter mPreviewAdapter;
private int[] mContentDescriptions;
private int mLayoutMinHorizontalPadding = 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) {
super(context);
init();
@@ -95,7 +78,23 @@ public class TextReadingPreviewPreference extends Preference {
adjustPaddings(previewLayout, backgroundView);
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 =
(DotsPageIndicator) holder.findViewById(R.id.page_indicator);
updateAdapterIfNeeded(viewPager, pageIndicator, mPreviewAdapter);
@@ -122,6 +121,10 @@ public class TextReadingPreviewPreference extends Preference {
viewPager.setCurrentItem(getCurrentItem() + 1));
nextButton.setContentDescription(getContext().getString(
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
@@ -170,6 +173,10 @@ public class TextReadingPreviewPreference extends Preference {
);
}
void setContentDescription(int[] stringIds) {
mContentDescriptions = stringIds;
}
void setPreviewAdapter(PreviewPagerAdapter previewAdapter) {
if (previewAdapter != mPreviewAdapter) {
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 {
throw UnsupportedOperationException("query operation not supported currently.")
throw UnsupportedOperationException("delete operation not supported currently.")
}
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.settingslib.RestrictedLockUtils;
import com.android.settingslib.supervision.SupervisionRestrictionsHelper;
/**
* 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);
}
final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
final SupervisionManager sm =
android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()
? context.getSystemService(SupervisionManager.class)
: null;
return parentConsentRequiredInternal(dpm, sm, modality, userHandle);
return parentConsentRequiredInternal(context, modality, userHandle);
}
/**
@@ -74,18 +70,22 @@ public class ParentalControlsUtils {
@Nullable
@VisibleForTesting
static RestrictedLockUtils.EnforcedAdmin parentConsentRequiredInternal(
@NonNull DevicePolicyManager dpm,
@Nullable SupervisionManager sm,
@NonNull Context context,
@BiometricAuthenticator.Modality int modality,
@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(
dpm, sm, modality, userHandle)) {
return null;
}
if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) {
// Supervision doesn't necessarily have have an admin component.
return new RestrictedLockUtils.EnforcedAdmin(
/* component= */ null, UserManager.DISALLOW_BIOMETRIC, userHandle);
return SupervisionRestrictionsHelper.createEnforcedAdmin(
context, UserManager.DISALLOW_BIOMETRIC, userHandle);
} else {
final ComponentName cn =
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 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_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED =
"biometrics_successfully_authenticated";
@@ -163,6 +164,7 @@ public class FaceSettings extends DashboardFragment {
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putByteArray(KEY_TOKEN, mToken);
outState.putBoolean(KEY_CONFIRMING_PASSWORD, mConfirmingPassword);
}
@Override
@@ -273,6 +275,7 @@ public class FaceSettings extends DashboardFragment {
if (savedInstanceState != null) {
mToken = savedInstanceState.getByteArray(KEY_TOKEN);
mConfirmingPassword = savedInstanceState.getBoolean(KEY_CONFIRMING_PASSWORD);
}
if (Flags.biometricsOnboardingEducation()) {

View File

@@ -98,11 +98,14 @@ public class FaceStatusUtils {
return mContext.getString(
com.android.settingslib.widget.restricted.R.string.disabled_by_admin);
} 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()
.getString(
hasEnrolled()
? 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);
} else {
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.
*/
public static boolean isAvailable(Context context) {
if (com.android.systemui.Flags.glanceableHubV2()) {
return false;
}
if (!Utils.canCurrentUserDream(context)) {
return false;
}
if (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);
return context.getResources().getBoolean(R.bool.config_show_communal_settings);
}
}

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

View File

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

View File

@@ -16,9 +16,13 @@
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.STREAMING;
import static java.util.Collections.emptyList;
import android.app.Notification;
import android.app.NotificationChannel;
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.LocalBluetoothLeBroadcastSourceState;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.PrivateBroadcastReceiveData;
import com.android.settingslib.bluetooth.VolumeControlProfile;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.flags.Flags;
import com.android.settingslib.utils.ThreadUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AudioStreamMediaService extends Service {
@@ -103,7 +110,7 @@ public class AudioStreamMediaService extends Service {
private final PlaybackState.Builder mPlayStateHysteresisBuilder =
new PlaybackState.Builder()
.setState(
PlaybackState.STATE_STOPPED,
PlaybackState.STATE_PAUSED,
STATIC_PLAYBACK_POSITION,
ZERO_PLAYBACK_SPEED)
.addCustomAction(
@@ -122,7 +129,9 @@ public class AudioStreamMediaService extends Service {
private int mLatestPositiveVolume = 25;
private boolean mHysteresisModeFixAvailable;
private int mBroadcastId;
@Nullable private Map<BluetoothDevice, LocalBluetoothLeBroadcastSourceState> mStateByDevice;
@VisibleForTesting
@Nullable
Map<BluetoothDevice, LocalBluetoothLeBroadcastSourceState> mStateByDevice;
@Nullable private LocalBluetoothManager mLocalBtManager;
@Nullable private AudioStreamsHelper mAudioStreamsHelper;
@Nullable private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
@@ -236,6 +245,19 @@ public class AudioStreamMediaService extends Service {
stopSelf();
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(() -> {
mBroadcastId = intent.getIntExtra(BROADCAST_ID, -1);
if (mBroadcastId == -1) {
@@ -258,6 +280,78 @@ public class AudioStreamMediaService extends Service {
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) {
if (mLocalSession != null) {
return mLocalSession.getSessionToken();
@@ -288,7 +382,8 @@ public class AudioStreamMediaService extends Service {
}
private String getDeviceName() {
if (mStateByDevice == null || mStateByDevice.isEmpty() || mLocalBtManager == null) {
List<BluetoothDevice> validDevices = getDeviceInValidState();
if (validDevices.isEmpty() || mLocalBtManager == null) {
return DEFAULT_DEVICE_NAME;
}
@@ -297,8 +392,7 @@ public class AudioStreamMediaService extends Service {
return DEFAULT_DEVICE_NAME;
}
CachedBluetoothDevice device = manager.findDevice(
mStateByDevice.keySet().iterator().next());
CachedBluetoothDevice device = manager.findDevice(validDevices.getFirst());
return device != null ? device.getName() : DEFAULT_DEVICE_NAME;
}
@@ -320,6 +414,47 @@ public class AudioStreamMediaService extends Service {
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
@Override
public IBinder onBind(Intent intent) {
@@ -342,6 +477,9 @@ public class AudioStreamMediaService extends Service {
@Override
public void onReceiveStateChanged(
BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) {
if (Flags.audioStreamMediaServiceByReceiveState()) {
return;
}
super.onReceiveStateChanged(sink, sourceId, state);
if (!mHysteresisModeFixAvailable || mStateByDevice == null
|| !mStateByDevice.containsKey(sink)) {
@@ -383,23 +521,21 @@ public class AudioStreamMediaService extends Service {
@Override
public void onDeviceVolumeChanged(
@NonNull BluetoothDevice device, @IntRange(from = -255, to = 255) int volume) {
if (mStateByDevice == null || mStateByDevice.isEmpty()) {
Log.w(TAG, "active device or device has source is null!");
if (!getDeviceInValidState().contains(device)) {
Log.w(TAG, "onDeviceVolumeChanged() : device not in valid state list");
return;
}
Log.d(
TAG,
"onDeviceVolumeChanged() bluetoothDevice : " + device + " volume: " + volume);
if (mStateByDevice.containsKey(device)) {
if (volume == 0) {
mIsMuted = true;
} else {
mIsMuted = false;
mLatestPositiveVolume = volume;
}
if (mLocalSession != null) {
mLocalSession.setPlaybackState(getPlaybackState());
}
if (volume == 0) {
mIsMuted = true;
} else {
mIsMuted = false;
mLatestPositiveVolume = volume;
}
if (mLocalSession != null) {
mLocalSession.setPlaybackState(getPlaybackState());
}
}
}
@@ -426,7 +562,7 @@ public class AudioStreamMediaService extends Service {
&& mStateByDevice != null) {
mStateByDevice.remove(cachedDevice.getDevice());
}
if (mStateByDevice == null || mStateByDevice.isEmpty()) {
if (getDeviceInValidState().isEmpty()) {
Log.d(TAG, "onProfileConnectionStateChanged() : stopSelf");
stopSelf();
}
@@ -484,11 +620,7 @@ public class AudioStreamMediaService extends Service {
}
private void handleOnPlay() {
if (mStateByDevice == null || mStateByDevice.isEmpty()) {
Log.w(TAG, "active device or device has source is null!");
return;
}
mStateByDevice.keySet().forEach(device -> {
getDeviceInValidState().forEach(device -> {
Log.d(TAG, "onPlay() setting volume for device : " + device + " volume: "
+ mLatestPositiveVolume);
setDeviceVolume(device, mLatestPositiveVolume);
@@ -496,11 +628,7 @@ public class AudioStreamMediaService extends Service {
}
private void handleOnPause() {
if (mStateByDevice == null || mStateByDevice.isEmpty()) {
Log.w(TAG, "active device or device has source is null!");
return;
}
mStateByDevice.keySet().forEach(device -> {
getDeviceInValidState().forEach(device -> {
Log.d(TAG, "onPause() setting volume for device : " + 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.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.flags.Flags;
class SourceAddedState extends AudioStreamStateHandler {
@VisibleForTesting
@@ -55,10 +56,12 @@ class SourceAddedState extends AudioStreamStateHandler {
if (cached != null) {
mAudioStreamsRepository.saveMetadata(context, cached);
}
helper.startMediaService(
context,
preference.getAudioStreamBroadcastId(),
String.valueOf(preference.getTitle()));
if (!Flags.audioStreamMediaServiceByReceiveState()) {
helper.startMediaService(
context,
preference.getAudioStreamBroadcastId(),
String.valueOf(preference.getTitle()));
}
mMetricsFeatureProvider.action(
preference.getContext(),
SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED,

View File

@@ -81,9 +81,11 @@ public class ExternalDisplaySizePreference extends AccessibilitySeekBarPreferenc
implements SeekBar.OnSeekBarChangeListener {
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_SEEKBAR_DELAY_MS = 100;
private final DisplaySizeData mDisplaySizeData;
private int mLastDisplayProgress;
private long mLastCommitTime;
private boolean mSeekByTouch;
ExternalDisplaySizePreferenceStateHandler(DisplaySizeData displaySizeData) {
mDisplaySizeData = displaySizeData;
}
@@ -99,8 +101,7 @@ public class ExternalDisplaySizePreference extends AccessibilitySeekBarPreferenc
mLastCommitTime = SystemClock.elapsedRealtime();
}
private void postCommitDelayed() {
var commitDelayMs = CHANGE_BY_BUTTON_DELAY_MS;
private void postCommitDelayed(long commitDelayMs) {
if (SystemClock.elapsedRealtime() - mLastCommitTime < MIN_COMMIT_INTERVAL_MS) {
commitDelayMs += MIN_COMMIT_INTERVAL_MS;
}
@@ -112,13 +113,18 @@ public class ExternalDisplaySizePreference extends AccessibilitySeekBarPreferenc
@Override
public void onProgressChanged(@NonNull SeekBar seekBar, int i, boolean b) {
postCommitDelayed();
if (!mSeekByTouch) postCommitDelayed(CHANGE_BY_BUTTON_DELAY_MS);
}
@Override
public void onStartTrackingTouch(@NonNull SeekBar seekBar) {}
public void onStartTrackingTouch(@NonNull SeekBar seekBar) {
mSeekByTouch = true;
}
@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. */
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);
TextView customValueTextView = accessibilityKeyDialog.findViewById(
R.id.input_setting_keys_value_custom_value);
View seekbarView = accessibilityKeyDialog.findViewById(
R.id.input_setting_keys_custom_seekbar_layout);
SeekBar customProgressBar = accessibilityKeyDialog.findViewById(
R.id.input_setting_keys_value_custom_slider);
TextView titleTextView = accessibilityKeyDialog.findViewById(
@@ -147,7 +149,7 @@ public abstract class KeyboardAccessibilityKeysDialogFragment extends DialogFrag
customValueTextView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
customValueTextView.setText(
progressToThresholdInSecond(customProgressBar.getProgress()));
customProgressBar.setVisibility(isChecked ? View.VISIBLE : View.GONE);
seekbarView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
buttonView.setChecked(isChecked);
});
cannedValueRadioGroup.setOnCheckedChangeListener(
@@ -174,14 +176,14 @@ public abstract class KeyboardAccessibilityKeysDialogFragment extends DialogFrag
// setting
initStateBasedOnThreshold(cannedValueRadioGroup, customRadioButton,
customValueTextView,
customProgressBar);
customProgressBar, seekbarView);
} else if (customRadioButton.isChecked()) {
cannedValueRadioGroup.clearCheck();
customRadioButton.setChecked(true);
customValueTextView.setVisibility(View.VISIBLE);
customValueTextView.setText(
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,
RadioButton customRadioButton, TextView customValueTextView,
SeekBar customProgressBar) {
SeekBar customProgressBar, View seekbarView) {
int inputSettingKeysThreshold = getInputSettingKeysValue();
switch (inputSettingKeysThreshold) {
case 600 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_600);
@@ -213,5 +215,6 @@ public abstract class KeyboardAccessibilityKeysDialogFragment extends DialogFrag
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.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.EthernetManager
import android.net.EthernetManager.STATE_ABSENT
import android.net.EthernetNetworkManagementException
import android.net.EthernetNetworkUpdateRequest
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.util.Log
import androidx.core.content.ContextCompat
import com.google.common.annotations.VisibleForTesting
class EthernetInterface(private val context: Context, private val id: String) :
EthernetManager.InterfaceStateListener {
interface EthernetInterfaceStateListener {
fun interfaceUpdated()
}
private val ethernetManager: EthernetManager? =
context.getSystemService(EthernetManager::class.java)
private val connectivityManager: ConnectivityManager? =
context.getSystemService(ConnectivityManager::class.java)
private val executor = ContextCompat.getMainExecutor(context)
private val interfaceListeners = mutableListOf<EthernetInterfaceStateListener>()
private val TAG = "EthernetInterface"
private val networkRequest: NetworkRequest =
NetworkRequest.Builder()
.clearCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
.build()
private var interfaceState = STATE_ABSENT
private var ipConfiguration = IpConfiguration()
private var linkProperties = LinkProperties()
fun getInterfaceState() = interfaceState
fun getId() = id
fun getConfiguration(): IpConfiguration {
return ipConfiguration
fun getConfiguration() = 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) {
@@ -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?) {
if (id == this.id) {
ipConfiguration = cfg ?: IpConfiguration()
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.net.EthernetManager
import android.net.IpConfiguration
import android.net.LinkProperties
import android.net.StaticIpConfiguration
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.PreferenceScreen
import com.android.settings.R
@@ -30,13 +37,25 @@ class EthernetInterfaceDetailsController(
context: Context,
private val fragment: PreferenceFragmentCompat,
private val preferenceId: String,
) : AbstractPreferenceController(context) {
private val lifecycle: Lifecycle,
) :
AbstractPreferenceController(context),
EthernetInterface.EthernetInterfaceStateListener,
LifecycleEventObserver {
private val KEY_HEADER = "ethernet_details"
private val ethernetManager = context.getSystemService(EthernetManager::class.java)
private val ethernetInterface =
EthernetTrackerImpl.getInstance(context).getInterface(preferenceId)
private lateinit var entityHeaderController: EntityHeaderController
private var ipAddressPref: Preference? = null
init {
lifecycle.addObserver(this)
}
override fun isAvailable(): Boolean {
return true
}
@@ -45,10 +64,24 @@ class EthernetInterfaceDetailsController(
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) {
val headerPref: LayoutPreference? = screen.findPreference(KEY_HEADER)
val mEntityHeaderController =
entityHeaderController =
EntityHeaderController.newInstance(
fragment.getActivity(),
fragment,
@@ -59,17 +92,49 @@ class EthernetInterfaceDetailsController(
iconView?.setScaleType(ImageView.ScaleType.CENTER_INSIDE)
mEntityHeaderController
.setLabel("Ethernet")
.setSummary(
if (ethernetInterface?.getInterfaceState() == EthernetManager.STATE_LINK_UP) {
mContext.getString(R.string.network_connected)
} else {
mContext.getString(R.string.network_disconnected)
}
)
.setSecondSummary("")
.setIcon(mContext.getDrawable(R.drawable.ic_settings_ethernet))
.done(true /* rebind */)
if (entityHeaderController != null) {
entityHeaderController
.setLabel("Ethernet")
.setSummary(
if (ethernetInterface?.getInterfaceState() == EthernetManager.STATE_LINK_UP) {
mContext.getString(R.string.network_connected)
} else {
mContext.getString(R.string.network_disconnected)
}
)
.setSecondSummary("")
.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(
context: Context
): 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_INSTALL_UNKNOWN_SOURCES
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.SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION
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.SettingsAlertDialogContent
import com.android.settingslib.wifi.WifiUtils.Companion.DIALOG_WINDOW_TYPE
import android.security.advancedprotection.AdvancedProtectionManager
class ActionDisabledByAdvancedProtectionDialog : SpaDialogWindowTypeActivity() {
@Composable
override fun Content() {
SettingsAlertDialogContent(
confirmButton = AlertDialogButton(getString(R.string.okay)) { finish() },
confirmButton = AlertDialogButton(getString(R.string.okay)) {
finish()
logDialogShown(learnMoreClicked = false)
},
dismissButton = getSupportButtonIfExists(),
title = getString(R.string.disabled_by_advanced_protection_title),
icon = {
@@ -56,8 +61,8 @@ class ActionDisabledByAdvancedProtectionDialog : SpaDialogWindowTypeActivity() {
}
private fun getDialogMessage(): String {
val featureId = intent.getIntExtra(EXTRA_SUPPORT_DIALOG_FEATURE, -1)
val type = intent.getIntExtra(EXTRA_SUPPORT_DIALOG_TYPE, SUPPORT_DIALOG_TYPE_UNKNOWN)
val featureId = getIntentFeatureId()
val type = getIntentDialogueType()
val messageId = when (type) {
SUPPORT_DIALOG_TYPE_DISABLED_SETTING -> {
if (featureIdsWithSettingOn.contains(featureId)) {
@@ -93,6 +98,7 @@ class ActionDisabledByAdvancedProtectionDialog : SpaDialogWindowTypeActivity() {
) {
startActivity(helpIntent)
finish()
logDialogShown(learnMoreClicked = true)
}
} catch (e: Exception) {
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
}
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)) {
intent.getIntExtra(DIALOG_WINDOW_TYPE, WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW)
} 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 {
const val TAG = "AdvancedProtectionDlg"
val defaultMessageId = R.string.disabled_by_advanced_protection_action_message

View File

@@ -178,7 +178,9 @@ public class ScreenLockPreferenceDetailsUtils {
if (!mLockPatternUtils.isSecure(userId)) {
if (userId == mProfileChallengeUserId
|| 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 {
return R.string.unlock_set_unlock_mode_none;
}

View File

@@ -16,9 +16,12 @@
package com.android.settings.sound
import android.app.settings.SettingsEnums.ACTION_SHOW_MEDIA_ON_LOCK_SCREEN
import android.content.Context
import android.provider.Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN
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.KeyValueStoreDelegate
import com.android.settingslib.datastore.SettingsSecureStore
@@ -32,7 +35,13 @@ class MediaControlsLockscreenSwitchPreference :
KEY,
R.string.media_controls_lockscreen_title,
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
get() = SensitivityLevel.NO_SENSITIVITY

View File

@@ -16,30 +16,39 @@
package com.android.settings.sound
import android.app.settings.SettingsEnums.ACTION_PIN_MEDIA_PLAYER
import android.content.Context
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.SettingsSecureStore
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel
import com.android.settingslib.metadata.SwitchPreference
import com.android.settings.R
// LINT.IfChange
class MediaControlsSwitchPreference(
private val mediaControlsStore: MediaControlsScreen.MediaControlsStore,
) : SwitchPreference(
KEY,
R.string.media_controls_resume_title,
R.string.media_controls_resume_description,
) {
private val mediaControlsStore: MediaControlsScreen.MediaControlsStore
) :
SwitchPreference(
KEY,
R.string.media_controls_resume_title,
R.string.media_controls_resume_description,
),
PreferenceActionMetricsProvider {
override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY
override val keywords: Int
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 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) =
preferenceHierarchy(context, this) {
+SupervisionMainSwitchPreference()
+SupervisionMainSwitchPreference(context)
+TitlelessPreferenceGroup(SUPERVISION_DYNAMIC_GROUP_1) += {
+SupervisionWebContentFiltersScreen.KEY
}

View File

@@ -15,8 +15,10 @@
*/
package com.android.settings.supervision
import android.app.Activity
import android.app.supervision.SupervisionManager
import android.content.Context
import android.content.Intent
import androidx.preference.Preference
import com.android.settings.R
import com.android.settingslib.datastore.KeyValueStore
@@ -32,19 +34,22 @@ import com.android.settingslib.preference.MainSwitchPreferenceBinding
import com.android.settingslib.preference.forEachRecursively
/** Main toggle to enable or disable device supervision. */
class SupervisionMainSwitchPreference :
class SupervisionMainSwitchPreference(context: Context) :
MainSwitchPreference(KEY, R.string.device_supervision_switch_title),
PreferenceSummaryProvider,
MainSwitchPreferenceBinding,
Preference.OnPreferenceChangeListener,
PreferenceLifecycleProvider {
private val supervisionMainSwitchStorage = SupervisionMainSwitchStorage(context)
private lateinit var lifeCycleContext: PreferenceLifecycleContext
// TODO(b/383568136): Make presence of summary conditional on whether PIN
// has been set up before or not.
override fun getSummary(context: Context): CharSequence? =
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) =
ReadWritePermit.DISALLOW
@@ -55,26 +60,49 @@ class SupervisionMainSwitchPreference :
override val sensitivityLevel: Int
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) {
super.bind(preference, metadata)
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 {
if (newValue !is Boolean) return true
updateDependentPreferencesEnabledState(preference, newValue)
return true
val intent = Intent(lifeCycleContext, ConfirmSupervisionCredentialsActivity::class.java)
lifeCycleContext.startActivityForResult(
intent,
REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
null,
)
return false
}
private fun updateDependentPreferencesEnabledState(
@@ -83,9 +111,8 @@ class SupervisionMainSwitchPreference :
) {
preference?.parent?.forEachRecursively {
if (
it.parent?.key?.toString() ==
SupervisionDashboardScreen.SUPERVISION_DYNAMIC_GROUP_1 ||
it.key?.toString() == SupervisionPinManagementScreen.KEY
it.parent?.key == SupervisionDashboardScreen.SUPERVISION_DYNAMIC_GROUP_1 ||
it.key == SupervisionPinManagementScreen.KEY
) {
it.isEnabled = isChecked
}
@@ -103,7 +130,6 @@ class SupervisionMainSwitchPreference :
as 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) {
val supervisionManager = context.getSystemService(SupervisionManager::class.java)
supervisionManager?.setSupervisionEnabled(value)
@@ -113,5 +139,6 @@ class SupervisionMainSwitchPreference :
companion object {
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;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -76,6 +78,15 @@ public class TextReadingPreviewControllerTest {
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
public void initPreviewerAdapter_verifyAction() {
when(mPreferenceScreen.findPreference(PREVIEW_KEY)).thenReturn(mPreviewPreference);

View File

@@ -49,29 +49,48 @@ import org.robolectric.RobolectricTestRunner;
*/
@RunWith(RobolectricTestRunner.class)
public class TextReadingPreviewPreferenceTest {
private Context mContext;
private TextReadingPreviewPreference mTextReadingPreviewPreference;
private PreferenceViewHolder mHolder;
private ViewPager mViewPager;
private PreviewPagerAdapter mPreviewPagerAdapter;
private int mPreviewSampleCount;
private int[] mPreviewContentDescriptions;
@Before
public void setUp() {
final Context context = ApplicationProvider.getApplicationContext();
final int[] previewSamples = TextReadingPreviewController.getPreviewSampleLayouts(context);
mContext = ApplicationProvider.getApplicationContext();
mPreviewContentDescriptions =
TextReadingPreviewController.getPreviewSampleContentDescriptions(mContext);
final int[] previewSamples = TextReadingPreviewController.getPreviewSampleLayouts(mContext);
mPreviewSampleCount = previewSamples.length;
final Configuration[] configurations = createConfigurations(mPreviewSampleCount);
mTextReadingPreviewPreference = new TextReadingPreviewPreference(context);
mTextReadingPreviewPreference = new TextReadingPreviewPreference(mContext);
mPreviewPagerAdapter =
spy(new PreviewPagerAdapter(context, /* isLayoutRtl= */ false,
spy(new PreviewPagerAdapter(mContext, /* isLayoutRtl= */ false,
previewSamples, configurations));
final LayoutInflater inflater = LayoutInflater.from(context);
final LayoutInflater inflater = LayoutInflater.from(mContext);
final View view =
inflater.inflate(mTextReadingPreviewPreference.getLayoutResource(),
new LinearLayout(context), false);
new LinearLayout(mContext), false);
mHolder = PreferenceViewHolder.createInstanceForTests(view);
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

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.FaceManager;
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.preference.Preference;
@@ -50,6 +53,7 @@ import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -67,6 +71,8 @@ public class FaceStatusPreferenceControllerTest {
private static final String TEST_PREF_KEY = "baz";
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private LockPatternUtils mLockPatternUtils;
@Mock
@@ -125,7 +131,8 @@ public class FaceStatusPreferenceControllerTest {
}
@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);
mController.updateState(mPreference);
@@ -135,6 +142,18 @@ public class FaceStatusPreferenceControllerTest {
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
public void updateState_hasFace_shouldShowSummary() {
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.FingerprintManager;
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.preference.Preference;
@@ -51,6 +54,7 @@ import com.android.settingslib.utils.StringUtil;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -66,6 +70,8 @@ import java.util.Collections;
@Config(shadows = {ShadowRestrictedLockUtilsInternal.class})
public class FingerprintStatusPreferenceControllerTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private LockPatternUtils mLockPatternUtils;
@Mock
@@ -125,7 +131,8 @@ public class FingerprintStatusPreferenceControllerTest {
}
@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);
mController.updateState(mPreference);
@@ -135,6 +142,19 @@ public class FingerprintStatusPreferenceControllerTest {
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
public void updateState_hasFingerprint_shouldShowSummary() {
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);

View File

@@ -16,11 +16,8 @@
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.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -66,6 +63,7 @@ public class CommunalPreferenceControllerTest {
}
@Test
@DisableFlags(FLAG_GLANCEABLE_HUB_V2)
public void isAvailable_communalEnabled_shouldBeTrueForPrimaryUser() {
setCommunalEnabled(true);
mShadowUserManager.setUserForeground(true);
@@ -73,6 +71,7 @@ public class CommunalPreferenceControllerTest {
}
@Test
@DisableFlags(FLAG_GLANCEABLE_HUB_V2)
public void isAvailable_communalEnabled_shouldBeFalseForSecondaryUser() {
setCommunalEnabled(true);
mShadowUserManager.setUserForeground(false);
@@ -80,6 +79,7 @@ public class CommunalPreferenceControllerTest {
}
@Test
@DisableFlags(FLAG_GLANCEABLE_HUB_V2)
public void isAvailable_communalDisabled_shouldBeFalseForPrimaryUser() {
setCommunalEnabled(false);
mShadowUserManager.setUserForeground(true);
@@ -88,36 +88,8 @@ public class CommunalPreferenceControllerTest {
@Test
@EnableFlags(FLAG_GLANCEABLE_HUB_V2)
public void isAvailable_communalOnMobileEnabled_shouldBeTrueForPrimaryUser() {
setCommunalEnabled(false);
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);
public void isAvailable_glanceableHubV2Enabled_shouldBeFalseForPrimaryUser() {
setCommunalEnabled(true);
mShadowUserManager.setUserForeground(true);
assertFalse(mController.isAvailable());
}
@@ -125,9 +97,4 @@ public class CommunalPreferenceControllerTest {
private void setCommunalEnabled(boolean 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.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
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.BluetoothCallback;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.HeadsetProfile;
@@ -144,6 +146,7 @@ public class AudioSharingDevicePreferenceControllerTest {
@Mock private LeAudioProfile mLeAudioProfile;
@Mock private A2dpProfile mA2dpProfile;
@Mock private HeadsetProfile mHeadsetProfile;
@Mock private ContentResolver mContentResolver;
private Context mContext;
private AudioSharingDevicePreferenceController mController;
@@ -156,7 +159,7 @@ public class AudioSharingDevicePreferenceControllerTest {
@Before
public void setUp() {
mContext = ApplicationProvider.getApplicationContext();
mContext = spy(ApplicationProvider.getApplicationContext());
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
@@ -185,6 +188,7 @@ public class AudioSharingDevicePreferenceControllerTest {
when(mA2dpProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP);
when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO);
when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(true);
when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mScreen.getContext()).thenReturn(mContext);
mPreferenceGroup = spy(new PreferenceCategory(mContext));
doReturn(mPreferenceManager).when(mPreferenceGroup).getPreferenceManager();
@@ -211,6 +215,12 @@ public class AudioSharingDevicePreferenceControllerTest {
verify(mAssistant, never())
.registerServiceCallBack(
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()).refreshPreference();
}
@@ -224,6 +234,12 @@ public class AudioSharingDevicePreferenceControllerTest {
verify(mAssistant)
.registerServiceCallBack(
any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class));
verify(mContentResolver)
.registerContentObserver(
Settings.Secure.getUriFor(
BluetoothUtils.getPrimaryGroupIdUriForBroadcast()),
false,
mController.mSettingsObserver);
verify(mBluetoothDeviceUpdater).registerCallback();
verify(mBluetoothDeviceUpdater).refreshPreference();
}
@@ -236,6 +252,7 @@ public class AudioSharingDevicePreferenceControllerTest {
verify(mDialogHandler, never()).unregisterCallbacks();
verify(mAssistant, never())
.unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
verify(mContentResolver, never()).unregisterContentObserver(mController.mSettingsObserver);
verify(mBluetoothDeviceUpdater, never()).unregisterCallback();
}
@@ -247,6 +264,7 @@ public class AudioSharingDevicePreferenceControllerTest {
verify(mDialogHandler).unregisterCallbacks();
verify(mAssistant)
.unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
verify(mContentResolver).unregisterContentObserver(mController.mSettingsObserver);
verify(mBluetoothDeviceUpdater).unregisterCallback();
}
@@ -485,6 +503,12 @@ public class AudioSharingDevicePreferenceControllerTest {
verifyNoInteractions(mDialogHandler);
}
@Test
public void onFallbackDeviceChanged_updateSummary() {
mController.mSettingsObserver.onChange(true);
verify(mBluetoothDeviceUpdater).refreshPreference();
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
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.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.never;
import static org.mockito.Mockito.times;
@@ -39,6 +42,7 @@ import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.lifecycle.LifecycleOwner;
@@ -52,6 +56,7 @@ import com.android.settings.testutils.shadow.ShadowThreadUtils;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.flags.Flags;
import com.android.settingslib.widget.LayoutPreference;
import org.junit.After;
@@ -254,6 +259,7 @@ public class AudioStreamHeaderControllerTest {
}
@Test
@EnableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE)
public void testCallback_onReceiveStateChanged_updateButton() {
when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
.thenReturn(Map.of(BROADCAST_ID, STREAMING));
@@ -271,6 +277,7 @@ public class AudioStreamHeaderControllerTest {
verify(mHeaderController, times(2))
.setSummary(mContext.getString(AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY));
verify(mHeaderController, times(2)).done(true);
verify(mAudioStreamsHelper, never()).startMediaService(any(), anyInt(), anyString());
}
@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.DEVICES;
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;
@@ -57,6 +61,7 @@ import android.os.Looper;
import android.os.RemoteException;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.DisplayMetrics;
import android.view.KeyEvent;
import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper;
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.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.bluetooth.PrivateBroadcastReceiveData;
import com.android.settingslib.bluetooth.VolumeControlProfile;
import com.android.settingslib.flags.Flags;
@@ -116,6 +122,8 @@ public class AudioStreamMediaServiceTest {
@Mock private VolumeControlProfile mVolumeControlProfile;
@Mock private CachedBluetoothDevice mCachedBluetoothDevice;
@Mock private BluetoothDevice mDevice;
@Mock
private BluetoothDevice mDevice2;
@Mock private ISession mISession;
@Mock private ISessionController mISessionController;
@Mock private PackageManager mPackageManager;
@@ -240,6 +248,7 @@ public class AudioStreamMediaServiceTest {
@Test
public void onDestroy_flagOn_cleanup() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
var devices = new ArrayList<BluetoothDevice>();
devices.add(mDevice);
@@ -256,8 +265,33 @@ public class AudioStreamMediaServiceTest {
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
public void onStartCommand_noBroadcastId_stopSelf() {
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onStartCommand(new Intent(), /* flags= */ 0, /* startId= */ 0);
verify(mAudioStreamMediaService).stopSelf();
@@ -265,6 +299,7 @@ public class AudioStreamMediaServiceTest {
@Test
public void onStartCommand_noDevice_stopSelf() {
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
Intent intent = new Intent();
intent.putExtra(BROADCAST_ID, 1);
@@ -273,8 +308,110 @@ public class AudioStreamMediaServiceTest {
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
public void onStartCommand_createSessionAndStartForeground() {
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
var devices = new ArrayList<BluetoothDevice>();
devices.add(mDevice);
@@ -317,6 +454,7 @@ public class AudioStreamMediaServiceTest {
@Test
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_ENABLE_LE_AUDIO_SHARING);
@@ -338,6 +476,7 @@ public class AudioStreamMediaServiceTest {
@Test
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_ENABLE_LE_AUDIO_SHARING);
@@ -357,6 +496,7 @@ public class AudioStreamMediaServiceTest {
@Test
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.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
@@ -386,6 +526,7 @@ public class AudioStreamMediaServiceTest {
@Test
public void bluetoothCallback_onDeviceDisconnect_stopSelf() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate();
assertThat(mAudioStreamMediaService.mBluetoothCallback).isNotNull();
mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
@@ -398,9 +539,26 @@ public class AudioStreamMediaServiceTest {
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
public void mediaSessionCallback_onPause_setVolume() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate();
mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
@@ -415,9 +573,26 @@ public class AudioStreamMediaServiceTest {
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
public void mediaSessionCallback_onPlay_setVolume() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate();
mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
@@ -432,9 +607,66 @@ public class AudioStreamMediaServiceTest {
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
public void mediaSessionCallback_onCustomAction_leaveBroadcast() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate();
mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
@@ -449,6 +681,23 @@ public class AudioStreamMediaServiceTest {
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
public void onBind_returnNull() {
IBinder binder = mAudioStreamMediaService.onBind(new Intent());
@@ -466,4 +715,13 @@ public class AudioStreamMediaServiceTest {
intent.putParcelableArrayListExtra(DEVICES, devices);
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 org.mockito.ArgumentMatchers.any;
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.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -33,6 +36,9 @@ import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.content.Context;
import android.content.Intent;
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.preference.Preference;
@@ -43,6 +49,7 @@ import com.android.settings.SettingsActivity;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowFragment;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.flags.Flags;
import org.junit.Before;
import org.junit.Rule;
@@ -62,6 +69,7 @@ import org.robolectric.annotation.Config;
})
public class SourceAddedStateTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final int BROADCAST_ID = 1;
private static final String BROADCAST_TITLE = "title";
private final Context mContext = ApplicationProvider.getApplicationContext();
@@ -105,7 +113,8 @@ public class SourceAddedStateTest {
}
@Test
public void testPerformAction() {
@DisableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE)
public void testPerformAction_startService() {
mInstance.setAudioStreamsRepositoryForTesting(mRepository);
BluetoothLeBroadcastMetadata mockMetadata = mock(BluetoothLeBroadcastMetadata.class);
when(mRepository.getCachedMetadata(anyInt())).thenReturn(mockMetadata);
@@ -124,6 +133,27 @@ public class SourceAddedStateTest {
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
public void testGetOnClickListener_startSubSettings() {
when(mController.getFragment()).thenReturn(mFragment);

View File

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

View File

@@ -18,14 +18,19 @@ package com.android.settings.network.ethernet
import android.content.Context
import android.content.ContextWrapper
import android.net.ConnectivityManager
import android.net.EthernetManager
import android.net.EthernetManager.STATE_ABSENT
import android.net.EthernetManager.STATE_LINK_DOWN
import android.net.EthernetManager.STATE_LINK_UP
import android.net.IpConfiguration
import android.net.LinkProperties
import android.net.Network
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
@@ -34,12 +39,15 @@ import org.mockito.kotlin.mock
class EthernetInterfaceTest {
private val mockEthernetManager = mock<EthernetManager>()
private val mockConnectivityManager = mock<ConnectivityManager>()
private val mockNetwork = mock<Network>()
private val context: Context =
object : ContextWrapper(ApplicationProvider.getApplicationContext()) {
override fun getSystemService(name: String): Any? =
when (name) {
Context.ETHERNET_SERVICE -> mockEthernetManager
Context.CONNECTIVITY_SERVICE -> mockConnectivityManager
else -> super.getSystemService(name)
}
}
@@ -85,4 +93,27 @@ class EthernetInterfaceTest {
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
import android.app.Activity
import android.app.supervision.flags.Flags
import android.content.Context
import android.platform.test.annotations.DisableFlags
@@ -24,6 +25,7 @@ import androidx.fragment.app.testing.FragmentScenario
import androidx.preference.Preference
import androidx.test.core.app.ApplicationProvider
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.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -57,7 +59,7 @@ class SupervisionDashboardScreenTest {
@Test
@EnableFlags(Flags.FLAG_ENABLE_SUPERVISION_SETTINGS_SCREEN)
fun toggleMainSwitch_disablesChildPreferences() {
fun toggleMainSwitch_pinVerificationSucceeded_enablesChildPreferences() {
FragmentScenario.launchInContainer(preferenceScreenCreator.fragmentClass()).onFragment {
fragment ->
val mainSwitchPreference =
@@ -68,8 +70,38 @@ class SupervisionDashboardScreenTest {
assertThat(childPreference.isEnabled).isFalse()
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()
}
}
@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
import android.app.Activity
import android.app.supervision.SupervisionManager
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import androidx.preference.Preference
import androidx.test.core.app.ApplicationProvider
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.widget.MainSwitchPreference
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.stub
import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
class SupervisionMainSwitchPreferenceTest {
private val preference = SupervisionMainSwitchPreference()
private val mockLifeCycleContext = mock<PreferenceLifecycleContext>()
private val mockSupervisionManager = mock<SupervisionManager>()
private val appContext: Context = ApplicationProvider.getApplicationContext()
@@ -46,6 +54,13 @@ class SupervisionMainSwitchPreferenceTest {
}
}
private val preference = SupervisionMainSwitchPreference(context)
@Before
fun setUp() {
preference.onCreate(mockLifeCycleContext)
}
@Test
fun checked_supervisionEnabled_returnTrue() {
setSupervisionEnabled(true)
@@ -61,7 +76,7 @@ class SupervisionMainSwitchPreferenceTest {
}
@Test
fun toggleOn() {
fun toggleOn_triggersPinVerification() {
setSupervisionEnabled(false)
val widget = getMainSwitchPreference()
@@ -69,26 +84,90 @@ class SupervisionMainSwitchPreferenceTest {
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()
verify(mockSupervisionManager).setSupervisionEnabled(true)
}
@Test
fun toggleOff() {
fun toggleOff_pinVerificationSucceeded_supervisionDisabled() {
setSupervisionEnabled(true)
val widget = getMainSwitchPreference()
assertThat(widget.isChecked).isTrue()
widget.performClick()
preference.onActivityResult(
mockLifeCycleContext,
REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
Activity.RESULT_OK,
null,
)
assertThat(widget.isChecked).isFalse()
verify(mockSupervisionManager).setSupervisionEnabled(false)
}
private fun getMainSwitchPreference(): MainSwitchPreference =
preference.createAndBindWidget(context)
@Test
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) =
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.anyInt;
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 android.app.admin.DevicePolicyManager;
import android.app.supervision.SupervisionManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.os.UserHandle;
@@ -44,6 +43,7 @@ import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.settingslib.RestrictedLockUtils;
@@ -53,22 +53,29 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@RunWith(AndroidJUnit4.class)
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;
@Mock private DevicePolicyManager mDpm;
@Mock private SupervisionManager mSm;
private Context mContext;
@Mock
private DevicePolicyManager mDpm;
@Mock
private SupervisionManager mSm;
private ComponentName mSupervisionComponentName = new ComponentName("pkg", "cls");
private final ComponentName mSupervisionComponent = new ComponentName("pkg", "cls");
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class));
mContext = spy(ApplicationProvider.getApplicationContext());
when(mContext.getSystemService(DevicePolicyManager.class)).thenReturn(mDpm);
when(mContext.getSystemService(SupervisionManager.class)).thenReturn(mSm);
}
/**
@@ -85,7 +92,7 @@ public class ParentalControlsUtilsTest {
.thenReturn(keyguardDisabledFlags);
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,
@BiometricAuthenticator.Modality int modality,
int keyguardDisabledFlags) {
when(mSm.isSupervisionEnabledForUser(anyInt())).thenReturn(supervisionEnabled);
when(mDpm.getKeyguardDisabledFeatures(eq(null))).thenReturn(keyguardDisabledFlags);
when(mSm.isSupervisionEnabledForUser(anyInt())).thenReturn(supervisionEnabled);
when(mSm.getActiveSupervisionAppPackage()).thenReturn(
supervisionEnabled ? mSupervisionComponent.getPackageName() : null);
return ParentalControlsUtils.parentConsentRequiredInternal(
mDpm, mSm, modality, new UserHandle(UserHandle.myUserId()));
mContext, modality, new UserHandle(UserHandle.myUserId()));
}
@Test
@@ -115,11 +124,11 @@ public class ParentalControlsUtilsTest {
for (int i = 0; i < tests.length; i++) {
RestrictedLockUtils.EnforcedAdmin admin = getEnforcedAdminForCombination(
mSupervisionComponentName, tests[i][0] /* modality */,
mSupervisionComponent, tests[i][0] /* modality */,
tests[i][1] /* keyguardDisableFlags */);
assertNotNull(admin);
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)
public void getDisabledAdmin_whenFingerprintDisabled_whenFaceDisabled_returnsRestrictions() {
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null))
.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)
public void getDisabledAdmin_whenFaceDisabled_returnsRestriction() {
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null))
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE);
@@ -172,7 +173,8 @@ public class FaceStatusUtilsTest {
}
@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);
assertThat(mFaceStatusUtils.getSummary())
@@ -181,6 +183,17 @@ public class FaceStatusUtilsTest {
"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
public void getSummary_whenEnrolled_returnsSummary() {
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)
public void getDisabledAdmin_whenFingerprintDisabled_returnsRestriction() {
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null))
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
@@ -179,7 +180,8 @@ public class FingerprintStatusUtilsTest {
}
@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);
assertThat(mFingerprintStatusUtils.getSummary())
@@ -188,6 +190,17 @@ public class FingerprintStatusUtilsTest {
"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
public void getSummary_whenEnrolled_returnsSummary() {
final int enrolledFingerprintsCount = 2;

View File

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

View File

@@ -203,7 +203,7 @@ public class FingerprintSafetySourceTest {
assertSafetySourceDisabledDataSetWithSingularSummary(
"security_settings_fingerprint",
"security_settings_fingerprint_preference_summary_none");
"security_settings_fingerprint_preference_summary_none_new");
}
@Test
@@ -211,6 +211,7 @@ public class FingerprintSafetySourceTest {
@EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS)
public void setSafetySourceData_withFingerprintNotEnrolled_whenSupervisionIsOn_setsData() {
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false);
@@ -222,7 +223,7 @@ public class FingerprintSafetySourceTest {
assertSafetySourceDisabledDataSetWithSingularSummary(
"security_settings_fingerprint",
"security_settings_fingerprint_preference_summary_none");
"security_settings_fingerprint_preference_summary_none_new");
}
@Test
@@ -238,7 +239,7 @@ public class FingerprintSafetySourceTest {
assertSafetySourceEnabledDataSetWithSingularSummary(
"security_settings_fingerprint",
"security_settings_fingerprint_preference_summary_none",
"security_settings_fingerprint_preference_summary_none_new",
FingerprintSettings.class.getName());
}
@@ -272,6 +273,7 @@ public class FingerprintSafetySourceTest {
public void setSafetySourceData_withFingerprintsEnrolled_whenSupervisionIsOn_setsData() {
int enrolledFingerprintsCount = 2;
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
when(mFingerprintManager.isHardwareDetected()).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.os.UserManager;
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.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.core.app.ApplicationProvider;
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 USER_ID = 11;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock
@@ -118,7 +123,8 @@ public class ScreenLockPreferenceDetailsUtilsTest {
}
@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");
when(mLockPatternUtils.isSecure(USER_ID)).thenReturn(false);
@@ -127,6 +133,17 @@ public class ScreenLockPreferenceDetailsUtilsTest {
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
public void getSummary_unsecurePattern_shouldReturnUnlockModeNone() {
final String summary =