Snap for 12813595 from 74b1086f23 to 25Q2-release

Change-Id: Ia2029b51d4ba712705cee26749f5a0e366049b3f
This commit is contained in:
Android Build Coastguard Worker
2024-12-17 16:18:06 -08:00
54 changed files with 2601 additions and 792 deletions

View File

@@ -8,12 +8,3 @@ flag {
description: "Enable the time feedback feature, a button to launch feedback in Date & Time Settings"
bug: "283239837"
}
flag {
name: "revamp_toggles"
# "location" is used by the Android System Time team for feature flags.
namespace: "location"
description: "Makes the use location toggle dependent on automatic time zone detection"
bug: "296835792"
}

View File

@@ -76,6 +76,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layoutDirection="ltr"
android:orientation="horizontal">
<include

View File

@@ -21,6 +21,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layoutDirection="locale"
android:focusable="true"
android:orientation="vertical">

View File

@@ -1,76 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2009 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:minHeight="?android:attr/listPreferredItemHeight"
android:focusable="false"
android:gravity="center_vertical">
<RelativeLayout
android:id="@+id/text_layout"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_weight="1"
android:focusable="true"
android:clickable="true"
android:background="?android:attr/selectableItemBackground">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
android:labelFor="@id/apn_radio_button_frame"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceListItem" />
<TextView
android:id="@android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@android:id/title"
android:layout_alignStart="@android:id/title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
android:focusable="false"
android:hyphenationFrequency="normalFast"
android:lineBreakWordStyle="phrase"
android:maxLines="2" />
</RelativeLayout>
<FrameLayout
android:id="@+id/apn_radio_button_frame"
android:layout_width="@dimen/min_tap_target_size"
android:layout_height="@dimen/min_tap_target_size"
android:layout_margin="8dp">
<RadioButton
android:id="@+id/apn_radiobutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:clickable="false"
android:focusable="false" />
</FrameLayout>
</LinearLayout>

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:clickable="false"
android:orientation="horizontal">
<include
layout="@layout/settingslib_icon_frame"
android:layout_width="48dp"
android:layout_height="48dp"/>
<TextView
android:id="@android:id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorPrimary"
android:ellipsize="marquee"
android:fadingEdge="horizontal"/>
<ImageView
android:id="@+id/expand_icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="10dp"
android:contentDescription="@null"
android:tint="@androidprv:color/materialColorOnPrimaryContainer"
android:src="@drawable/ic_keyboard_arrow_down"/>
</LinearLayout>

View File

@@ -50,8 +50,6 @@
<attr name="android:textAppearance" />
</declare-styleable>
<attr name="apnPreferenceStyle" format="reference" />
<attr name="slicePreferenceStyle" format="reference" />
<attr name="cardPreferenceStyle" format="reference" />

View File

@@ -164,6 +164,22 @@
<string name="bluetooth_hearing_aids_presets_empty_list_message">There are no presets programmed by your audiologist</string>
<!-- Message when selecting hearing aids presets failed. [CHAR LIMIT=NONE] -->
<string name="bluetooth_hearing_aids_presets_error">Couldn\u2019t update preset</string>
<!-- Connected devices settings. Title for ambient volume control which controls the remote device's microphone input volume. [CHAR LIMIT=60] -->
<string name="bluetooth_ambient_volume_control">Surroundings</string>
<!-- Connected devices settings. Content description for the icon to expand the unified ambient volume control to left and right separated controls. [CHAR LIMIT=NONE] -->
<string name="bluetooth_ambient_volume_control_expand">Expand to left and right separated controls</string>
<!-- Connected devices settings. Content description for the icon to collapse the left and right separated ambient volume controls to unified control. [CHAR LIMIT=NONE] -->
<string name="bluetooth_ambient_volume_control_collapse">Collapse to unified control</string>
<!-- Connected devices settings. The text to show the control is for left side device. [CHAR LIMIT=30] -->
<string name="bluetooth_ambient_volume_control_left">Left</string>
<!-- Connected devices settings. The text to show the control is for right side device. [CHAR LIMIT=30] -->
<string name="bluetooth_ambient_volume_control_right">Right</string>
<!-- Connected devices settings. Content description for a button, that mute ambient volume [CHAR_LIMIT=NONE] -->
<string name="bluetooth_ambient_volume_mute">Mute surroundings</string>
<!-- Connected devices settings. Content description for a button, that unmute ambient volume [CHAR LIMIT=NONE] -->
<string name="bluetooth_ambient_volume_unmute">Unmute surroundings</string>
<!-- Message when changing ambient state failed. [CHAR LIMIT=NONE] -->
<string name="bluetooth_ambient_volume_error">Couldn\u2019t update surroundings</string>
<!-- Connected devices settings. Title of the preference to show the entrance of the audio output page. It can change different types of audio are played on phone or other bluetooth devices. [CHAR LIMIT=35] -->
<string name="bluetooth_audio_routing_title">Audio output</string>
<!-- Title for bluetooth audio routing page footer. [CHAR LIMIT=30] -->
@@ -4514,6 +4530,8 @@
<string name="language_settings">Languages&#160;&amp; input</string>
<!-- Title of setting on main settings screen. This item will take the user to the screen to tweak settings related to languages -->
<string name="languages_settings">Languages</string>
<!-- Title of setting on main settings screen. This item will take the user to the screen to tweak settings related to language and region-->
<string name="language_and_region_settings">Language &amp; region</string>
<!-- Title of setting on main settings screen. This item will take the user to the screen to tweak settings related to keyboards -->
<string name="keyboard_settings">Keyboard</string>
<!-- Text displayed when user has restriction DISALLOW_CONFIG_LOCALE [CHAR LIMIT=NONE]-->

View File

@@ -20,7 +20,6 @@
<resources>
<style name="SettingsPreferenceTheme" parent="@style/PreferenceTheme.SettingsLib">
<item name="apnPreferenceStyle">@style/ApnPreference</item>
<item name="cardPreferenceStyle">@style/CardPreference</item>
<item name="slicePreferenceStyle">@style/SlicePreference</item>
<item name="seekBarPreferenceStyle">@style/SettingsSeekBarPreference</item>
@@ -32,10 +31,6 @@
<item name="preferenceFragmentCompatStyle">@style/SetupWizardPreferenceFragmentStyle</item>
</style>
<style name="ApnPreference" parent="@style/Preference.Material">
<item name="android:layout">@layout/apn_preference_layout</item>
</style>
<style name="CardPreference" parent="@style/Preference.Material">
<item name="android:layout">@layout/card_preference_layout</item>
</style>

View File

@@ -20,7 +20,6 @@
<resources>
<style name="SettingsPreferenceTheme.Expressive" parent="@style/PreferenceTheme.SettingsLib.Expressive">
<item name="apnPreferenceStyle">@style/ApnPreference</item>
<item name="cardPreferenceStyle">@style/CardPreference</item>
<item name="slicePreferenceStyle">@style/SlicePreference</item>
<item name="seekBarPreferenceStyle">@style/SettingsSeekBarPreference</item>

View File

@@ -20,11 +20,15 @@
android:key="app_data_usage_screen"
android:title="@string/data_usage_app_summary_title">
<com.android.settingslib.widget.IntroPreference
android:key="app_header"
android:order="-10000"/>
<com.android.settings.datausage.SpinnerPreference
android:key="cycle"
settings:controller="com.android.settings.datausage.AppDataUsageCycleController" />
<com.android.settings.spa.preference.ComposePreference
<com.android.settings.spa.preference.ComposeGroupSectionPreference
android:key="app_data_usage_summary"
settings:controller="com.android.settings.datausage.AppDataUsageSummaryController"/>

View File

@@ -42,7 +42,7 @@
android:order="-7"
android:title="@string/nfc_quick_toggle_title"
settings:controller="com.android.settings.connecteddevice.NfcAndPaymentFragmentController"
settings:searchable="false"
settings:searchable="true"
settings:useAdminDisabledSummary="true"
settings:userRestriction="no_near_field_communication_radio" />

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project
<!-- Copyright (C) 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -94,10 +94,6 @@
android:key="time_format_preference_category"
android:title="@string/time_format_category_title"
settings:keywords="@string/keywords_time_format">
<SwitchPreferenceCompat
android:key="auto_24hour"
android:title="@string/date_time_24hour_auto"
settings:controller="com.android.settings.datetime.AutoTimeFormatPreferenceController" />
<SwitchPreferenceCompat
android:key="24 hour"

View File

@@ -1,104 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/date_and_time"
settings:keywords="@string/keywords_date_and_time">
<com.android.settingslib.RestrictedSwitchPreference
android:key="auto_time"
android:title="@string/date_time_auto"
android:summary="@string/summary_placeholder"
settings:userRestriction="no_config_date_time"
settings:controller="com.android.settings.datetime.AutoTimePreferenceController" />
<com.android.settingslib.RestrictedPreference
android:key="date"
android:title="@string/date_time_set_date_title"
android:summary="@string/summary_placeholder"
settings:userRestriction="no_config_date_time"
settings:controller="com.android.settings.datetime.DatePreferenceController" />
<com.android.settingslib.RestrictedPreference
android:key="time"
android:title="@string/date_time_set_time_title"
android:summary="@string/summary_placeholder"
settings:userRestriction="no_config_date_time"
settings:controller="com.android.settings.datetime.TimePreferenceController" />
<PreferenceCategory
android:key="timezone_preference_category"
android:title="@string/date_time_set_timezone_title">
<com.android.settingslib.RestrictedSwitchPreference
android:key="auto_zone"
android:title="@string/zone_auto_title"
android:summary="@string/summary_placeholder"
settings:userRestriction="no_config_date_time"
settings:controller="com.android.settings.datetime.AutoTimeZonePreferenceController" />
<com.android.settingslib.widget.BannerMessagePreference
android:key="location_time_zone_detection_status"
android:title="@string/location_time_zone_detection_status_title"
settings:controller="com.android.settings.datetime.LocationProviderStatusPreferenceController"/>
<!-- This preference gets removed if location-based time zone detection is not supported -->
<SwitchPreferenceCompat
android:key="location_time_zone_detection"
android:title="@string/location_time_zone_detection_toggle_title"
android:summary="@string/summary_placeholder"
settings:controller="com.android.settings.datetime.LocationTimeZoneDetectionPreferenceController"/>
<com.android.settingslib.RestrictedPreference
android:key="timezone"
android:title="@string/date_time_set_timezone_title"
android:summary="@string/summary_placeholder"
android:fragment="com.android.settings.datetime.timezone.TimeZoneSettings"
settings:userRestriction="no_config_date_time"
settings:keywords="@string/keywords_time_zone"
settings:controller="com.android.settings.datetime.TimeZonePreferenceController" />
</PreferenceCategory>
<!-- An optional preference category for feedback. Only displayed up if enabled via flags and config. -->
<PreferenceCategory
android:key="time_feedback_preference_category"
android:title="@string/time_feedback_category_title"
settings:keywords="@string/keywords_time_feedback_category"
settings:controller="com.android.settings.datetime.TimeFeedbackPreferenceCategoryController">
<Preference
android:key="time_feedback"
android:title="@string/time_feedback_title"
settings:keywords="@string/keywords_time_feedback"
settings:controller="com.android.settings.datetime.TimeFeedbackPreferenceController" />
</PreferenceCategory>
<PreferenceCategory
android:key="time_format_preference_category"
android:title="@string/time_format_category_title"
settings:keywords="@string/keywords_time_format">
<SwitchPreferenceCompat
android:key="24 hour"
android:title="@string/date_time_24hour"
settings:controller="com.android.settings.datetime.TimeFormatPreferenceController" />
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -0,0 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/language_and_region_settings"
android:key="language_and_region_settings">
<PreferenceCategory
android:key="languages_category"
android:title="@string/locale_picker_category_title">
<Preference
android:key="phone_language"
android:title="@string/system_language"
android:fragment="com.android.settings.localepicker.LocaleListEditor"
settings:controller="com.android.settings.language.PhoneLanguagePreferenceController" />
</PreferenceCategory>
<PreferenceCategory
android:key="more_language_settings_category"
android:title="@string/more_language_settings_category"
settings:controller="com.android.settings.language.MoreLanguagesSettingsCategoryController">
<Preference
android:key="apps_language_in_more_language_settings"
android:title="@string/app_locales_picker_menu_title"
android:summary="@string/app_locale_picker_summary"
android:fragment="com.android.settings.applications.manageapplications.ManageApplications"
settings:controller="com.android.settings.applications.appinfo.NewAppsLocalePreferenceController">
<extra
android:name="classname"
android:value="com.android.settings.applications.appinfo.AppLocaleDetails" />
</Preference>
</PreferenceCategory>
<PreferenceCategory
android:key="regional_preferences_category"
android:title="@string/regional_preferences_category_title"
settings:controller="com.android.settings.regionalpreferences.RegionalPreferencesCategoryController">
<Preference
android:key="temperature_preference"
android:title="@string/temperature_preferences_title"
android:summary="@string/default_string_of_regional_preference"
settings:controller="com.android.settings.regionalpreferences.NewTemperatureUnitController"
settings:fragment="com.android.settings.regionalpreferences.TemperatureUnitFragment">
<extra
android:name="arg_key_regional_preference"
android:value="mu"/>
</Preference>
<Preference
android:key="key_measurement_system"
android:title="@string/measurement_system_preferences_title"
android:summary="@string/default_string_of_regional_preference"
settings:controller="com.android.settings.regionalpreferences.MeasurementSystemController"
settings:fragment="com.android.settings.regionalpreferences.MeasurementSystemItemFragment">
<extra
android:name="arg_key_regional_preference"
android:value="ms"/>
</Preference>
<Preference
android:key="first_day_of_week_preference"
android:title="@string/first_day_of_week_preferences_title"
android:summary="@string/default_string_of_regional_preference"
settings:controller="com.android.settings.regionalpreferences.NewFirstDayOfWeekController"
settings:fragment="com.android.settings.regionalpreferences.FirstDayOfWeekItemFragment">
<extra
android:name="arg_key_regional_preference"
android:value="fw"/>
</Preference>
<Preference
android:key="numbering_system_preference"
android:title="@string/numbers_preferences_title"
android:summary="@string/default_string_of_regional_preference"
settings:controller="com.android.settings.regionalpreferences.NewNumberingSystemController"
settings:fragment="com.android.settings.regionalpreferences.NumberingPreferencesFragment">
<extra
android:name="arg_key_regional_preference"
android:value="arg_value_language_select"/>
</Preference>
</PreferenceCategory>
<PreferenceCategory
android:key="speech_category"
android:title="@string/speech_category_title">
<com.android.settings.widget.GearPreference
android:key="voice_input_settings"
android:title="@string/voice_input_settings_title"
android:fragment="com.android.settings.language.DefaultVoiceInputPicker" />
<Preference
android:key="on_device_recognition_settings"
android:title="@string/on_device_recognition_settings_title"
android:summary="@string/on_device_recognition_settings_summary"
settings:controller=
"com.android.settings.language.OnDeviceRecognitionPreferenceController" />
<Preference
android:key="tts_settings_summary"
android:title="@string/tts_settings_title"
android:fragment="com.android.settings.tts.TextToSpeechSettings"
settings:searchable="false"/>
</PreferenceCategory>
<com.android.settingslib.widget.FooterPreference
android:key="new_regional_pref_footer"
android:title="@string/title_regional_pref_footer"
android:selectable="false"
settings:searchable="false"
settings:controller="com.android.settings.regionalpreferences.NewRegionalFooterPreferenceController"/>
</PreferenceScreen>

View File

@@ -49,72 +49,6 @@
</PreferenceCategory>
<PreferenceCategory
android:key="more_language_settings_category"
android:title="@string/more_language_settings_category"
settings:controller="com.android.settings.language.MoreLanguagesSettingsCategoryController">
<Preference
android:key="apps_language_in_more_language_settings"
android:title="@string/app_locales_picker_menu_title"
android:summary="@string/app_locale_picker_summary"
android:fragment="com.android.settings.applications.manageapplications.ManageApplications"
settings:controller="com.android.settings.applications.appinfo.NewAppsLocalePreferenceController">
<extra
android:name="classname"
android:value="com.android.settings.applications.appinfo.AppLocaleDetails" />
</Preference>
</PreferenceCategory>
<PreferenceCategory
android:key="regional_preferences_category"
android:title="@string/regional_preferences_category_title"
settings:controller="com.android.settings.regionalpreferences.RegionalPreferencesCategoryController">
<Preference
android:key="temperature_preference"
android:title="@string/temperature_preferences_title"
android:summary="@string/default_string_of_regional_preference"
settings:controller="com.android.settings.regionalpreferences.NewTemperatureUnitController"
settings:fragment="com.android.settings.regionalpreferences.TemperatureUnitFragment">
<extra
android:name="arg_key_regional_preference"
android:value="mu"/>
</Preference>
<Preference
android:key="key_measurement_system"
android:title="@string/measurement_system_preferences_title"
android:summary="@string/default_string_of_regional_preference"
settings:controller="com.android.settings.regionalpreferences.MeasurementSystemController"
settings:fragment="com.android.settings.regionalpreferences.MeasurementSystemItemFragment">
<extra
android:name="arg_key_regional_preference"
android:value="ms"/>
</Preference>
<Preference
android:key="first_day_of_week_preference"
android:title="@string/first_day_of_week_preferences_title"
android:summary="@string/default_string_of_regional_preference"
settings:controller="com.android.settings.regionalpreferences.NewFirstDayOfWeekController"
settings:fragment="com.android.settings.regionalpreferences.FirstDayOfWeekItemFragment">
<extra
android:name="arg_key_regional_preference"
android:value="fw"/>
</Preference>
<Preference
android:key="numbering_system_preference"
android:title="@string/numbers_preferences_title"
android:summary="@string/default_string_of_regional_preference"
settings:controller="com.android.settings.regionalpreferences.NewNumberingSystemController"
settings:fragment="com.android.settings.regionalpreferences.NumberingPreferencesFragment">
<extra
android:name="arg_key_regional_preference"
android:value="arg_value_language_select"/>
</Preference>
</PreferenceCategory>
<PreferenceCategory
android:key="speech_category"
android:title="@string/speech_category_title">
@@ -136,11 +70,4 @@
android:fragment="com.android.settings.tts.TextToSpeechSettings"
settings:searchable="false"/>
</PreferenceCategory>
<com.android.settingslib.widget.FooterPreference
android:key="new_regional_pref_footer"
android:title="@string/title_regional_pref_footer"
android:selectable="false"
settings:searchable="false"
settings:controller="com.android.settings.regionalpreferences.NewRegionalFooterPreferenceController"/>
</PreferenceScreen>

View File

@@ -18,7 +18,7 @@
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="mobile_network_pref_screen">
<com.android.settings.spa.preference.ComposeMainSwitchPreference
<com.android.settings.spa.preference.ComposeGroupSectionPreference
android:key="use_sim_switch"
settings:controller="com.android.settings.network.telephony.MobileNetworkSwitchController"/>

View File

@@ -29,6 +29,15 @@
android:fragment="com.android.settings.language.LanguageSettings"
settings:controller="com.android.settings.language.LanguagePreferenceController"/>
<Preference
android:key="language_and_region_settings"
android:title="@string/language_and_region_settings"
android:summary="@string/languages_setting_summary"
android:icon="@drawable/ic_settings_languages"
android:order="-260"
android:fragment="com.android.settings.language.LanguageAndRegionSettings"
settings:controller="com.android.settings.language.LanguageAndRegionPreferenceController"/>
<Preference
android:key="Keyboard_settings"
android:title="@string/keyboard_settings"

View File

@@ -43,7 +43,6 @@ import com.android.settings.applications.AppInfoBase;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.EmptyTextSettings;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.widget.AppPreference;
import java.text.Collator;
import java.util.ArrayList;
@@ -160,7 +159,7 @@ public class PictureInPictureSettings extends EmptyTextSettings {
final String packageName = appInfo.packageName;
final CharSequence label = appInfo.loadLabel(mPackageManager);
final Preference pref = new AppPreference(prefContext);
final Preference pref = new Preference(prefContext);
pref.setIcon(mIconDrawableFactory.getBadgedIcon(appInfo, userId));
pref.setTitle(mPackageManager.getUserBadgedLabel(label, user));
pref.setSummary(PictureInPictureDetails.getPreferenceSummary(prefContext,

View File

@@ -0,0 +1,307 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.bluetooth;
import static android.view.View.GONE;
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO;
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
import static android.view.View.VISIBLE;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
import android.content.Context;
import android.util.ArrayMap;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settings.widget.SeekBarPreference;
import com.google.common.primitives.Ints;
import java.util.List;
import java.util.Map;
/**
* A preference group of ambient volume controls.
*
* <p> It consists of a header with an expand icon and volume sliders for unified control and
* separated control for devices in the same set. Toggle the expand icon will make the UI switch
* between unified and separated control.
*/
public class AmbientVolumePreference extends PreferenceGroup {
/** Interface definition for a callback to be invoked when the icon is clicked. */
public interface OnIconClickListener {
/** Called when the expand icon is clicked. */
void onExpandIconClick();
/** Called when the ambient volume icon is clicked. */
void onAmbientVolumeIconClick();
};
static final float ROTATION_COLLAPSED = 0f;
static final float ROTATION_EXPANDED = 180f;
static final int AMBIENT_VOLUME_LEVEL_MIN = 0;
static final int AMBIENT_VOLUME_LEVEL_MAX = 24;
static final int AMBIENT_VOLUME_LEVEL_DEFAULT = 24;
static final int SIDE_UNIFIED = 999;
static final List<Integer> VALID_SIDES = List.of(SIDE_UNIFIED, SIDE_LEFT, SIDE_RIGHT);
@Nullable
private OnIconClickListener mListener;
@Nullable
private View mExpandIcon;
@Nullable
private ImageView mVolumeIcon;
private boolean mExpandable = true;
private boolean mExpanded = false;
private boolean mMutable = false;
private boolean mMuted = false;
private Map<Integer, SeekBarPreference> mSideToSliderMap = new ArrayMap<>();
/**
* Ambient volume level for hearing device ambient control icon
* <p>
* This icon visually represents the current ambient gain setting.
* It displays separate levels for the left and right sides, each with 5 levels ranging from 0
* to 4.
* <p>
* To represent the combined left/right levels with a single value, the following calculation
* is used:
* finalLevel = (leftLevel * 5) + rightLevel
* For example:
* <ul>
* <li>If left level is 2 and right level is 3, the final level will be 13 (2 * 5 + 3)</li>
* <li>If both left and right levels are 0, the final level will be 0</li>
* <li>If both left and right levels are 4, the final level will be 24</li>
* </ul>
*/
private int mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;
public AmbientVolumePreference(@NonNull Context context) {
super(context, null);
setLayoutResource(R.layout.preference_ambient_volume);
setIcon(com.android.settingslib.R.drawable.ic_ambient_volume);
setTitle(R.string.bluetooth_ambient_volume_control);
setSelectable(false);
}
@Override
public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
holder.setDividerAllowedAbove(false);
holder.setDividerAllowedBelow(false);
mVolumeIcon = holder.itemView.requireViewById(com.android.internal.R.id.icon);
mVolumeIcon.getDrawable().mutate().setTint(getContext().getColor(
com.android.internal.R.color.materialColorOnPrimaryContainer));
final View iconView = holder.itemView.requireViewById(R.id.icon_frame);
iconView.setOnClickListener(v -> {
if (!mMutable) {
return;
}
setMuted(!mMuted);
if (mListener != null) {
mListener.onAmbientVolumeIconClick();
}
});
updateVolumeIcon();
mExpandIcon = holder.itemView.requireViewById(R.id.expand_icon);
mExpandIcon.setOnClickListener(v -> {
setExpanded(!mExpanded);
if (mListener != null) {
mListener.onExpandIconClick();
}
});
updateExpandIcon();
}
void setExpandable(boolean expandable) {
mExpandable = expandable;
if (!mExpandable) {
setExpanded(false);
}
updateExpandIcon();
}
boolean isExpandable() {
return mExpandable;
}
void setExpanded(boolean expanded) {
if (!mExpandable && expanded) {
return;
}
mExpanded = expanded;
updateExpandIcon();
updateLayout();
}
boolean isExpanded() {
return mExpanded;
}
void setMutable(boolean mutable) {
mMutable = mutable;
if (!mMutable) {
mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;
setMuted(false);
}
updateVolumeIcon();
}
boolean isMutable() {
return mMutable;
}
void setMuted(boolean muted) {
if (!mMutable && muted) {
return;
}
mMuted = muted;
if (mMutable && mMuted) {
for (SeekBarPreference slider : mSideToSliderMap.values()) {
slider.setProgress(slider.getMin());
}
}
updateVolumeIcon();
}
boolean isMuted() {
return mMuted;
}
void setOnIconClickListener(@Nullable OnIconClickListener listener) {
mListener = listener;
}
void setSliders(Map<Integer, SeekBarPreference> sideToSliderMap) {
mSideToSliderMap = sideToSliderMap;
for (SeekBarPreference preference : sideToSliderMap.values()) {
if (findPreference(preference.getKey()) == null) {
addPreference(preference);
}
}
updateLayout();
}
void setSliderEnabled(int side, boolean enabled) {
SeekBarPreference slider = mSideToSliderMap.get(side);
if (slider != null && slider.isEnabled() != enabled) {
slider.setEnabled(enabled);
updateLayout();
}
}
void setSliderValue(int side, int value) {
SeekBarPreference slider = mSideToSliderMap.get(side);
if (slider != null && slider.getProgress() != value) {
slider.setProgress(value);
updateVolumeLevel();
}
}
void setSliderRange(int side, int min, int max) {
SeekBarPreference slider = mSideToSliderMap.get(side);
if (slider != null) {
slider.setMin(min);
slider.setMax(max);
}
}
void updateLayout() {
mSideToSliderMap.forEach((side, slider) -> {
if (side == SIDE_UNIFIED) {
slider.setVisible(!mExpanded);
} else {
slider.setVisible(mExpanded);
}
if (!slider.isEnabled()) {
slider.setProgress(slider.getMin());
}
});
updateVolumeLevel();
}
private void updateVolumeLevel() {
int leftLevel, rightLevel;
if (mExpanded) {
leftLevel = getVolumeLevel(SIDE_LEFT);
rightLevel = getVolumeLevel(SIDE_RIGHT);
} else {
final int unifiedLevel = getVolumeLevel(SIDE_UNIFIED);
leftLevel = unifiedLevel;
rightLevel = unifiedLevel;
}
mVolumeLevel = Ints.constrainToRange(leftLevel * 5 + rightLevel,
AMBIENT_VOLUME_LEVEL_MIN, AMBIENT_VOLUME_LEVEL_MAX);
updateVolumeIcon();
}
private int getVolumeLevel(int side) {
SeekBarPreference slider = mSideToSliderMap.get(side);
if (slider == null || !slider.isEnabled()) {
return 0;
}
final double min = slider.getMin();
final double max = slider.getMax();
final double levelGap = (max - min) / 4.0;
final int value = slider.getProgress();
return (int) Math.ceil((value - min) / levelGap);
}
private void updateExpandIcon() {
if (mExpandIcon == null) {
return;
}
mExpandIcon.setVisibility(mExpandable ? VISIBLE : GONE);
mExpandIcon.setRotation(mExpanded ? ROTATION_EXPANDED : ROTATION_COLLAPSED);
if (mExpandable) {
final int stringRes = mExpanded
? R.string.bluetooth_ambient_volume_control_collapse
: R.string.bluetooth_ambient_volume_control_expand;
mExpandIcon.setContentDescription(getContext().getString(stringRes));
} else {
mExpandIcon.setContentDescription(null);
}
}
private void updateVolumeIcon() {
if (mVolumeIcon == null) {
return;
}
mVolumeIcon.setImageLevel(mMuted ? 0 : mVolumeLevel);
if (mMutable) {
final int stringRes = mMuted
? R.string.bluetooth_ambient_volume_unmute
: R.string.bluetooth_ambient_volume_mute;
mVolumeIcon.setContentDescription(getContext().getString(stringRes));
mVolumeIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
} else {
mVolumeIcon.setContentDescription(null);
mVolumeIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
}
}

View File

@@ -0,0 +1,614 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.bluetooth;
import static android.bluetooth.AudioInputControl.MUTE_NOT_MUTED;
import static android.bluetooth.AudioInputControl.MUTE_MUTED;
import static android.bluetooth.BluetoothDevice.BOND_BONDED;
import static com.android.settings.bluetooth.AmbientVolumePreference.SIDE_UNIFIED;
import static com.android.settings.bluetooth.AmbientVolumePreference.VALID_SIDES;
import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP;
import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.ORDER_AMBIENT_VOLUME;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_INVALID;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
import static com.android.settingslib.bluetooth.HearingDeviceLocalDataManager.Data.INVALID_VOLUME;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.util.ArraySet;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.widget.SeekBarPreference;
import com.android.settingslib.bluetooth.AmbientVolumeController;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HearingDeviceLocalDataManager;
import com.android.settingslib.bluetooth.HearingDeviceLocalDataManager.Data;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.VolumeControlProfile;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
import com.android.settingslib.utils.ThreadUtils;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import java.util.Map;
import java.util.Set;
/** A {@link BluetoothDetailsController} that manages ambient volume control preferences. */
public class BluetoothDetailsAmbientVolumePreferenceController extends
BluetoothDetailsController implements Preference.OnPreferenceChangeListener,
HearingDeviceLocalDataManager.OnDeviceLocalDataChangeListener, OnStart, OnStop,
AmbientVolumeController.AmbientVolumeControlCallback, BluetoothCallback {
private static final boolean DEBUG = true;
private static final String TAG = "AmbientPrefController";
static final String KEY_AMBIENT_VOLUME = "ambient_volume";
static final String KEY_AMBIENT_VOLUME_SLIDER = "ambient_volume_slider";
private static final int ORDER_AMBIENT_VOLUME_CONTROL_UNIFIED = 0;
private static final int ORDER_AMBIENT_VOLUME_CONTROL_SEPARATED = 1;
private final LocalBluetoothManager mBluetoothManager;
private final Set<CachedBluetoothDevice> mCachedDevices = new ArraySet<>();
private final BiMap<Integer, BluetoothDevice> mSideToDeviceMap = HashBiMap.create();
private final BiMap<Integer, SeekBarPreference> mSideToSliderMap = HashBiMap.create();
private final HearingDeviceLocalDataManager mLocalDataManager;
private final AmbientVolumeController mVolumeController;
@Nullable
private PreferenceCategory mDeviceControls;
@Nullable
private AmbientVolumePreference mPreference;
@Nullable
private Toast mToast;
public BluetoothDetailsAmbientVolumePreferenceController(@NonNull Context context,
@NonNull LocalBluetoothManager manager,
@NonNull PreferenceFragmentCompat fragment,
@NonNull CachedBluetoothDevice device,
@NonNull Lifecycle lifecycle) {
super(context, fragment, device, lifecycle);
mBluetoothManager = manager;
mLocalDataManager = new HearingDeviceLocalDataManager(context);
mLocalDataManager.setOnDeviceLocalDataChangeListener(this,
ThreadUtils.getBackgroundExecutor());
mVolumeController = new AmbientVolumeController(manager.getProfileManager(), this);
}
@VisibleForTesting
BluetoothDetailsAmbientVolumePreferenceController(@NonNull Context context,
@NonNull LocalBluetoothManager manager,
@NonNull PreferenceFragmentCompat fragment,
@NonNull CachedBluetoothDevice device,
@NonNull Lifecycle lifecycle,
@NonNull HearingDeviceLocalDataManager localSettings,
@NonNull AmbientVolumeController volumeController) {
super(context, fragment, device, lifecycle);
mBluetoothManager = manager;
mLocalDataManager = localSettings;
mVolumeController = volumeController;
}
@Override
protected void init(PreferenceScreen screen) {
mDeviceControls = screen.findPreference(KEY_HEARING_DEVICE_GROUP);
if (mDeviceControls == null) {
return;
}
loadDevices();
}
@Override
public void onStart() {
ThreadUtils.postOnBackgroundThread(() -> {
mBluetoothManager.getEventManager().registerCallback(this);
mLocalDataManager.start();
mCachedDevices.forEach(device -> {
device.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
mVolumeController.registerCallback(ThreadUtils.getBackgroundExecutor(),
device.getDevice());
});
});
}
@Override
public void onResume() {
refresh();
}
@Override
public void onPause() {
}
@Override
public void onStop() {
ThreadUtils.postOnBackgroundThread(() -> {
mBluetoothManager.getEventManager().unregisterCallback(this);
mLocalDataManager.stop();
mCachedDevices.forEach(device -> {
device.unregisterCallback(this);
mVolumeController.unregisterCallback(device.getDevice());
});
});
}
@Override
protected void refresh() {
if (!isAvailable()) {
return;
}
boolean shouldShowAmbientControl = isAmbientControlAvailable();
if (shouldShowAmbientControl) {
if (mPreference != null) {
mPreference.setVisible(true);
}
loadRemoteDataToUi();
} else {
if (mPreference != null) {
mPreference.setVisible(false);
}
}
}
@Override
public boolean isAvailable() {
return mCachedDevice.getProfiles().stream().anyMatch(
profile -> profile instanceof VolumeControlProfile);
}
@Nullable
@Override
public String getPreferenceKey() {
return KEY_AMBIENT_VOLUME;
}
@Override
public boolean onPreferenceChange(@NonNull Preference preference, @Nullable Object newValue) {
if (preference instanceof SeekBarPreference && newValue instanceof final Integer value) {
final int side = mSideToSliderMap.inverse().getOrDefault(preference, SIDE_INVALID);
if (DEBUG) {
Log.d(TAG, "onPreferenceChange: side=" + side + ", value=" + value);
}
setVolumeIfValid(side, value);
Runnable setAmbientRunnable = () -> {
if (side == SIDE_UNIFIED) {
mSideToDeviceMap.forEach((s, d) -> mVolumeController.setAmbient(d, value));
} else {
final BluetoothDevice device = mSideToDeviceMap.get(side);
mVolumeController.setAmbient(device, value);
}
};
if (isControlMuted()) {
// User drag on the volume slider when muted. Unmute the devices first.
if (mPreference != null) {
mPreference.setMuted(false);
}
for (BluetoothDevice device : mSideToDeviceMap.values()) {
mVolumeController.setMuted(device, false);
}
// Restore the value before muted
loadLocalDataToUi();
// Delay set ambient on remote device since the immediately sequential command
// might get failed sometimes
mContext.getMainThreadHandler().postDelayed(setAmbientRunnable, 1000L);
} else {
setAmbientRunnable.run();
}
return true;
}
return false;
}
@Override
public void onProfileConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
int state, int bluetoothProfile) {
if (bluetoothProfile == BluetoothProfile.VOLUME_CONTROL
&& state == BluetoothProfile.STATE_CONNECTED
&& mCachedDevices.contains(cachedDevice)) {
// After VCP connected, AICS may not ready yet and still return invalid value, delay
// a while to wait AICS ready as a workaround
mContext.getMainThreadHandler().postDelayed(this::refresh, 1000L);
}
}
@Override
public void onDeviceAttributesChanged() {
mCachedDevices.forEach(device -> {
device.unregisterCallback(this);
mVolumeController.unregisterCallback(device.getDevice());
});
mContext.getMainExecutor().execute(() -> {
loadDevices();
if (!mCachedDevices.isEmpty()) {
refresh();
}
ThreadUtils.postOnBackgroundThread(() ->
mCachedDevices.forEach(device -> {
device.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
mVolumeController.registerCallback(ThreadUtils.getBackgroundExecutor(),
device.getDevice());
})
);
});
}
@Override
public void onDeviceLocalDataChange(@NonNull String address, @Nullable Data data) {
if (data == null) {
// The local data is removed because the device is unpaired, do nothing
return;
}
for (BluetoothDevice device : mSideToDeviceMap.values()) {
if (device.getAnonymizedAddress().equals(address)) {
mContext.getMainExecutor().execute(() -> loadLocalDataToUi(device));
return;
}
}
}
@Override
public void onVolumeControlServiceConnected() {
mCachedDevices.forEach(
device -> mVolumeController.registerCallback(ThreadUtils.getBackgroundExecutor(),
device.getDevice()));
}
@Override
public void onAmbientChanged(@NonNull BluetoothDevice device, int gainSettings) {
if (DEBUG) {
Log.d(TAG, "onAmbientChanged, value:" + gainSettings + ", device:" + device);
}
Data data = mLocalDataManager.get(device);
boolean isInitiatedFromUi = (isControlExpanded() && data.ambient() == gainSettings)
|| (!isControlExpanded() && data.groupAmbient() == gainSettings);
if (isInitiatedFromUi) {
// The change is initiated from UI, no need to update UI
return;
}
// We have to check if we need to expand the controls by getting all remote
// device's ambient value, delay for a while to wait all remote devices update
// to the latest value to avoid unnecessary expand action.
mContext.getMainThreadHandler().postDelayed(this::refresh, 1200L);
}
@Override
public void onMuteChanged(@NonNull BluetoothDevice device, int mute) {
if (DEBUG) {
Log.d(TAG, "onMuteChanged, mute:" + mute + ", device:" + device);
}
boolean isInitiatedFromUi = (isControlMuted() && mute == MUTE_MUTED)
|| (!isControlMuted() && mute == MUTE_NOT_MUTED);
if (isInitiatedFromUi) {
// The change is initiated from UI, no need to update UI
return;
}
// We have to check if we need to mute the devices by getting all remote
// device's mute state, delay for a while to wait all remote devices update
// to the latest value.
mContext.getMainThreadHandler().postDelayed(this::refresh, 1200L);
}
@Override
public void onCommandFailed(@NonNull BluetoothDevice device) {
Log.w(TAG, "onCommandFailed, device:" + device);
mContext.getMainExecutor().execute(() -> {
showErrorToast();
refresh();
});
}
private void loadDevices() {
mSideToDeviceMap.clear();
mCachedDevices.clear();
if (VALID_SIDES.contains(mCachedDevice.getDeviceSide())
&& mCachedDevice.getBondState() == BOND_BONDED) {
mSideToDeviceMap.put(mCachedDevice.getDeviceSide(), mCachedDevice.getDevice());
mCachedDevices.add(mCachedDevice);
}
for (CachedBluetoothDevice memberDevice : mCachedDevice.getMemberDevice()) {
if (VALID_SIDES.contains(memberDevice.getDeviceSide())
&& memberDevice.getBondState() == BOND_BONDED) {
mSideToDeviceMap.put(memberDevice.getDeviceSide(), memberDevice.getDevice());
mCachedDevices.add(memberDevice);
}
}
createAmbientVolumePreference();
createSliderPreferences();
if (mPreference != null) {
mPreference.setExpandable(mSideToDeviceMap.size() > 1);
mPreference.setSliders((mSideToSliderMap));
}
}
private void createAmbientVolumePreference() {
if (mPreference != null || mDeviceControls == null) {
return;
}
mPreference = new AmbientVolumePreference(mDeviceControls.getContext());
mPreference.setKey(KEY_AMBIENT_VOLUME);
mPreference.setOrder(ORDER_AMBIENT_VOLUME);
mPreference.setOnIconClickListener(
new AmbientVolumePreference.OnIconClickListener() {
@Override
public void onExpandIconClick() {
mSideToDeviceMap.forEach((s, d) -> {
if (!isControlMuted()) {
// Apply previous collapsed/expanded volume to remote device
Data data = mLocalDataManager.get(d);
int volume = isControlExpanded()
? data.ambient() : data.groupAmbient();
mVolumeController.setAmbient(d, volume);
}
// Update new value to local data
mLocalDataManager.updateAmbientControlExpanded(d, isControlExpanded());
});
}
@Override
public void onAmbientVolumeIconClick() {
if (!isControlMuted()) {
loadLocalDataToUi();
}
for (BluetoothDevice device : mSideToDeviceMap.values()) {
mVolumeController.setMuted(device, isControlMuted());
}
}
});
if (mDeviceControls.findPreference(mPreference.getKey()) == null) {
mDeviceControls.addPreference(mPreference);
}
}
private void createSliderPreferences() {
mSideToDeviceMap.forEach((s, d) ->
createSliderPreference(s, ORDER_AMBIENT_VOLUME_CONTROL_SEPARATED + s));
createSliderPreference(SIDE_UNIFIED, ORDER_AMBIENT_VOLUME_CONTROL_UNIFIED);
}
private void createSliderPreference(int side, int order) {
if (mSideToSliderMap.containsKey(side) || mDeviceControls == null) {
return;
}
SeekBarPreference preference = new SeekBarPreference(mDeviceControls.getContext());
preference.setKey(KEY_AMBIENT_VOLUME_SLIDER + "_" + side);
preference.setOrder(order);
preference.setOnPreferenceChangeListener(this);
if (side == SIDE_LEFT) {
preference.setTitle(mContext.getString(R.string.bluetooth_ambient_volume_control_left));
} else if (side == SIDE_RIGHT) {
preference.setTitle(
mContext.getString(R.string.bluetooth_ambient_volume_control_right));
}
mSideToSliderMap.put(side, preference);
}
/** Refreshes the control UI visibility and enabled state. */
private void refreshControlUi() {
if (mPreference != null) {
boolean isAnySliderEnabled = false;
for (Map.Entry<Integer, BluetoothDevice> entry : mSideToDeviceMap.entrySet()) {
final int side = entry.getKey();
final BluetoothDevice device = entry.getValue();
final boolean enabled = isDeviceConnectedToVcp(device)
&& mVolumeController.isAmbientControlAvailable(device);
isAnySliderEnabled |= enabled;
mPreference.setSliderEnabled(side, enabled);
}
mPreference.setSliderEnabled(SIDE_UNIFIED, isAnySliderEnabled);
mPreference.updateLayout();
}
}
/** Sets the volume to the corresponding control slider. */
private void setVolumeIfValid(int side, int volume) {
if (volume == INVALID_VOLUME) {
return;
}
if (mPreference != null) {
mPreference.setSliderValue(side, volume);
}
// Update new value to local data
if (side == SIDE_UNIFIED) {
mSideToDeviceMap.forEach((s, d) -> mLocalDataManager.updateGroupAmbient(d, volume));
} else {
mLocalDataManager.updateAmbient(mSideToDeviceMap.get(side), volume);
}
}
private void loadLocalDataToUi() {
mSideToDeviceMap.forEach((s, d) -> loadLocalDataToUi(d));
}
private void loadLocalDataToUi(BluetoothDevice device) {
final Data data = mLocalDataManager.get(device);
if (DEBUG) {
Log.d(TAG, "loadLocalDataToUi, data=" + data + ", device=" + device);
}
final int side = mSideToDeviceMap.inverse().getOrDefault(device, SIDE_INVALID);
if (isDeviceConnectedToVcp(device) && !isControlMuted()) {
setVolumeIfValid(side, data.ambient());
setVolumeIfValid(SIDE_UNIFIED, data.groupAmbient());
}
setControlExpanded(data.ambientControlExpanded());
refreshControlUi();
}
private void loadRemoteDataToUi() {
BluetoothDevice leftDevice = mSideToDeviceMap.get(SIDE_LEFT);
AmbientVolumeController.RemoteAmbientState leftState =
mVolumeController.refreshAmbientState(leftDevice);
BluetoothDevice rightDevice = mSideToDeviceMap.get(SIDE_RIGHT);
AmbientVolumeController.RemoteAmbientState rightState =
mVolumeController.refreshAmbientState(rightDevice);
if (DEBUG) {
Log.d(TAG, "loadRemoteDataToUi, left=" + leftState + ", right=" + rightState);
}
if (mPreference != null) {
mSideToDeviceMap.forEach((side, device) -> {
int ambientMax = mVolumeController.getAmbientMax(device);
int ambientMin = mVolumeController.getAmbientMin(device);
if (ambientMin != ambientMax) {
mPreference.setSliderRange(side, ambientMin, ambientMax);
mPreference.setSliderRange(SIDE_UNIFIED, ambientMin, ambientMax);
}
});
}
// Update ambient volume
final int leftAmbient = leftState != null ? leftState.gainSetting() : INVALID_VOLUME;
final int rightAmbient = rightState != null ? rightState.gainSetting() : INVALID_VOLUME;
if (isControlExpanded()) {
setVolumeIfValid(SIDE_LEFT, leftAmbient);
setVolumeIfValid(SIDE_RIGHT, rightAmbient);
} else {
if (leftAmbient != rightAmbient && leftAmbient != INVALID_VOLUME
&& rightAmbient != INVALID_VOLUME) {
setVolumeIfValid(SIDE_LEFT, leftAmbient);
setVolumeIfValid(SIDE_RIGHT, rightAmbient);
setControlExpanded(true);
} else {
int unifiedAmbient = leftAmbient != INVALID_VOLUME ? leftAmbient : rightAmbient;
setVolumeIfValid(SIDE_UNIFIED, unifiedAmbient);
}
}
// Initialize local data between side and group value
initLocalDataIfNeeded();
// Update mute state
boolean mutable = true;
boolean muted = true;
if (isDeviceConnectedToVcp(leftDevice) && leftState != null) {
mutable &= leftState.isMutable();
muted &= leftState.isMuted();
}
if (isDeviceConnectedToVcp(rightDevice) && rightState != null) {
mutable &= rightState.isMutable();
muted &= rightState.isMuted();
}
if (mPreference != null) {
mPreference.setMutable(mutable);
mPreference.setMuted(muted);
}
// Ensure remote device mute state is synced
syncMuteStateIfNeeded(leftDevice, leftState, muted);
syncMuteStateIfNeeded(rightDevice, rightState, muted);
refreshControlUi();
}
/** Check if any device in the group has valid ambient control points */
private boolean isAmbientControlAvailable() {
for (BluetoothDevice device : mSideToDeviceMap.values()) {
// Found ambient local data for this device, show the ambient control
if (mLocalDataManager.get(device).hasAmbientData()) {
return true;
}
// Found remote ambient control points on this device, show the ambient control
if (mVolumeController.isAmbientControlAvailable(device)) {
return true;
}
}
return false;
}
private boolean isControlExpanded() {
return mPreference != null && mPreference.isExpanded();
}
private void setControlExpanded(boolean expanded) {
if (mPreference != null && mPreference.isExpanded() != expanded) {
mPreference.setExpanded(expanded);
}
mSideToDeviceMap.forEach((s, d) -> {
// Update new value to local data
mLocalDataManager.updateAmbientControlExpanded(d, expanded);
});
}
private boolean isControlMuted() {
return mPreference != null && mPreference.isMuted();
}
private void initLocalDataIfNeeded() {
int smallerVolumeAmongGroup = Integer.MAX_VALUE;
for (BluetoothDevice device : mSideToDeviceMap.values()) {
Data data = mLocalDataManager.get(device);
if (data.ambient() != INVALID_VOLUME) {
smallerVolumeAmongGroup = Math.min(data.ambient(), smallerVolumeAmongGroup);
} else if (data.groupAmbient() != INVALID_VOLUME) {
// Initialize side ambient from group ambient value
mLocalDataManager.updateAmbient(device, data.groupAmbient());
}
}
if (smallerVolumeAmongGroup != Integer.MAX_VALUE) {
for (BluetoothDevice device : mSideToDeviceMap.values()) {
Data data = mLocalDataManager.get(device);
if (data.groupAmbient() == INVALID_VOLUME) {
// Initialize group ambient from smaller side ambient value
mLocalDataManager.updateGroupAmbient(device, smallerVolumeAmongGroup);
}
}
}
}
private void syncMuteStateIfNeeded(@Nullable BluetoothDevice device,
@Nullable AmbientVolumeController.RemoteAmbientState state, boolean muted) {
if (isDeviceConnectedToVcp(device) && state != null && state.isMutable()) {
if (state.isMuted() != muted) {
mVolumeController.setMuted(device, muted);
}
}
}
private boolean isDeviceConnectedToVcp(@Nullable BluetoothDevice device) {
return device != null && device.isConnected()
&& mBluetoothManager.getProfileManager().getVolumeControlProfile()
.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED;
}
private void showErrorToast() {
if (mToast != null) {
mToast.cancel();
}
mToast = Toast.makeText(mContext, R.string.bluetooth_ambient_volume_error,
Toast.LENGTH_SHORT);
mToast.show();
}
}

View File

@@ -42,6 +42,7 @@ public class BluetoothDetailsHearingDeviceController extends BluetoothDetailsCon
public static final int ORDER_HEARING_DEVICE_SETTINGS = 1;
public static final int ORDER_HEARING_AIDS_PRESETS = 2;
public static final int ORDER_AMBIENT_VOLUME = 4;
static final String KEY_HEARING_DEVICE_GROUP = "hearing_device_group";
private final List<BluetoothDetailsController> mControllers = new ArrayList<>();
@@ -107,6 +108,10 @@ public class BluetoothDetailsHearingDeviceController extends BluetoothDetailsCon
mControllers.add(new BluetoothDetailsHearingAidsPresetsController(mContext, mFragment,
mManager, mCachedDevice, mLifecycle));
}
if (com.android.settingslib.flags.Flags.hearingDevicesAmbientVolumeControl()) {
mControllers.add(new BluetoothDetailsAmbientVolumePreferenceController(mContext,
mManager, mFragment, mCachedDevice, mLifecycle));
}
}
@NonNull

View File

@@ -18,6 +18,7 @@ import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
import static com.android.settings.datausage.lib.AppDataUsageRepository.getAppUid;
import static com.android.settings.datausage.lib.AppDataUsageRepository.getAppUidList;
import static com.android.settings.spa.app.appinfo.AppInfoSettingsProvider.startAppInfoSettings;
import android.app.Activity;
import android.app.settings.SettingsEnums;
@@ -45,13 +46,14 @@ import com.android.settings.datausage.lib.AppDataUsageDetailsRepository;
import com.android.settings.datausage.lib.NetworkTemplates;
import com.android.settings.fuelgauge.datasaver.DynamicDenylistManager;
import com.android.settings.network.SubscriptionUtil;
import com.android.settings.widget.EntityHeaderController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.AppItem;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.net.UidDetail;
import com.android.settingslib.net.UidDetailProvider;
import com.android.settingslib.widget.IntroPreference;
import kotlin.Unit;
@@ -65,6 +67,8 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC
private static final String TAG = "AppDataUsage";
static final String ARG_APP_ITEM = "app_item";
@VisibleForTesting
static final String ARG_APP_HEADER = "app_header";
static final String ARG_NETWORK_TEMPLATE = "network_template";
static final String ARG_NETWORK_CYCLES = "network_cycles";
static final String ARG_SELECTED_CYCLE = "selected_cycle";
@@ -176,7 +180,7 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC
removePreference(KEY_RESTRICT_BACKGROUND);
}
addEntityHeader();
setupIntroPreference();
}
@Override
@@ -320,32 +324,32 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC
}
@VisibleForTesting
void addEntityHeader() {
String pkg = mPackages.size() != 0 ? mPackages.valueAt(0) : null;
int uid = 0;
if (pkg != null) {
void setupIntroPreference() {
final Preference pref = getPreferenceScreen().findPreference(ARG_APP_HEADER);
if (pref != null) {
pref.setIcon(mIcon);
pref.setTitle(mLabel);
pref.setSelectable(true);
}
}
@Override
public boolean onPreferenceTreeClick(Preference preference) {
if (!(preference instanceof IntroPreference)) return false;
String pkg = !mPackages.isEmpty() ? mPackages.valueAt(0) : null;
if (mAppItem.key > 0 && pkg != null) {
try {
uid = mPackageManager.getPackageUidAsUser(pkg,
int uid = mPackageManager.getPackageUidAsUser(pkg,
UserHandle.getUserId(mAppItem.key));
startAppInfoSettings(pkg, uid, this, 0 /* request */,
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
.getMetricsCategory(this));
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Skipping UID because cannot find package " + pkg);
}
}
final boolean showInfoButton = mAppItem.key > 0;
final Activity activity = getActivity();
final Preference pref = EntityHeaderController
.newInstance(activity, this, null /* header */)
.setUid(uid)
.setHasAppInfoLink(showInfoButton)
.setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
EntityHeaderController.ActionType.ACTION_NONE)
.setIcon(mIcon)
.setLabel(mLabel)
.setPackageName(pkg)
.done(getPrefContext());
getPreferenceScreen().addPreference(pref);
return true;
}
@Override

View File

@@ -17,7 +17,6 @@
package com.android.settings.datausage
import android.content.Context
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource
@@ -28,6 +27,7 @@ import com.android.settings.datausage.lib.NetworkUsageDetailsData
import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.ui.Category
import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
@@ -60,7 +60,7 @@ class AppDataUsageSummaryController(context: Context, preferenceKey: String) :
@Composable
override fun Content() {
Column {
Category {
val totalUsage by totalUsageFlow.collectAsStateWithLifecycle(emptyDataUsage)
val foregroundUsage by foregroundUsageFlow.collectAsStateWithLifecycle(emptyDataUsage)
val backgroundUsage by backgroundUsageFlow.collectAsStateWithLifecycle(emptyDataUsage)

View File

@@ -25,8 +25,10 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settingslib.widget.GroupSectionDividerMixin;
public class SpinnerPreference extends Preference implements CycleAdapter.SpinnerInterface {
public class SpinnerPreference extends Preference implements CycleAdapter.SpinnerInterface,
GroupSectionDividerMixin {
private CycleAdapter mAdapter;
@Nullable

View File

@@ -1,73 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.datetime;
import android.content.Context;
import android.provider.Settings;
import android.provider.Settings.System;
import android.text.format.DateFormat;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
import java.util.Locale;
public class AutoTimeFormatPreferenceController extends TogglePreferenceController {
public AutoTimeFormatPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public boolean isChecked() {
return isAutoTimeFormatSelection(mContext);
}
@Override
public boolean setChecked(boolean isChecked) {
Boolean is24Hour;
if (isChecked) {
is24Hour = null;
} else {
is24Hour = is24HourLocale(mContext.getResources().getConfiguration().locale);
}
TimeFormatPreferenceController.update24HourFormat(mContext, is24Hour);
return true;
}
@Override
public int getSliceHighlightMenuRes() {
return R.string.menu_key_system;
}
boolean is24HourLocale(Locale locale) {
return DateFormat.is24HourLocale(locale);
}
/**
* Returns if the system is currently configured to pick the time format automatically based on
* the locale.
*/
static boolean isAutoTimeFormatSelection(Context context) {
return Settings.System.getString(context.getContentResolver(), System.TIME_12_24) == null;
}
}

View File

@@ -32,7 +32,6 @@ import androidx.preference.Preference;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.flags.Flags;
public class AutoTimeZonePreferenceController extends TogglePreferenceController {
@@ -107,19 +106,17 @@ public class AutoTimeZonePreferenceController extends TogglePreferenceController
TimeZoneConfiguration.Builder configuration = new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(isChecked);
if (Flags.revampToggles()) {
// "Use location for time zone" is only used if "Automatic time zone" is enabled. If
// the user toggles off automatic time zone, set the toggle off and disable the toggle.
int geoDetectionCapability = mTimeManager
.getTimeZoneCapabilitiesAndConfig()
.getCapabilities()
.getConfigureGeoDetectionEnabledCapability();
// "Use location for time zone" is only used if "Automatic time zone" is enabled. If
// the user toggles off automatic time zone, set the toggle off and disable the toggle.
int geoDetectionCapability = mTimeManager
.getTimeZoneCapabilitiesAndConfig()
.getCapabilities()
.getConfigureGeoDetectionEnabledCapability();
if (!isChecked
&& (geoDetectionCapability == CAPABILITY_NOT_APPLICABLE
|| geoDetectionCapability == CAPABILITY_POSSESSED)) {
configuration.setGeoDetectionEnabled(false);
}
if (!isChecked
&& (geoDetectionCapability == CAPABILITY_NOT_APPLICABLE
|| geoDetectionCapability == CAPABILITY_POSSESSED)) {
configuration.setGeoDetectionEnabled(false);
}
boolean result = mTimeManager.updateTimeZoneConfiguration(configuration.build());

View File

@@ -23,7 +23,6 @@ import android.content.Context;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.flags.Flags;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
@@ -50,9 +49,6 @@ public class DateTimeSettings extends DashboardFragment implements
@Override
protected int getPreferenceScreenResId() {
if (Flags.revampToggles()) {
return R.xml.date_time_prefs_revamped;
}
return R.xml.date_time_prefs;
}
@@ -123,6 +119,5 @@ public class DateTimeSettings extends DashboardFragment implements
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(
Flags.revampToggles() ? R.xml.date_time_prefs_revamped : R.xml.date_time_prefs);
new BaseSearchIndexProvider(R.xml.date_time_prefs);
}

View File

@@ -32,7 +32,6 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.flags.Flags;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
@@ -68,7 +67,7 @@ public class LocationTimeZoneDetectionPreferenceController
// forceRefresh set to true as the location toggle may have been turned off by switching off
// automatic time zone
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
getTimeZoneCapabilitiesAndConfig(/*forceRefresh=*/ Flags.revampToggles());
getTimeZoneCapabilitiesAndConfig(/*forceRefresh=*/ true);
TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
return configuration.isGeoDetectionEnabled();
}
@@ -137,11 +136,7 @@ public class LocationTimeZoneDetectionPreferenceController
if (capability == CAPABILITY_NOT_SUPPORTED || capability == CAPABILITY_NOT_ALLOWED) {
return UNSUPPORTED_ON_DEVICE;
} else if (capability == CAPABILITY_NOT_APPLICABLE || capability == CAPABILITY_POSSESSED) {
if (Flags.revampToggles()) {
return isAutoTimeZoneEnabled() ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
} else {
return AVAILABLE;
}
return isAutoTimeZoneEnabled() ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
} else {
throw new IllegalStateException("Unknown capability=" + capability);
}
@@ -151,10 +146,8 @@ public class LocationTimeZoneDetectionPreferenceController
public void updateState(Preference preference) {
super.updateState(preference);
if (Flags.revampToggles()) {
// enable / disable the toggle based on automatic time zone being enabled or not
preference.setEnabled(isAutoTimeZoneEnabled());
}
// enable / disable the toggle based on automatic time zone being enabled or not
preference.setEnabled(isAutoTimeZoneEnabled());
}
@Override

View File

@@ -25,7 +25,6 @@ import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.flags.Flags;
import java.util.Calendar;
import java.util.Date;
@@ -73,11 +72,6 @@ public class TimeFormatPreferenceController extends TogglePreferenceController {
if (mIsFromSUW) {
return DISABLED_DEPENDENT_SETTING;
}
if (!Flags.revampToggles()) {
if (AutoTimeFormatPreferenceController.isAutoTimeFormatSelection(mContext)) {
return DISABLED_DEPENDENT_SETTING;
}
}
return AVAILABLE;
}
@@ -120,12 +114,12 @@ public class TimeFormatPreferenceController extends TogglePreferenceController {
return DateFormat.is24HourFormat(mContext);
}
static void update24HourFormat(Context context, Boolean is24Hour) {
private static void update24HourFormat(Context context, Boolean is24Hour) {
set24Hour(context, is24Hour);
timeUpdated(context, is24Hour);
}
static void timeUpdated(Context context, Boolean is24Hour) {
private static void timeUpdated(Context context, Boolean is24Hour) {
Intent timeChanged = new Intent(Intent.ACTION_TIME_CHANGED);
timeChanged.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
int timeFormatPreference;
@@ -139,9 +133,8 @@ public class TimeFormatPreferenceController extends TogglePreferenceController {
context.sendBroadcast(timeChanged);
}
static void set24Hour(Context context, Boolean is24Hour) {
String value = is24Hour == null ? null :
is24Hour ? HOURS_24 : HOURS_12;
private static void set24Hour(Context context, boolean is24Hour) {
String value = is24Hour ? HOURS_24 : HOURS_12;
Settings.System.putString(context.getContentResolver(),
Settings.System.TIME_12_24, value);
}

View File

@@ -31,16 +31,20 @@ import com.android.settings.R
import com.android.settings.Utils
import com.android.settings.core.SettingsBaseActivity
import com.android.settingslib.RestrictedPreference
import com.android.settingslib.datastore.AbstractKeyedDataObservable
import com.android.settingslib.datastore.DataChangeReason
import com.android.settingslib.datastore.HandlerExecutor
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.KeyedObserver
import com.android.settingslib.datastore.SettingsSystemStore
import com.android.settingslib.display.BrightnessUtils.GAMMA_SPACE_MAX
import com.android.settingslib.display.BrightnessUtils.GAMMA_SPACE_MIN
import com.android.settingslib.display.BrightnessUtils.convertLinearToGammaFloat
import com.android.settingslib.metadata.PreferenceLifecycleContext
import com.android.settingslib.metadata.PreferenceLifecycleProvider
import com.android.settingslib.metadata.PersistentPreference
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceSummaryProvider
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel
import com.android.settingslib.preference.PreferenceBinding
import com.android.settingslib.transition.SettingsTransitionHelper
import java.text.NumberFormat
@@ -48,15 +52,12 @@ import java.text.NumberFormat
// LINT.IfChange
class BrightnessLevelPreference :
PreferenceMetadata,
PersistentPreference<Float>,
PreferenceBinding,
PreferenceRestrictionMixin,
PreferenceSummaryProvider,
PreferenceLifecycleProvider,
Preference.OnPreferenceClickListener {
private var brightnessObserver: KeyedObserver<String>? = null
private var displayListener: DisplayListener? = null
override val key: String
get() = KEY
@@ -67,7 +68,7 @@ class BrightnessLevelPreference :
get() = R.string.keywords_display_brightness_level
override fun getSummary(context: Context): CharSequence? =
NumberFormat.getPercentInstance().format(getCurrentBrightness(context))
NumberFormat.getPercentInstance().format(context.brightness)
override fun isEnabled(context: Context) = super<PreferenceRestrictionMixin>.isEnabled(context)
@@ -77,91 +78,113 @@ class BrightnessLevelPreference :
override val useAdminDisabledSummary: Boolean
get() = true
override fun intent(context: Context) =
Intent(ACTION_SHOW_BRIGHTNESS_DIALOG)
.setPackage(Utils.SYSTEMUI_PACKAGE_NAME)
.putExtra(
SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
SettingsTransitionHelper.TransitionType.TRANSITION_NONE,
)
.putExtra(EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH, true)
override fun createWidget(context: Context) = RestrictedPreference(context)
override fun bind(preference: Preference, metadata: PreferenceMetadata) {
super.bind(preference, metadata)
preference.onPreferenceClickListener = this
preference.isPersistent = false
}
override fun onStart(context: PreferenceLifecycleContext) {
val observer = KeyedObserver<String> { _, _ -> context.notifyPreferenceChange(KEY) }
brightnessObserver = observer
SettingsSystemStore.get(context)
.addObserver(System.SCREEN_AUTO_BRIGHTNESS_ADJ, observer, HandlerExecutor.main)
override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
val listener =
object : DisplayListener {
override fun onDisplayAdded(displayId: Int) {}
override fun getWritePermit(context: Context, value: Float?, callingPid: Int, callingUid: Int) =
ReadWritePermit.DISALLOW
override fun onDisplayRemoved(displayId: Int) {}
override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY
override fun onDisplayChanged(displayId: Int) {
context.notifyPreferenceChange(KEY)
}
}
displayListener = listener
context.displayManager.registerDisplayListener(
listener,
HandlerExecutor.main,
/* eventFlags= */ 0,
DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS,
)
}
override fun storage(context: Context): KeyValueStore = BrightnessStorage(context)
override fun onStop(context: PreferenceLifecycleContext) {
brightnessObserver?.let {
SettingsSystemStore.get(context).removeObserver(System.SCREEN_AUTO_BRIGHTNESS_ADJ, it)
brightnessObserver = null
private class BrightnessStorage(private val context: Context) :
AbstractKeyedDataObservable<String>(),
KeyValueStore,
KeyedObserver<String>,
DisplayListener {
override fun contains(key: String) = key == KEY
@Suppress("UNCHECKED_CAST")
override fun <T : Any> getValue(key: String, valueType: Class<T>) =
context.brightness.toFloat() as T
override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {}
override fun onFirstObserverAdded() {
SettingsSystemStore.get(context)
.addObserver(System.SCREEN_AUTO_BRIGHTNESS_ADJ, this, HandlerExecutor.main)
context.displayManager.registerDisplayListener(
this,
HandlerExecutor.main,
/* eventFlags= */ 0,
DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS,
)
}
displayListener?.let {
context.displayManager.unregisterDisplayListener(it)
displayListener = null
override fun onLastObserverRemoved() {
SettingsSystemStore.get(context).removeObserver(System.SCREEN_AUTO_BRIGHTNESS_ADJ, this)
context.displayManager.unregisterDisplayListener(this)
}
override fun onKeyChanged(key: String, reason: Int) {
notifyChange(KEY, DataChangeReason.UPDATE)
}
override fun onDisplayAdded(displayId: Int) {}
override fun onDisplayRemoved(displayId: Int) {}
override fun onDisplayChanged(displayId: Int) {
notifyChange(KEY, DataChangeReason.UPDATE)
}
}
private val Context.displayManager: DisplayManager
get() = getSystemService(DisplayManager::class.java)!!
override fun onPreferenceClick(preference: Preference): Boolean {
val context = preference.context
val intent =
Intent(ACTION_SHOW_BRIGHTNESS_DIALOG)
.setPackage(Utils.SYSTEMUI_PACKAGE_NAME)
.putExtra(
SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
SettingsTransitionHelper.TransitionType.TRANSITION_NONE,
)
.putExtra(EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH, true)
val options =
ActivityOptions.makeCustomAnimation(
context,
android.R.anim.fade_in,
android.R.anim.fade_out,
)
context.startActivityForResult(preference.key, intent, 0, options.toBundle())
context.startActivityForResult(preference.key, intent(context), 0, options.toBundle())
return true
}
private fun getCurrentBrightness(context: Context): Double {
val info: BrightnessInfo? = context.display.brightnessInfo
val value =
info?.run {
convertLinearToGammaFloat(brightness, brightnessMinimum, brightnessMaximum)
}
return getPercentage(value?.toDouble() ?: 0.0)
}
private fun getPercentage(value: Double): Double =
when {
value > GAMMA_SPACE_MAX -> 1.0
value < GAMMA_SPACE_MIN -> 0.0
else -> (value - GAMMA_SPACE_MIN) / (GAMMA_SPACE_MAX - GAMMA_SPACE_MIN)
}
companion object {
const val KEY = "brightness"
private val Context.displayManager: DisplayManager
get() = getSystemService(DisplayManager::class.java)!!
private val Context.brightness: Double
get() {
val info: BrightnessInfo? = display.brightnessInfo
val value =
info?.run {
convertLinearToGammaFloat(brightness, brightnessMinimum, brightnessMaximum)
}
return getPercentage(value?.toDouble() ?: 0.0)
}
private fun getPercentage(value: Double): Double =
when {
value > GAMMA_SPACE_MAX -> 1.0
value < GAMMA_SPACE_MIN -> 0.0
else -> (value - GAMMA_SPACE_MIN) / (GAMMA_SPACE_MAX - GAMMA_SPACE_MIN)
}
}
}
// LINT.ThenChange(BrightnessLevelPreferenceController.java)

View File

@@ -118,6 +118,7 @@ public final class BatteryUsageDataLoader {
final BatteryLevelData batteryLevelData =
DataProcessManager.getBatteryLevelData(
context,
null,
userIdsSeries,
/* isFromPeriodJob= */ true,
batteryDiffDataMap -> {

View File

@@ -18,12 +18,12 @@ package com.android.settings.fuelgauge.batteryusage;
import android.app.usage.UsageEvents;
import android.content.Context;
import android.os.AsyncTask;
import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
@@ -80,6 +80,7 @@ public class DataProcessManager {
private final long mLastFullChargeTimestamp;
private final boolean mIsFromPeriodJob;
private final Context mContext;
private final @Nullable Lifecycle mLifecycle;
private final UserIdsSeries mUserIdsSeries;
private final OnBatteryDiffDataMapLoadedListener mCallbackFunction;
private final List<AppUsageEvent> mAppUsageEventList = new ArrayList<>();
@@ -120,6 +121,7 @@ public class DataProcessManager {
/** Constructor when there exists battery level data. */
DataProcessManager(
Context context,
@Nullable Lifecycle lifecycle,
final UserIdsSeries userIdsSeries,
final boolean isFromPeriodJob,
final long rawStartTimestamp,
@@ -128,6 +130,7 @@ public class DataProcessManager {
@NonNull final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
@NonNull final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
mContext = context.getApplicationContext();
mLifecycle = lifecycle;
mUserIdsSeries = userIdsSeries;
mIsFromPeriodJob = isFromPeriodJob;
mRawStartTimestamp = rawStartTimestamp;
@@ -140,9 +143,11 @@ public class DataProcessManager {
/** Constructor when there is no battery level data. */
DataProcessManager(
Context context,
@Nullable Lifecycle lifecycle,
final UserIdsSeries userIdsSeries,
@NonNull final OnBatteryDiffDataMapLoadedListener callbackFunction) {
mContext = context.getApplicationContext();
mLifecycle = lifecycle;
mUserIdsSeries = userIdsSeries;
mCallbackFunction = callbackFunction;
mIsFromPeriodJob = false;
@@ -223,7 +228,7 @@ public class DataProcessManager {
}
private void loadCurrentBatteryHistoryMap() {
new AsyncTask<Void, Void, Map<String, BatteryHistEntry>>() {
new LifecycleAwareAsyncTask<Map<String, BatteryHistEntry>>(mLifecycle) {
@Override
protected Map<String, BatteryHistEntry> doInBackground(Void... voids) {
final long startTime = System.currentTimeMillis();
@@ -242,6 +247,7 @@ public class DataProcessManager {
@Override
protected void onPostExecute(
final Map<String, BatteryHistEntry> currentBatteryHistoryMap) {
super.onPostExecute(currentBatteryHistoryMap);
if (mBatteryHistoryMap != null) {
// Replaces the placeholder in mBatteryHistoryMap.
for (Map.Entry<Long, Map<String, BatteryHistEntry>> mapEntry :
@@ -256,11 +262,11 @@ public class DataProcessManager {
mIsCurrentBatteryHistoryLoaded = true;
tryToGenerateFinalDataAndApplyCallback();
}
}.execute();
}.start();
}
private void loadCurrentAppUsageList() {
new AsyncTask<Void, Void, List<AppUsageEvent>>() {
new LifecycleAwareAsyncTask<List<AppUsageEvent>>(mLifecycle) {
@Override
@Nullable
protected List<AppUsageEvent> doInBackground(Void... voids) {
@@ -299,6 +305,7 @@ public class DataProcessManager {
@Override
protected void onPostExecute(final List<AppUsageEvent> currentAppUsageList) {
super.onPostExecute(currentAppUsageList);
if (currentAppUsageList == null || currentAppUsageList.isEmpty()) {
Log.d(TAG, "currentAppUsageList is null or empty");
} else {
@@ -307,11 +314,11 @@ public class DataProcessManager {
mIsCurrentAppUsageLoaded = true;
tryToProcessAppUsageData();
}
}.execute();
}.start();
}
private void loadDatabaseAppUsageList() {
new AsyncTask<Void, Void, List<AppUsageEvent>>() {
new LifecycleAwareAsyncTask<List<AppUsageEvent>>(mLifecycle) {
@Override
protected List<AppUsageEvent> doInBackground(Void... voids) {
if (!shouldLoadAppUsageData()) {
@@ -337,6 +344,7 @@ public class DataProcessManager {
@Override
protected void onPostExecute(final List<AppUsageEvent> databaseAppUsageList) {
super.onPostExecute(databaseAppUsageList);
if (databaseAppUsageList == null || databaseAppUsageList.isEmpty()) {
Log.d(TAG, "databaseAppUsageList is null or empty");
} else {
@@ -345,11 +353,11 @@ public class DataProcessManager {
mIsDatabaseAppUsageLoaded = true;
tryToProcessAppUsageData();
}
}.execute();
}.start();
}
private void loadPowerConnectionBatteryEventList() {
new AsyncTask<Void, Void, List<BatteryEvent>>() {
new LifecycleAwareAsyncTask<List<BatteryEvent>>(mLifecycle) {
@Override
protected List<BatteryEvent> doInBackground(Void... voids) {
final long startTime = System.currentTimeMillis();
@@ -370,6 +378,7 @@ public class DataProcessManager {
@Override
protected void onPostExecute(final List<BatteryEvent> batteryEventList) {
super.onPostExecute(batteryEventList);
if (batteryEventList == null || batteryEventList.isEmpty()) {
Log.d(TAG, "batteryEventList is null or empty");
} else {
@@ -379,11 +388,11 @@ public class DataProcessManager {
mIsBatteryEventLoaded = true;
tryToProcessAppUsageData();
}
}.execute();
}.start();
}
private void loadBatteryUsageSlotList() {
new AsyncTask<Void, Void, List<BatteryUsageSlot>>() {
new LifecycleAwareAsyncTask<List<BatteryUsageSlot>>(mLifecycle) {
@Override
protected List<BatteryUsageSlot> doInBackground(Void... voids) {
final long startTime = System.currentTimeMillis();
@@ -402,6 +411,7 @@ public class DataProcessManager {
@Override
protected void onPostExecute(final List<BatteryUsageSlot> batteryUsageSlotList) {
super.onPostExecute(batteryUsageSlotList);
if (batteryUsageSlotList == null || batteryUsageSlotList.isEmpty()) {
Log.d(TAG, "batteryUsageSlotList is null or empty");
} else {
@@ -411,11 +421,11 @@ public class DataProcessManager {
mIsBatteryUsageSlotLoaded = true;
tryToGenerateFinalDataAndApplyCallback();
}
}.execute();
}.start();
}
private void loadAndApplyBatteryMapFromServiceOnly() {
new AsyncTask<Void, Void, Map<Long, BatteryDiffData>>() {
new LifecycleAwareAsyncTask<Map<Long, BatteryDiffData>>(mLifecycle) {
@Override
protected Map<Long, BatteryDiffData> doInBackground(Void... voids) {
final long startTime = System.currentTimeMillis();
@@ -437,11 +447,12 @@ public class DataProcessManager {
@Override
protected void onPostExecute(final Map<Long, BatteryDiffData> batteryDiffDataMap) {
super.onPostExecute(batteryDiffDataMap);
if (mCallbackFunction != null) {
mCallbackFunction.onBatteryDiffDataMapLoaded(batteryDiffDataMap);
}
}
}.execute();
}.start();
}
private void tryToProcessAppUsageData() {
@@ -481,7 +492,7 @@ public class DataProcessManager {
}
private synchronized void generateFinalDataAndApplyCallback() {
new AsyncTask<Void, Void, Map<Long, BatteryDiffData>>() {
new LifecycleAwareAsyncTask<Map<Long, BatteryDiffData>>(mLifecycle) {
@Override
protected Map<Long, BatteryDiffData> doInBackground(Void... voids) {
final long startTime = System.currentTimeMillis();
@@ -523,11 +534,12 @@ public class DataProcessManager {
@Override
protected void onPostExecute(final Map<Long, BatteryDiffData> batteryDiffDataMap) {
super.onPostExecute(batteryDiffDataMap);
if (mCallbackFunction != null) {
mCallbackFunction.onBatteryDiffDataMapLoaded(batteryDiffDataMap);
}
}
}.execute();
}.start();
}
// Whether we should load app usage data from service or database.
@@ -566,6 +578,7 @@ public class DataProcessManager {
@Nullable
public static BatteryLevelData getBatteryLevelData(
Context context,
@Nullable Lifecycle lifecycle,
final UserIdsSeries userIdsSeries,
final boolean isFromPeriodJob,
final OnBatteryDiffDataMapLoadedListener onBatteryUsageMapLoadedListener) {
@@ -585,6 +598,7 @@ public class DataProcessManager {
final BatteryLevelData batteryLevelData =
getPeriodBatteryLevelData(
context,
lifecycle,
userIdsSeries,
startTimestamp,
lastFullChargeTime,
@@ -604,6 +618,7 @@ public class DataProcessManager {
private static BatteryLevelData getPeriodBatteryLevelData(
Context context,
@Nullable Lifecycle lifecycle,
final UserIdsSeries userIdsSeries,
final long startTimestamp,
final long lastFullChargeTime,
@@ -631,7 +646,8 @@ public class DataProcessManager {
lastFullChargeTime);
if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
Log.d(TAG, "batteryHistoryMap is null in getPeriodBatteryLevelData()");
new DataProcessManager(context, userIdsSeries, onBatteryDiffDataMapLoadedListener)
new DataProcessManager(context, lifecycle, userIdsSeries,
onBatteryDiffDataMapLoadedListener)
.start();
return null;
}
@@ -660,7 +676,8 @@ public class DataProcessManager {
DataProcessor.getLevelDataThroughProcessedHistoryMap(
context, processedBatteryHistoryMap);
if (batteryLevelData == null) {
new DataProcessManager(context, userIdsSeries, onBatteryDiffDataMapLoadedListener)
new DataProcessManager(context, lifecycle, userIdsSeries,
onBatteryDiffDataMapLoadedListener)
.start();
Log.d(TAG, "getBatteryLevelData() returns null");
return null;
@@ -669,6 +686,7 @@ public class DataProcessManager {
// Start the async task to compute diff usage data and load labels and icons.
new DataProcessManager(
context,
lifecycle,
userIdsSeries,
isFromPeriodJob,
startTimestamp,

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batteryusage
import android.os.AsyncTask
import androidx.annotation.CallSuper
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import com.android.settingslib.datastore.HandlerExecutor.Companion.main as mainExecutor
/**
* Lifecycle aware [AsyncTask] to cancel task automatically when [lifecycle] is stopped.
*
* Must call [start] instead of [execute] to run the task.
*/
abstract class LifecycleAwareAsyncTask<Result>(private val lifecycle: Lifecycle?) :
AsyncTask<Void, Void, Result>(), DefaultLifecycleObserver {
@CallSuper
override fun onPostExecute(result: Result) {
lifecycle?.removeObserver(this)
}
override fun onStop(owner: LifecycleOwner) {
cancel(false)
lifecycle?.removeObserver(this)
}
/**
* Starts the task, which invokes [execute] (cannot override [execute] as it is final).
*
* This method is expected to be invoked from main thread but current usage might call from
* background thread.
*/
fun start() {
execute() // expects main thread
val lifecycle = lifecycle ?: return
mainExecutor.execute {
// Status is updated to FINISHED if onPoseExecute happened before. And task is cancelled
// if lifecycle is stopped.
if (status == Status.RUNNING && !isCancelled) {
lifecycle.addObserver(this) // requires main thread
}
}
}
}

View File

@@ -503,9 +503,11 @@ public class PowerUsageAdvanced extends PowerUsageBase {
@Override
public BatteryLevelData loadInBackground() {
Context context = getContext();
return DataProcessManager.getBatteryLevelData(
getContext(),
new UserIdsSeries(getContext(), /* isNonUIRequest= */ false),
context,
getLifecycle(),
new UserIdsSeries(context, /* isNonUIRequest= */ false),
/* isFromPeriodJob= */ false,
PowerUsageAdvanced.this::onBatteryDiffDataMapUpdate);
}

View File

@@ -22,9 +22,12 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.internal.annotations.Initializer;
import com.android.settings.applications.defaultapps.DefaultAppPreferenceController;
import com.android.settingslib.applications.DefaultAppInfo;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -43,7 +46,8 @@ public class DefaultVoiceInputPreferenceController extends DefaultAppPreferenceC
private Preference mPreference;
private Context mContext;
public DefaultVoiceInputPreferenceController(Context context, Lifecycle lifecycle) {
public DefaultVoiceInputPreferenceController(
@NonNull Context context, @Nullable Lifecycle lifecycle) {
super(context);
mContext = context;
mHelper = new VoiceInputHelper(context);
@@ -65,6 +69,7 @@ public class DefaultVoiceInputPreferenceController extends DefaultAppPreferenceC
}
@Override
@Initializer
public void displayPreference(PreferenceScreen screen) {
mScreen = screen;
mPreference = screen.findPreference(getPreferenceKey());

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.language;
import android.content.Context;
import androidx.annotation.NonNull;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.flags.Flags;
/**
* This is a display controller for new language activity entry.
* TODO(b/379962955): When new layout is on board, all old layouts should be removed.
*/
public class LanguageAndRegionPreferenceController extends BasePreferenceController {
public LanguageAndRegionPreferenceController(@NonNull Context context, @NonNull String key) {
super(context, key);
}
@Override
public int getAvailabilityStatus() {
if (!Flags.regionalPreferencesApiEnabled()) {
return CONDITIONALLY_UNAVAILABLE;
}
// TODO: Add setComponentEnabledSetting after LanguageAndRegionSettingsActivity is created.
return AVAILABLE;
}
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.language;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.flags.Flags;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.PreferenceCategoryController;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.List;
@SearchIndexable
public class LanguageAndRegionSettings extends DashboardFragment {
private static final String KEY_SPEECH_CATEGORY = "speech_category";
private static final String KEY_ON_DEVICE_RECOGNITION = "on_device_recognition_settings";
private static final String KEY_TEXT_TO_SPEECH = "tts_settings_summary";
private static final String TAG = "LanguageAndRegionSettings";
@Override
public int getMetricsCategory() {
return SettingsEnums.SETTINGS_LANGUAGES_CATEGORY;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
public void onResume() {
super.onResume();
// Hack to update action bar title. It's necessary to refresh title because this page user
// can change locale from here and fragment won't relaunch. Once language changes, title
// must display in the new language.
final Activity activity = getActivity();
if (activity == null) {
return;
}
activity.setTitle(R.string.languages_settings);
}
@Override
public @Nullable String getPreferenceScreenBindingKey(@NonNull Context context) {
return LanguageSettingScreen.KEY;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.language_and_region_settings;
}
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
return buildPreferenceControllers(context, getSettingsLifecycle());
}
private static List<AbstractPreferenceController> buildPreferenceControllers(
@NonNull Context context, @Nullable Lifecycle lifecycle) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
final DefaultVoiceInputPreferenceController defaultVoiceInputPreferenceController =
new DefaultVoiceInputPreferenceController(context, lifecycle);
final TtsPreferenceController ttsPreferenceController =
new TtsPreferenceController(context, KEY_TEXT_TO_SPEECH);
final OnDeviceRecognitionPreferenceController onDeviceRecognitionPreferenceController =
new OnDeviceRecognitionPreferenceController(context, KEY_ON_DEVICE_RECOGNITION);
controllers.add(defaultVoiceInputPreferenceController);
controllers.add(ttsPreferenceController);
List<AbstractPreferenceController> speechCategoryChildren = new ArrayList<>(
List.of(defaultVoiceInputPreferenceController, ttsPreferenceController));
if (onDeviceRecognitionPreferenceController.isAvailable()) {
controllers.add(onDeviceRecognitionPreferenceController);
speechCategoryChildren.add(onDeviceRecognitionPreferenceController);
}
controllers.add(new PreferenceCategoryController(context, KEY_SPEECH_CATEGORY)
.setChildren(speechCategoryChildren));
return controllers;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.language_and_region_settings) {
@Override
@NonNull
public List<AbstractPreferenceController> createPreferenceControllers(
@NonNull Context context) {
return buildPreferenceControllers(context, null);
}
@Override
protected boolean isPageSearchEnabled(Context context) {
if (Flags.regionalPreferencesApiEnabled()) {
return true;
}
return false;
}
};
}

View File

@@ -22,6 +22,7 @@ import android.content.pm.PackageManager;
import com.android.settings.Settings;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.flags.Flags;
/**
* This is a display controller for new language activity entry.
@@ -34,6 +35,10 @@ public class LanguagePreferenceController extends BasePreferenceController {
@Override
public int getAvailabilityStatus() {
if (Flags.regionalPreferencesApiEnabled()) {
setActivityEnabled(mContext, Settings.LanguageSettingsActivity.class, false);
return CONDITIONALLY_UNAVAILABLE;
}
setActivityEnabled(mContext, Settings.LanguageSettingsActivity.class, true);
return AVAILABLE;
}

View File

@@ -25,6 +25,7 @@ import androidx.annotation.Nullable;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.flags.Flags;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.PreferenceCategoryController;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -116,6 +117,9 @@ public class LanguageSettings extends DashboardFragment {
}
@Override
protected boolean isPageSearchEnabled(Context context) {
if (Flags.regionalPreferencesApiEnabled()) {
return false;
}
return true;
}
};

View File

@@ -24,83 +24,67 @@ import android.content.Intent;
import android.net.Uri;
import android.provider.Telephony;
import android.telephony.SubscriptionManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.RadioButton;
import android.widget.RelativeLayout;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settings.flags.Flags;
import com.android.settings.spa.SpaActivity;
import com.android.settingslib.widget.TwoTargetPreference;
/**
* Preference of APN UI entry
*/
public class ApnPreference extends Preference
implements CompoundButton.OnCheckedChangeListener, View.OnClickListener {
public class ApnPreference extends TwoTargetPreference
implements CompoundButton.OnCheckedChangeListener, Preference.OnPreferenceClickListener {
private static final String TAG = "ApnPreference";
private boolean mIsChecked = false;
@Nullable
private RadioButton mRadioButton = null;
private RadioButton mRadioButton;
private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private boolean mProtectFromCheckedChange = false;
private boolean mDefaultSelectable = true;
private boolean mHideDetails = false;
/**
* Constructor of Preference
*/
public ApnPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// Primary target and radio button could be selectable, but entire preference itself is not
// selectable.
setSelectable(false);
}
/**
* Constructor of Preference
*/
public ApnPreference(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.apnPreferenceStyle);
}
/**
* Constructor of Preference
*/
public ApnPreference(Context context) {
this(context, null);
super(context);
setOnPreferenceClickListener(this);
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final RelativeLayout textArea = (RelativeLayout) view.findViewById(R.id.text_layout);
textArea.setOnClickListener(this);
final View radioButtonFrame = view.itemView.requireViewById(R.id.apn_radio_button_frame);
final RadioButton rb = view.itemView.requireViewById(R.id.apn_radiobutton);
mRadioButton = rb;
if (mDefaultSelectable) {
radioButtonFrame.setOnClickListener((v) -> {
rb.performClick();
});
rb.setOnCheckedChangeListener(this);
mProtectFromCheckedChange = true;
rb.setChecked(mIsChecked);
mProtectFromCheckedChange = false;
radioButtonFrame.setVisibility(View.VISIBLE);
} else {
radioButtonFrame.setVisibility(View.GONE);
final RadioButton rb = (RadioButton) holder.findViewById(android.R.id.checkbox);
final View radioButtonFrame = holder.findViewById(android.R.id.widget_frame);
if (rb == null || radioButtonFrame == null) {
throw new RuntimeException("Failed to load system layout.");
}
mRadioButton = rb;
radioButtonFrame.setOnClickListener(v -> rb.performClick());
rb.setOnCheckedChangeListener(this);
setIsChecked(mIsChecked);
rb.setVisibility(View.VISIBLE);
}
@Override
protected boolean shouldHideSecondTarget() {
return !mDefaultSelectable;
}
@Override
protected int getSecondTargetResId() {
return R.layout.preference_widget_radiobutton;
}
/**
@@ -118,6 +102,7 @@ public class ApnPreference extends Preference
/**
* Change the preference status.
*/
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Log.i(TAG, "ID: " + getKey() + " :" + isChecked);
if (mProtectFromCheckedChange) {
@@ -130,19 +115,14 @@ public class ApnPreference extends Preference
}
@Override
public void onClick(View layoutView) {
super.onClick();
public boolean onPreferenceClick(@NonNull Preference preference) {
final Context context = getContext();
final int pos = Integer.parseInt(getKey());
if (context == null) {
Log.w(TAG, "No context available for pos=" + pos);
return;
}
if (mHideDetails) {
Toast.makeText(context, context.getString(R.string.cannot_change_apn_toast),
Toast.LENGTH_LONG).show();
return;
return true;
}
final Uri url = ContentUris.withAppendedId(Telephony.Carriers.CONTENT_URI, pos);
@@ -156,6 +136,7 @@ public class ApnPreference extends Preference
editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(editIntent);
}
return true;
}
public void setDefaultSelectable(boolean defaultSelectable) {

View File

@@ -63,7 +63,7 @@ public class MeasurementSystemItemFragment extends DashboardFragment {
new BaseSearchIndexProvider(R.xml.regional_preferences_measurement_system) {
@Override
protected boolean isPageSearchEnabled(Context context) {
if (Flags.regionalPreferencesApiEnabled()) {
if (!Flags.regionalPreferencesApiEnabled()) {
return false;
}
return true;

View File

@@ -28,7 +28,7 @@ import com.android.settings.R
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.widget.GroupSectionDividerMixin
open class ComposeMainSwitchPreference @JvmOverloads constructor(
open class ComposeGroupSectionPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,

View File

@@ -0,0 +1,238 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.bluetooth;
import static com.android.settings.bluetooth.AmbientVolumePreference.ROTATION_COLLAPSED;
import static com.android.settings.bluetooth.AmbientVolumePreference.ROTATION_EXPANDED;
import static com.android.settings.bluetooth.AmbientVolumePreference.SIDE_UNIFIED;
import static com.android.settings.bluetooth.BluetoothDetailsAmbientVolumePreferenceController.KEY_AMBIENT_VOLUME;
import static com.android.settings.bluetooth.BluetoothDetailsAmbientVolumePreferenceController.KEY_AMBIENT_VOLUME_SLIDER;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.util.ArrayMap;
import android.view.View;
import android.widget.ImageView;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
import com.android.settings.widget.SeekBarPreference;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import java.util.Map;
/** Tests for {@link AmbientVolumePreference}. */
@RunWith(RobolectricTestRunner.class)
public class AmbientVolumePreferenceTest {
private static final int TEST_LEFT_VOLUME_LEVEL = 1;
private static final int TEST_RIGHT_VOLUME_LEVEL = 2;
private static final int TEST_UNIFIED_VOLUME_LEVEL = 3;
private static final String KEY_UNIFIED_SLIDER = KEY_AMBIENT_VOLUME_SLIDER + "_" + SIDE_UNIFIED;
private static final String KEY_LEFT_SLIDER = KEY_AMBIENT_VOLUME_SLIDER + "_" + SIDE_LEFT;
private static final String KEY_RIGHT_SLIDER = KEY_AMBIENT_VOLUME_SLIDER + "_" + SIDE_RIGHT;
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Spy
private Context mContext = ApplicationProvider.getApplicationContext();
@Mock
private AmbientVolumePreference.OnIconClickListener mListener;
@Mock
private View mItemView;
private AmbientVolumePreference mPreference;
private ImageView mExpandIcon;
private ImageView mVolumeIcon;
private final Map<Integer, SeekBarPreference> mSideToSlidersMap = new ArrayMap<>();
@Before
public void setUp() {
PreferenceManager preferenceManager = new PreferenceManager(mContext);
PreferenceScreen preferenceScreen = preferenceManager.createPreferenceScreen(mContext);
mPreference = new AmbientVolumePreference(mContext);
mPreference.setKey(KEY_AMBIENT_VOLUME);
mPreference.setOnIconClickListener(mListener);
mPreference.setExpandable(true);
mPreference.setMutable(true);
preferenceScreen.addPreference(mPreference);
prepareSliders();
mPreference.setSliders(mSideToSlidersMap);
mExpandIcon = new ImageView(mContext);
mVolumeIcon = new ImageView(mContext);
mVolumeIcon.setImageResource(com.android.settingslib.R.drawable.ic_ambient_volume);
mVolumeIcon.setImageLevel(0);
when(mItemView.requireViewById(R.id.expand_icon)).thenReturn(mExpandIcon);
when(mItemView.requireViewById(com.android.internal.R.id.icon)).thenReturn(mVolumeIcon);
when(mItemView.requireViewById(R.id.icon_frame)).thenReturn(mVolumeIcon);
PreferenceViewHolder preferenceViewHolder = PreferenceViewHolder.createInstanceForTests(
mItemView);
mPreference.onBindViewHolder(preferenceViewHolder);
}
@Test
public void setExpandable_expandable_expandIconVisible() {
mPreference.setExpandable(true);
assertThat(mExpandIcon.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void setExpandable_notExpandable_expandIconGone() {
mPreference.setExpandable(false);
assertThat(mExpandIcon.getVisibility()).isEqualTo(View.GONE);
}
@Test
public void setExpanded_expanded_assertControlUiCorrect() {
mPreference.setExpanded(true);
assertControlUiCorrect();
}
@Test
public void setExpanded_notExpanded_assertControlUiCorrect() {
mPreference.setExpanded(false);
assertControlUiCorrect();
}
@Test
public void setMutable_mutable_clickOnMuteIconChangeMuteState() {
mPreference.setMutable(true);
mPreference.setMuted(false);
mVolumeIcon.callOnClick();
assertThat(mPreference.isMuted()).isTrue();
}
@Test
public void setMutable_notMutable_clickOnMuteIconWontChangeMuteState() {
mPreference.setMutable(false);
mPreference.setMuted(false);
mVolumeIcon.callOnClick();
assertThat(mPreference.isMuted()).isFalse();
}
@Test
public void updateLayout_mute_volumeIconIsCorrect() {
mPreference.setMuted(true);
mPreference.updateLayout();
assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(0);
}
@Test
public void updateLayout_unmuteAndExpanded_volumeIconIsCorrect() {
mPreference.setMuted(false);
mPreference.setExpanded(true);
mPreference.updateLayout();
int expectedLevel = calculateVolumeLevel(TEST_LEFT_VOLUME_LEVEL, TEST_RIGHT_VOLUME_LEVEL);
assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
}
@Test
public void updateLayout_unmuteAndNotExpanded_volumeIconIsCorrect() {
mPreference.setMuted(false);
mPreference.setExpanded(false);
mPreference.updateLayout();
int expectedLevel = calculateVolumeLevel(TEST_UNIFIED_VOLUME_LEVEL,
TEST_UNIFIED_VOLUME_LEVEL);
assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
}
@Test
public void setSliderEnabled_expandedAndLeftIsDisabled_volumeIconIcCorrect() {
mPreference.setExpanded(true);
mPreference.setSliderEnabled(SIDE_LEFT, false);
int expectedLevel = calculateVolumeLevel(0, TEST_RIGHT_VOLUME_LEVEL);
assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
}
@Test
public void setSliderValue_expandedAndLeftValueChanged_volumeIconIcCorrect() {
mPreference.setExpanded(true);
mPreference.setSliderValue(SIDE_LEFT, 4);
int expectedLevel = calculateVolumeLevel(4, TEST_RIGHT_VOLUME_LEVEL);
assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
}
private int calculateVolumeLevel(int left, int right) {
return left * 5 + right;
}
private void assertControlUiCorrect() {
final boolean expanded = mPreference.isExpanded();
assertThat(mSideToSlidersMap.get(SIDE_UNIFIED).isVisible()).isEqualTo(!expanded);
assertThat(mSideToSlidersMap.get(SIDE_LEFT).isVisible()).isEqualTo(expanded);
assertThat(mSideToSlidersMap.get(SIDE_RIGHT).isVisible()).isEqualTo(expanded);
final float rotation = expanded ? ROTATION_EXPANDED : ROTATION_COLLAPSED;
assertThat(mExpandIcon.getRotation()).isEqualTo(rotation);
}
private void prepareSliders() {
prepareSlider(SIDE_UNIFIED);
prepareSlider(SIDE_LEFT);
prepareSlider(SIDE_RIGHT);
}
private void prepareSlider(int side) {
SeekBarPreference slider = new SeekBarPreference(mContext);
slider.setMin(0);
slider.setMax(4);
if (side == SIDE_LEFT) {
slider.setKey(KEY_LEFT_SLIDER);
slider.setProgress(TEST_LEFT_VOLUME_LEVEL);
} else if (side == SIDE_RIGHT) {
slider.setKey(KEY_RIGHT_SLIDER);
slider.setProgress(TEST_RIGHT_VOLUME_LEVEL);
} else {
slider.setKey(KEY_UNIFIED_SLIDER);
slider.setProgress(TEST_UNIFIED_VOLUME_LEVEL);
}
mSideToSlidersMap.put(side, slider);
}
}

View File

@@ -0,0 +1,434 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.bluetooth;
import static android.bluetooth.AudioInputControl.MUTE_DISABLED;
import static android.bluetooth.AudioInputControl.MUTE_NOT_MUTED;
import static android.bluetooth.AudioInputControl.MUTE_MUTED;
import static android.bluetooth.BluetoothDevice.BOND_BONDED;
import static com.android.settings.bluetooth.BluetoothDetailsAmbientVolumePreferenceController.KEY_AMBIENT_VOLUME;
import static com.android.settings.bluetooth.BluetoothDetailsAmbientVolumePreferenceController.KEY_AMBIENT_VOLUME_SLIDER;
import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.ContentResolver;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import androidx.preference.PreferenceCategory;
import com.android.settings.testutils.shadow.ShadowThreadUtils;
import com.android.settings.widget.SeekBarPreference;
import com.android.settingslib.bluetooth.AmbientVolumeController;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HearingDeviceLocalDataManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.bluetooth.VolumeControlProfile;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowSettings;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
/** Tests for {@link BluetoothDetailsAmbientVolumePreferenceController}. */
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
BluetoothDetailsAmbientVolumePreferenceControllerTest.ShadowGlobal.class,
ShadowThreadUtils.class
})
public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
BluetoothDetailsControllerTestBase {
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
private static final String LEFT_CONTROL_KEY = KEY_AMBIENT_VOLUME_SLIDER + "_" + SIDE_LEFT;
private static final String RIGHT_CONTROL_KEY = KEY_AMBIENT_VOLUME_SLIDER + "_" + SIDE_RIGHT;
private static final String TEST_ADDRESS = "00:00:00:00:11";
private static final String TEST_MEMBER_ADDRESS = "00:00:00:00:22";
@Mock
private CachedBluetoothDevice mCachedMemberDevice;
@Mock
private BluetoothDevice mDevice;
@Mock
private BluetoothDevice mMemberDevice;
@Mock
private HearingDeviceLocalDataManager mLocalDataManager;
@Mock
private LocalBluetoothManager mBluetoothManager;
@Mock
private BluetoothEventManager mEventManager;
@Mock
private LocalBluetoothProfileManager mProfileManager;
@Mock
private VolumeControlProfile mVolumeControlProfile;
@Mock
private AmbientVolumeController mVolumeController;
@Mock
private Handler mTestHandler;
private BluetoothDetailsAmbientVolumePreferenceController mController;
@Before
public void setUp() {
super.setUp();
mContext = spy(mContext);
PreferenceCategory deviceControls = new PreferenceCategory(mContext);
deviceControls.setKey(KEY_HEARING_DEVICE_GROUP);
mScreen.addPreference(deviceControls);
mController = spy(
new BluetoothDetailsAmbientVolumePreferenceController(mContext, mBluetoothManager,
mFragment, mCachedDevice, mLifecycle, mLocalDataManager,
mVolumeController));
when(mBluetoothManager.getEventManager()).thenReturn(mEventManager);
when(mBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
when(mProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControlProfile);
when(mVolumeControlProfile.getConnectionStatus(mDevice)).thenReturn(
BluetoothProfile.STATE_CONNECTED);
when(mVolumeControlProfile.getConnectionStatus(mMemberDevice)).thenReturn(
BluetoothProfile.STATE_CONNECTED);
when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile));
when(mLocalDataManager.get(any(BluetoothDevice.class))).thenReturn(
new HearingDeviceLocalDataManager.Data.Builder().build());
when(mContext.getMainThreadHandler()).thenReturn(mTestHandler);
when(mTestHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
invocationOnMock -> {
invocationOnMock.getArgument(0, Runnable.class).run();
return null;
});
}
@Test
public void init_deviceWithoutMember_controlNotExpandable() {
prepareDevice(/* hasMember= */ false);
mController.init(mScreen);
AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
assertThat(preference).isNotNull();
assertThat(preference.isExpandable()).isFalse();
}
@Test
public void init_deviceWithMember_controlExpandable() {
prepareDevice(/* hasMember= */ true);
mController.init(mScreen);
AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
assertThat(preference).isNotNull();
assertThat(preference.isExpandable()).isTrue();
}
@Test
public void onDeviceLocalDataChange_noMemberAndExpanded_uiCorrectAndDataUpdated() {
prepareDevice(/* hasMember= */ false);
mController.init(mScreen);
HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
.ambient(0).groupAmbient(0).ambientControlExpanded(true).build();
when(mLocalDataManager.get(mDevice)).thenReturn(data);
mController.onDeviceLocalDataChange(TEST_ADDRESS, data);
shadowOf(Looper.getMainLooper()).idle();
AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
assertThat(preference).isNotNull();
assertThat(preference.isExpanded()).isFalse();
verifyDeviceDataUpdated(mDevice);
}
@Test
public void onDeviceLocalDataChange_noMemberAndCollapsed_uiCorrectAndDataUpdated() {
prepareDevice(/* hasMember= */ false);
mController.init(mScreen);
HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
.ambient(0).groupAmbient(0).ambientControlExpanded(false).build();
when(mLocalDataManager.get(mDevice)).thenReturn(data);
mController.onDeviceLocalDataChange(TEST_ADDRESS, data);
shadowOf(Looper.getMainLooper()).idle();
AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
assertThat(preference).isNotNull();
assertThat(preference.isExpanded()).isFalse();
verifyDeviceDataUpdated(mDevice);
}
@Test
public void onDeviceLocalDataChange_hasMemberAndExpanded_uiCorrectAndDataUpdated() {
prepareDevice(/* hasMember= */ true);
mController.init(mScreen);
HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
.ambient(0).groupAmbient(0).ambientControlExpanded(true).build();
when(mLocalDataManager.get(mDevice)).thenReturn(data);
mController.onDeviceLocalDataChange(TEST_ADDRESS, data);
shadowOf(Looper.getMainLooper()).idle();
AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
assertThat(preference).isNotNull();
assertThat(preference.isExpanded()).isTrue();
verifyDeviceDataUpdated(mDevice);
}
@Test
public void onDeviceLocalDataChange_hasMemberAndCollapsed_uiCorrectAndDataUpdated() {
prepareDevice(/* hasMember= */ true);
mController.init(mScreen);
HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
.ambient(0).groupAmbient(0).ambientControlExpanded(false).build();
when(mLocalDataManager.get(mDevice)).thenReturn(data);
mController.onDeviceLocalDataChange(TEST_ADDRESS, data);
shadowOf(Looper.getMainLooper()).idle();
AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
assertThat(preference).isNotNull();
assertThat(preference.isExpanded()).isFalse();
verifyDeviceDataUpdated(mDevice);
}
@Test
public void onStart_localDataManagerStartAndCallbackRegistered() {
prepareDevice(/* hasMember= */ true);
mController.init(mScreen);
mController.onStart();
verify(mLocalDataManager, atLeastOnce()).start();
verify(mVolumeController).registerCallback(any(Executor.class), eq(mDevice));
verify(mVolumeController).registerCallback(any(Executor.class), eq(mMemberDevice));
verify(mCachedDevice).registerCallback(any(Executor.class),
any(CachedBluetoothDevice.Callback.class));
verify(mCachedMemberDevice).registerCallback(any(Executor.class),
any(CachedBluetoothDevice.Callback.class));
}
@Test
public void onStop_localDataManagerStopAndCallbackUnregistered() {
prepareDevice(/* hasMember= */ true);
mController.init(mScreen);
mController.onStop();
verify(mLocalDataManager).stop();
verify(mVolumeController).unregisterCallback(mDevice);
verify(mVolumeController).unregisterCallback(mMemberDevice);
verify(mCachedDevice).unregisterCallback(any(CachedBluetoothDevice.Callback.class));
verify(mCachedMemberDevice).unregisterCallback(any(CachedBluetoothDevice.Callback.class));
}
@Test
public void onDeviceAttributesChanged_newDevice_newPreference() {
prepareDevice(/* hasMember= */ false);
mController.init(mScreen);
// check the right control is null before onDeviceAttributesChanged()
SeekBarPreference leftControl = mScreen.findPreference(LEFT_CONTROL_KEY);
SeekBarPreference rightControl = mScreen.findPreference(RIGHT_CONTROL_KEY);
assertThat(leftControl).isNotNull();
assertThat(rightControl).isNull();
prepareDevice(/* hasMember= */ true);
mController.onDeviceAttributesChanged();
shadowOf(Looper.getMainLooper()).idle();
// check the right control is created after onDeviceAttributesChanged()
SeekBarPreference updatedLeftControl = mScreen.findPreference(LEFT_CONTROL_KEY);
SeekBarPreference updatedRightControl = mScreen.findPreference(RIGHT_CONTROL_KEY);
assertThat(updatedLeftControl).isEqualTo(leftControl);
assertThat(updatedRightControl).isNotNull();
}
@Test
public void onAmbientChanged_refreshWhenNotInitiateFromUi() {
prepareDevice(/* hasMember= */ false);
mController.init(mScreen);
final int testAmbient = 10;
HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
.ambient(testAmbient)
.groupAmbient(testAmbient)
.ambientControlExpanded(false)
.build();
when(mLocalDataManager.get(mDevice)).thenReturn(data);
getPreference().setExpanded(true);
mController.onAmbientChanged(mDevice, testAmbient);
verify(mController, never()).refresh();
final int updatedTestAmbient = 20;
mController.onAmbientChanged(mDevice, updatedTestAmbient);
verify(mController).refresh();
}
@Test
public void onMuteChanged_refreshWhenNotInitiateFromUi() {
prepareDevice(/* hasMember= */ false);
mController.init(mScreen);
final int testMute = MUTE_NOT_MUTED;
AmbientVolumeController.RemoteAmbientState state =
new AmbientVolumeController.RemoteAmbientState(testMute, 0);
when(mVolumeController.refreshAmbientState(mDevice)).thenReturn(state);
getPreference().setMuted(false);
mController.onMuteChanged(mDevice, testMute);
verify(mController, never()).refresh();
final int updatedTestMute = MUTE_MUTED;
mController.onMuteChanged(mDevice, updatedTestMute);
verify(mController).refresh();
}
@Test
public void refresh_leftAndRightDifferentGainSetting_expandControl() {
prepareDevice(/* hasMember= */ true);
mController.init(mScreen);
prepareRemoteData(mDevice, 10, MUTE_NOT_MUTED);
prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
getPreference().setExpanded(false);
mController.refresh();
assertThat(getPreference().isExpanded()).isTrue();
}
@Test
public void refresh_oneSideNotMutable_controlNotMutableAndNotMuted() {
prepareDevice(/* hasMember= */ true);
mController.init(mScreen);
prepareRemoteData(mDevice, 10, MUTE_DISABLED);
prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
getPreference().setMutable(true);
getPreference().setMuted(true);
mController.refresh();
assertThat(getPreference().isMutable()).isFalse();
assertThat(getPreference().isMuted()).isFalse();
}
@Test
public void refresh_oneSideNotMuted_controlNotMutedAndSyncToRemote() {
prepareDevice(/* hasMember= */ true);
mController.init(mScreen);
prepareRemoteData(mDevice, 10, MUTE_MUTED);
prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
getPreference().setMutable(true);
getPreference().setMuted(true);
mController.refresh();
assertThat(getPreference().isMutable()).isTrue();
assertThat(getPreference().isMuted()).isFalse();
verify(mVolumeController).setMuted(mDevice, false);
}
private void prepareDevice(boolean hasMember) {
when(mCachedDevice.getDeviceSide()).thenReturn(SIDE_LEFT);
when(mCachedDevice.getDevice()).thenReturn(mDevice);
when(mCachedDevice.getBondState()).thenReturn(BOND_BONDED);
when(mDevice.getAddress()).thenReturn(TEST_ADDRESS);
when(mDevice.getAnonymizedAddress()).thenReturn(TEST_ADDRESS);
when(mDevice.isConnected()).thenReturn(true);
if (hasMember) {
when(mCachedDevice.getMemberDevice()).thenReturn(Set.of(mCachedMemberDevice));
when(mCachedMemberDevice.getDeviceSide()).thenReturn(SIDE_RIGHT);
when(mCachedMemberDevice.getDevice()).thenReturn(mMemberDevice);
when(mCachedMemberDevice.getBondState()).thenReturn(BOND_BONDED);
when(mMemberDevice.getAddress()).thenReturn(TEST_MEMBER_ADDRESS);
when(mMemberDevice.getAnonymizedAddress()).thenReturn(TEST_MEMBER_ADDRESS);
when(mMemberDevice.isConnected()).thenReturn(true);
}
}
private void prepareRemoteData(BluetoothDevice device, int gainSetting, int mute) {
when(mVolumeController.isAmbientControlAvailable(device)).thenReturn(true);
when(mVolumeController.refreshAmbientState(device)).thenReturn(
new AmbientVolumeController.RemoteAmbientState(gainSetting, mute));
}
private void verifyDeviceDataUpdated(BluetoothDevice device) {
verify(mLocalDataManager, atLeastOnce()).updateAmbient(eq(device), anyInt());
verify(mLocalDataManager, atLeastOnce()).updateGroupAmbient(eq(device), anyInt());
verify(mLocalDataManager, atLeastOnce()).updateAmbientControlExpanded(eq(device),
anyBoolean());
}
private AmbientVolumePreference getPreference() {
return mScreen.findPreference(KEY_AMBIENT_VOLUME);
}
@Implements(value = Settings.Global.class)
public static class ShadowGlobal extends ShadowSettings.ShadowGlobal {
private static final Map<ContentResolver, Map<String, String>> sDataMap = new HashMap<>();
@Implementation
protected static boolean putStringForUser(
ContentResolver cr, String name, String value, int userHandle) {
get(cr).put(name, value);
return true;
}
@Implementation
protected static String getStringForUser(ContentResolver cr, String name, int userHandle) {
return get(cr).get(name);
}
private static Map<String, String> get(ContentResolver cr) {
return sDataMap.computeIfAbsent(cr, k -> new HashMap<>());
}
}
}

View File

@@ -53,12 +53,12 @@ public class BluetoothDetailsHearingDeviceControllerTest extends
@Mock
private LocalBluetoothProfileManager mProfileManager;
@Mock
private BluetoothDetailsHearingDeviceController mHearingDeviceController;
@Mock
private BluetoothDetailsHearingAidsPresetsController mPresetsController;
@Mock
private BluetoothDetailsHearingDeviceSettingsController mHearingDeviceSettingsController;
private BluetoothDetailsHearingDeviceController mHearingDeviceController;
@Override
public void setUp() {
super.setUp();
@@ -126,4 +126,24 @@ public class BluetoothDetailsHearingDeviceControllerTest extends
assertThat(mHearingDeviceController.getSubControllers().stream().anyMatch(
c -> c instanceof BluetoothDetailsHearingAidsPresetsController)).isFalse();
}
@Test
@RequiresFlagsEnabled(
com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICES_AMBIENT_VOLUME_CONTROL)
public void initSubControllers_flagEnabled_ambientVolumeControllerExist() {
mHearingDeviceController.initSubControllers(false);
assertThat(mHearingDeviceController.getSubControllers().stream().anyMatch(
c -> c instanceof BluetoothDetailsAmbientVolumePreferenceController)).isTrue();
}
@Test
@RequiresFlagsDisabled(
com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICES_AMBIENT_VOLUME_CONTROL)
public void initSubControllers_flagDisabled_ambientVolumeControllerNotExist() {
mHearingDeviceController.initSubControllers(false);
assertThat(mHearingDeviceController.getSubControllers().stream().anyMatch(
c -> c instanceof BluetoothDetailsAmbientVolumePreferenceController)).isFalse();
}
}

View File

@@ -28,11 +28,13 @@ import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -42,6 +44,7 @@ import android.os.Bundle;
import android.os.Process;
import android.telephony.SubscriptionManager;
import android.util.ArraySet;
import android.util.FeatureFlagUtils;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.PreferenceManager;
@@ -51,22 +54,20 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowDataUsageUtils;
import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
import com.android.settings.testutils.shadow.ShadowFragment;
import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.AppItem;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.net.UidDetail;
import com.android.settingslib.net.UidDetailProvider;
import com.android.settingslib.widget.IntroPreference;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
@@ -79,27 +80,25 @@ import org.robolectric.util.ReflectionHelpers;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowEntityHeaderController.class, ShadowRestrictedLockUtilsInternal.class})
@Config(shadows = {ShadowRestrictedLockUtilsInternal.class})
public class AppDataUsageTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private EntityHeaderController mHeaderController;
@Mock
private PackageManager mPackageManager;
private IntroPreference mIntroPreference;
private AppDataUsage mFragment;
private Context mContext;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
ShadowEntityHeaderController.setUseMock(mHeaderController);
when(mHeaderController.setUid(anyInt())).thenReturn(mHeaderController);
}
@After
public void tearDown() {
ShadowEntityHeaderController.reset();
mContext = spy(RuntimeEnvironment.application);
mIntroPreference = new IntroPreference(mContext);
FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_ENABLE_SPA, true);
}
@Test
@@ -161,6 +160,7 @@ public class AppDataUsageTest {
}
@Test
@Config(shadows = ShadowFragment.class)
public void bindAppHeader_allWorkApps_shouldNotShowAppInfoLink() {
mFragment = spy(new TestFragment());
@@ -169,12 +169,20 @@ public class AppDataUsageTest {
doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen();
ReflectionHelpers.setField(mFragment, "mAppItem", mock(AppItem.class));
mFragment.addEntityHeader();
when(mFragment.getPreferenceScreen().findPreference(AppDataUsage.ARG_APP_HEADER))
.thenReturn(mIntroPreference);
when(mFragment.getContext()).thenReturn(mContext);
doNothing().when(mContext).startActivity(any());
verify(mHeaderController).setHasAppInfoLink(false);
mFragment.setupIntroPreference();
mFragment.onPreferenceTreeClick(mIntroPreference);
verify(mFragment, never()).getActivity();
verify(mContext, never()).startActivity(any(Intent.class));
}
@Test
@Config(shadows = ShadowFragment.class)
public void bindAppHeader_workApp_shouldSetWorkAppUid()
throws PackageManager.NameNotFoundException {
final int fakeUserId = 100;
@@ -188,19 +196,21 @@ public class AppDataUsageTest {
ReflectionHelpers.setField(mFragment, "mAppItem", appItem);
ReflectionHelpers.setField(mFragment, "mPackages", packages);
when(mPackageManager.getPackageUidAsUser(anyString(), anyInt()))
.thenReturn(fakeUserId);
when(mHeaderController.setHasAppInfoLink(anyBoolean())).thenReturn(mHeaderController);
when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(fakeUserId);
when(mFragment.getPreferenceManager())
.thenReturn(mock(PreferenceManager.class, RETURNS_DEEP_STUBS));
doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen();
mFragment.addEntityHeader();
when(mFragment.getPreferenceScreen().findPreference(AppDataUsage.ARG_APP_HEADER))
.thenReturn(mIntroPreference);
when(mFragment.getContext()).thenReturn(mContext);
doNothing().when(mContext).startActivity(any());
verify(mHeaderController).setHasAppInfoLink(true);
verify(mHeaderController).setUid(fakeUserId);
mFragment.setupIntroPreference();
mFragment.onPreferenceTreeClick(mIntroPreference);
ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext).startActivity(argumentCaptor.capture());
}
@Test

View File

@@ -1,152 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.datetime;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
import android.text.format.DateFormat;
import androidx.preference.SwitchPreference;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowApplication;
import java.util.List;
import java.util.Locale;
@RunWith(RobolectricTestRunner.class)
public class AutoTimeFormatPreferenceControllerTest {
@Mock
private UpdateTimeAndDateCallback mCallback;
private ShadowApplication mApplication;
private Context mContext;
private SwitchPreference mPreference;
private TestAutoTimeFormatPreferenceController mController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mApplication = ShadowApplication.getInstance();
mContext = RuntimeEnvironment.application;
mController = new TestAutoTimeFormatPreferenceController(mContext, "test_key");
mPreference = new SwitchPreference(mContext);
mPreference.setKey("test_key");
}
@Test
public void updateState_24HourSet_shouldCheckPreference() {
Settings.System.putString(mContext.getContentResolver(), Settings.System.TIME_12_24,
TimeFormatPreferenceController.HOURS_24);
mController.updateState(mPreference);
assertThat(mPreference.isChecked()).isFalse();
}
@Test
public void updateState_12HourSet_shouldCheckPreference() {
Settings.System.putString(mContext.getContentResolver(), Settings.System.TIME_12_24,
TimeFormatPreferenceController.HOURS_12);
mController.updateState(mPreference);
assertThat(mPreference.isChecked()).isFalse();
}
@Test
public void updateState_autoSet_shouldNotCheckPreference() {
Settings.System.putString(mContext.getContentResolver(), Settings.System.TIME_12_24, null);
mController.updateState(mPreference);
assertThat(mPreference.isChecked()).isTrue();
}
@Test
public void updatePreference_autoSet_shouldSendIntent_12HourLocale() {
mController.setChecked(false);
List<Intent> intentsFired = mApplication.getBroadcastIntents();
assertThat(intentsFired.size()).isEqualTo(1);
Intent intentFired = intentsFired.get(0);
assertThat(intentFired.getAction()).isEqualTo(Intent.ACTION_TIME_CHANGED);
assertThat(intentFired.getIntExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, -1))
.isEqualTo(Intent.EXTRA_TIME_PREF_VALUE_USE_12_HOUR);
}
@Test
public void updatePreference_autoSet_shouldSendIntent_24HourLocale() {
mController.setIs24HourLocale(true);
mController.setChecked(false);
List<Intent> intentsFired = mApplication.getBroadcastIntents();
assertThat(intentsFired.size()).isEqualTo(1);
Intent intentFired = intentsFired.get(0);
assertThat(intentFired.getAction()).isEqualTo(Intent.ACTION_TIME_CHANGED);
assertThat(intentFired.getIntExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, -1))
.isEqualTo(Intent.EXTRA_TIME_PREF_VALUE_USE_24_HOUR);
}
@Test
public void updatePreference_24HourSet_shouldSendIntent() {
mController.setIs24HourLocale(false);
mController.setChecked(true);
List<Intent> intentsFired = mApplication.getBroadcastIntents();
assertThat(intentsFired.size()).isEqualTo(1);
Intent intentFired = intentsFired.get(0);
assertThat(intentFired.getAction()).isEqualTo(Intent.ACTION_TIME_CHANGED);
assertThat(intentFired.getIntExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, -1))
.isEqualTo(Intent.EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT);
}
/**
* Extend class under test to change {@link #is24HourLocale} to not call
* {@link DateFormat#is24HourLocale(Locale)} because that's not available in roboelectric.
*/
private static class TestAutoTimeFormatPreferenceController
extends AutoTimeFormatPreferenceController {
private boolean is24HourLocale = false;
TestAutoTimeFormatPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
void setIs24HourLocale(boolean value) {
is24HourLocale = value;
}
@Override
boolean is24HourLocale(Locale locale) {
return is24HourLocale;
}
}
}

View File

@@ -40,17 +40,12 @@ import android.app.time.TimeZoneConfiguration;
import android.app.time.TimeZoneDetectorStatus;
import android.content.Context;
import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.flags.Flags;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -62,9 +57,6 @@ import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class AutoTimeZonePreferenceControllerTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private UpdateTimeAndDateCallback mCallback;
private Context mContext;
@@ -247,7 +239,6 @@ public class AutoTimeZonePreferenceControllerTest {
}
@Test
@EnableFlags({Flags.FLAG_REVAMP_TOGGLES})
public void toggleOff_revampFlagOn_shouldToggleOffUseLocation() {
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createCapabilitiesAndConfig(
/* autoSupported= */ true,
@@ -266,25 +257,6 @@ public class AutoTimeZonePreferenceControllerTest {
verify(mTimeManager).updateTimeZoneConfiguration(configuration);
}
@Test
@DisableFlags({Flags.FLAG_REVAMP_TOGGLES})
public void toggleOff_revampFlagOff_shouldToggleOffUseLocation() {
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createCapabilitiesAndConfig(
/* autoSupported= */ true,
/* autoEnabled= */ true,
/* telephonySupported= */ true,
/* locationSupported= */ true);
when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
mController.setChecked(false);
TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(false)
.build();
verify(mTimeManager).updateTimeZoneConfiguration(configuration);
}
private static TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig(
boolean autoSupported, boolean autoEnabled, boolean telephonySupported) {
return createCapabilitiesAndConfig(autoSupported, autoEnabled, telephonySupported, false);

View File

@@ -45,17 +45,13 @@ import android.app.time.TimeZoneConfiguration;
import android.app.time.TimeZoneDetectorStatus;
import android.content.Context;
import android.os.UserHandle;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.flags.Flags;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
@@ -71,9 +67,6 @@ import org.robolectric.annotation.Config;
})
public class LocationTimeZoneDetectionPreferenceControllerTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private TimeManager mTimeManager;
private Context mContext;
@@ -131,8 +124,7 @@ public class LocationTimeZoneDetectionPreferenceControllerTest {
}
@Test
@EnableFlags({Flags.FLAG_REVAMP_TOGGLES})
public void flagRevampTogglesOn_toggleOff_automaticTimeZone_disablesLocationToggle() {
public void toggleOff_automaticTimeZone_disablesLocationToggle() {
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
createTimeZoneCapabilitiesAndConfig(/* useLocationEnabled= */ true,
CAPABILITY_POSSESSED, /* setAutoDetectionEnabled= */ false);

View File

@@ -23,16 +23,11 @@ import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.content.Intent;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import androidx.preference.SwitchPreference;
import com.android.settings.flags.Flags;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -46,9 +41,6 @@ import java.util.List;
@RunWith(RobolectricTestRunner.class)
public class TimeFormatPreferenceControllerTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private UpdateTimeAndDateCallback mCallback;
@@ -104,16 +96,6 @@ public class TimeFormatPreferenceControllerTest {
assertThat(mPreference.isChecked()).isFalse();
}
@Test
@DisableFlags({Flags.FLAG_REVAMP_TOGGLES})
public void updateState_autoSet_shouldNotEnablePreference() {
Settings.System.putString(mContext.getContentResolver(), Settings.System.TIME_12_24, null);
mController.updateState(mPreference);
assertThat(mPreference.isEnabled()).isFalse();
}
@Test
public void updatePreference_12HourSet_shouldSendIntent() {
mController.setChecked(false);

View File

@@ -110,6 +110,7 @@ public final class DataProcessManagerTest {
mDataProcessManager =
new DataProcessManager(
mContext,
null,
mUserIdsSeries,
/* isFromPeriodJob= */ false,
/* rawStartTimestamp= */ 0L,
@@ -130,6 +131,7 @@ public final class DataProcessManagerTest {
final DataProcessManager dataProcessManager =
new DataProcessManager(
mContext,
null,
mUserIdsSeries,
/* callbackFunction= */ null);
assertThat(dataProcessManager.getShowScreenOnTime()).isFalse();
@@ -255,6 +257,7 @@ public final class DataProcessManagerTest {
final DataProcessManager dataProcessManager =
new DataProcessManager(
mContext,
null,
mUserIdsSeries,
/* isFromPeriodJob= */ false,
/* rawStartTimestamp= */ 2L,
@@ -346,6 +349,7 @@ public final class DataProcessManagerTest {
assertThat(
DataProcessManager.getBatteryLevelData(
mContext,
null,
mUserIdsSeries,
/* isFromPeriodJob= */ false,
/* asyncResponseDelegate= */ null))
@@ -353,6 +357,7 @@ public final class DataProcessManagerTest {
assertThat(
DataProcessManager.getBatteryLevelData(
mContext,
null,
mUserIdsSeries,
/* isFromPeriodJob= */ true,
/* asyncResponseDelegate= */ null))
@@ -374,6 +379,7 @@ public final class DataProcessManagerTest {
final BatteryLevelData resultData =
DataProcessManager.getBatteryLevelData(
mContext,
null,
mUserIdsSeries,
/* isFromPeriodJob= */ false,
/* asyncResponseDelegate= */ null);
@@ -402,6 +408,7 @@ public final class DataProcessManagerTest {
final BatteryLevelData resultData =
DataProcessManager.getBatteryLevelData(
mContext,
null,
mUserIdsSeries,
/* isFromPeriodJob= */ false,
/* asyncResponseDelegate= */ null);

View File

@@ -0,0 +1,150 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batteryusage
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
class LifecycleAwareAsyncTaskTest {
private val instrumentation = InstrumentationRegistry.getInstrumentation()
private val lifecycle = mock<Lifecycle>()
private val lifecycleOwner = mock<LifecycleOwner>()
@Test
fun addObserver_onPostExecute_onStop() {
val taskBeginCountDownLatch = CountDownLatch(1)
val taskEndCountDownLatch = CountDownLatch(1)
val asyncTask =
object : LifecycleAwareAsyncTask<Void?>(lifecycle) {
override fun doInBackground(vararg params: Void): Void? {
taskBeginCountDownLatch.await()
taskEndCountDownLatch.countDown()
return null
}
}
asyncTask.start()
taskBeginCountDownLatch.countDown()
verify(lifecycle).addObserver(asyncTask)
taskEndCountDownLatch.await()
asyncTask.onStop(lifecycleOwner)
assertThat(asyncTask.isCancelled).isTrue()
verify(lifecycle).removeObserver(asyncTask)
}
@Test
fun addObserver_onStop() {
val executorBlocker = CountDownLatch(1)
object : LifecycleAwareAsyncTask<Void?>(null) {
override fun doInBackground(vararg params: Void?): Void? {
executorBlocker.await()
return null
}
}
.start()
val asyncTask =
object : LifecycleAwareAsyncTask<Void?>(lifecycle) {
override fun doInBackground(vararg params: Void) = null
}
asyncTask.start()
verify(lifecycle).addObserver(asyncTask)
asyncTask.onStop(lifecycleOwner)
executorBlocker.countDown()
assertThat(asyncTask.isCancelled).isTrue()
verify(lifecycle).removeObserver(asyncTask)
}
@Test
fun onPostExecute_addObserver() {
val observers = mutableListOf<LifecycleObserver>()
val lifecycle =
object : Lifecycle() {
override val currentState: State
get() = State.RESUMED
override fun addObserver(observer: LifecycleObserver) {
observers.add(observer)
}
override fun removeObserver(observer: LifecycleObserver) {
observers.remove(observer)
}
}
val asyncTask =
object : LifecycleAwareAsyncTask<Void?>(lifecycle) {
override fun doInBackground(vararg params: Void) = null
}
Thread { asyncTask.start() }.start()
idleAsyncTaskExecutor()
instrumentation.waitForIdleSync()
assertThat(observers).isEmpty()
}
private fun idleAsyncTaskExecutor() {
val taskCountDownLatch = CountDownLatch(1)
object : LifecycleAwareAsyncTask<Void?>(null) {
override fun doInBackground(vararg params: Void): Void? {
taskCountDownLatch.countDown()
return null
}
}
.start()
taskCountDownLatch.await()
}
@Test
fun onStop_addObserver() {
val executorBlocker = CountDownLatch(1)
object : LifecycleAwareAsyncTask<Void?>(null) {
override fun doInBackground(vararg params: Void?): Void? {
executorBlocker.await()
return null
}
}
.start()
val asyncTask =
object : LifecycleAwareAsyncTask<Void?>(lifecycle) {
override fun doInBackground(vararg params: Void) = null
}
asyncTask.onStop(lifecycleOwner)
assertThat(asyncTask.isCancelled).isTrue()
verify(lifecycle).removeObserver(asyncTask)
asyncTask.start()
verify(lifecycle, never()).addObserver(asyncTask)
executorBlocker.countDown()
}
}

View File

@@ -24,20 +24,29 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.R;
import com.android.server.display.feature.flags.Flags;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link ReduceBrightColorsIntensityPreferenceController} */
@RunWith(AndroidJUnit4.class)
public class ReduceBrightColorsIntensityPreferenceControllerTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
private Resources mResources;
private ReduceBrightColorsIntensityPreferenceController mPreferenceController;
@@ -52,27 +61,119 @@ public class ReduceBrightColorsIntensityPreferenceControllerTest {
}
@Test
public void isAvailable_configuredRbcAvailable_enabledRbc_shouldReturnTrue() {
@DisableFlags(Flags.FLAG_EVEN_DIMMER)
public void isAvailable_whenEvenDimmerOffAndDisabled_RbcOnAndAvailable_returnTrue() {
doReturn(false).when(mResources).getBoolean(
com.android.internal.R.bool.config_evenDimmerEnabled);
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
doReturn(true).when(mResources).getBoolean(
R.bool.config_reduceBrightColorsAvailable);
assertThat(mPreferenceController.isAvailable()).isTrue();
}
@Test
public void isAvailable_configuredRbcAvailable_disabledRbc_shouldReturnTrue() {
@DisableFlags(Flags.FLAG_EVEN_DIMMER)
public void isAvailable_whenEvenDimmerOffAndDisabled_RbcOffAndAvailable_returnTrue() {
doReturn(false).when(mResources).getBoolean(
com.android.internal.R.bool.config_evenDimmerEnabled);
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 0);
doReturn(true).when(mResources).getBoolean(
R.bool.config_reduceBrightColorsAvailable);
assertThat(mPreferenceController.isAvailable()).isTrue();
}
@Test
public void isAvailable_configuredRbcUnavailable_enabledRbc_shouldReturnFalse() {
@DisableFlags(Flags.FLAG_EVEN_DIMMER)
public void isAvailable_whenEvenDimmerOffAndDisabled_RbcOnAndUnavailable_returnFalse() {
doReturn(false).when(mResources).getBoolean(
com.android.internal.R.bool.config_evenDimmerEnabled);
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
doReturn(false).when(mResources).getBoolean(
R.bool.config_reduceBrightColorsAvailable);
assertThat(mPreferenceController.isAvailable()).isFalse();
}
@Test
@EnableFlags(Flags.FLAG_EVEN_DIMMER)
public void isAvailable_whenEvenDimmerOnAndDisabled_RbcOnAndAvailable_returnTrue() {
doReturn(false).when(mResources).getBoolean(
com.android.internal.R.bool.config_evenDimmerEnabled);
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
doReturn(true).when(mResources).getBoolean(
R.bool.config_reduceBrightColorsAvailable);
assertThat(mPreferenceController.isAvailable()).isTrue();
}
@Test
@EnableFlags(Flags.FLAG_EVEN_DIMMER)
public void isAvailable_whenEvenDimmerOnAndDisabled_RbcOffAndAvailable_returnTrue() {
doReturn(false).when(mResources).getBoolean(
com.android.internal.R.bool.config_evenDimmerEnabled);
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 0);
doReturn(true).when(mResources).getBoolean(
R.bool.config_reduceBrightColorsAvailable);
assertThat(mPreferenceController.isAvailable()).isTrue();
}
@Test
@EnableFlags(Flags.FLAG_EVEN_DIMMER)
public void isAvailable_whenEvenDimmerOnAndDisabled_RbcOnAndUnavailable_returnFalse() {
doReturn(false).when(mResources).getBoolean(
com.android.internal.R.bool.config_evenDimmerEnabled);
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
doReturn(false).when(mResources).getBoolean(
R.bool.config_reduceBrightColorsAvailable);
assertThat(mPreferenceController.isAvailable()).isFalse();
}
@Test
@EnableFlags(Flags.FLAG_EVEN_DIMMER)
public void isAvailable_whenEvenDimmerOnAndEnabled_RbcOnAndAvailable_returnFalse() {
doReturn(true).when(mResources).getBoolean(
com.android.internal.R.bool.config_evenDimmerEnabled);
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
doReturn(true).when(mResources).getBoolean(
R.bool.config_reduceBrightColorsAvailable);
assertThat(mPreferenceController.isAvailable()).isFalse();
}
@Test
@EnableFlags(Flags.FLAG_EVEN_DIMMER)
public void isAvailable_whenEvenDimmerOnAndEnabled_RbcOffAndAvailable_returnFalse() {
doReturn(true).when(mResources).getBoolean(
com.android.internal.R.bool.config_evenDimmerEnabled);
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 0);
doReturn(true).when(mResources).getBoolean(
R.bool.config_reduceBrightColorsAvailable);
assertThat(mPreferenceController.isAvailable()).isFalse();
}
@Test
@EnableFlags(Flags.FLAG_EVEN_DIMMER)
public void isAvailable_whenEvenDimmerOnAndEnabled_RbcOnAndUnavailable_returnFalse() {
doReturn(true).when(mResources).getBoolean(
com.android.internal.R.bool.config_evenDimmerEnabled);
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
doReturn(false).when(mResources).getBoolean(
R.bool.config_reduceBrightColorsAvailable);
assertThat(mPreferenceController.isAvailable()).isFalse();
}

View File

@@ -21,18 +21,24 @@ import static org.junit.Assert.assertTrue;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.Settings;
import com.android.settings.flags.Flags;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
public class LanguagePreferenceControllerTest {
private Context mContext;
private LanguagePreferenceController mController;
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setup() {
mContext = ApplicationProvider.getApplicationContext();
@@ -40,6 +46,7 @@ public class LanguagePreferenceControllerTest {
}
@Test
@DisableFlags(Flags.FLAG_REGIONAL_PREFERENCES_API_ENABLED)
public void getAvailabilityStatus_featureFlagOff_LanguageSettingsActivitydisabled() {
mController.getAvailabilityStatus();