Snap for 12813595 from 74b1086f23 to 25Q2-release
Change-Id: Ia2029b51d4ba712705cee26749f5a0e366049b3f
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
|
||||
@@ -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>
|
||||
53
res/layout/preference_ambient_volume.xml
Normal file
53
res/layout/preference_ambient_volume.xml
Normal 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>
|
||||
@@ -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" />
|
||||
|
||||
@@ -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 & 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 & 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]-->
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
127
res/xml/language_and_region_settings.xml
Normal file
127
res/xml/language_and_region_settings.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
307
src/com/android/settings/bluetooth/AmbientVolumePreference.java
Normal file
307
src/com/android/settings/bluetooth/AmbientVolumePreference.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -118,6 +118,7 @@ public final class BatteryUsageDataLoader {
|
||||
final BatteryLevelData batteryLevelData =
|
||||
DataProcessManager.getBatteryLevelData(
|
||||
context,
|
||||
null,
|
||||
userIdsSeries,
|
||||
/* isFromPeriodJob= */ true,
|
||||
batteryDiffDataMap -> {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
129
src/com/android/settings/language/LanguageAndRegionSettings.java
Normal file
129
src/com/android/settings/language/LanguageAndRegionSettings.java
Normal 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user