diff --git a/res/layout/preference_battery_header_text.xml b/res/layout/preference_battery_header_text.xml index 72bdbf6a00c..616984e9c80 100644 --- a/res/layout/preference_battery_header_text.xml +++ b/res/layout/preference_battery_header_text.xml @@ -18,8 +18,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:layout_marginStart="?android:attr/listPreferredItemPaddingStart" - android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:layout_marginTop="-6dp" android:paddingBottom="16dp"> Send and receive text messages by satellite. Not included with your account. - Satellite messaging + Satellite messaging, satellite connectivity About %1$s @@ -12328,6 +12328,12 @@ satellite messaging Use of data is included with your account + + Satellite SOS + + Message with emergency services when you can\u2019t connect to a mobile or Wi\u2011Fi network + + satellite sos, sos diff --git a/res/xml/accessibility_vibration_intensity_settings.xml b/res/xml/accessibility_vibration_intensity_settings.xml index f9a5578c517..ba1bd83da94 100644 --- a/res/xml/accessibility_vibration_intensity_settings.xml +++ b/res/xml/accessibility_vibration_intensity_settings.xml @@ -20,7 +20,7 @@ android:title="@string/accessibility_vibration_settings_title"> diff --git a/res/xml/accessibility_vibration_settings.xml b/res/xml/accessibility_vibration_settings.xml index 3ce92a6e303..be45659b310 100644 --- a/res/xml/accessibility_vibration_settings.xml +++ b/res/xml/accessibility_vibration_settings.xml @@ -20,7 +20,7 @@ android:title="@string/accessibility_vibration_settings_title"> diff --git a/res/xml/mobile_network_settings.xml b/res/xml/mobile_network_settings.xml index 3c1317d15b5..72acbe05461 100644 --- a/res/xml/mobile_network_settings.xml +++ b/res/xml/mobile_network_settings.xml @@ -217,6 +217,16 @@ settings:controller= "com.android.settings.network.telephony.SatelliteSettingPreferenceController"/> + + (), KeyValueStore { diff --git a/src/com/android/settings/accessibility/VibrationIntensityScreen.kt b/src/com/android/settings/accessibility/VibrationIntensityScreen.kt index 5d7d4fbfe45..0a372308066 100644 --- a/src/com/android/settings/accessibility/VibrationIntensityScreen.kt +++ b/src/com/android/settings/accessibility/VibrationIntensityScreen.kt @@ -19,18 +19,32 @@ import android.content.Context import androidx.fragment.app.Fragment import com.android.settings.R import com.android.settings.flags.Flags +import com.android.settingslib.metadata.PreferenceAvailabilityProvider import com.android.settingslib.metadata.ProvidePreferenceScreen import com.android.settingslib.metadata.preferenceHierarchy import com.android.settingslib.preference.PreferenceScreenCreator +/** + * Accessibility settings for vibration intensities. + */ +// TODO(b/368360218): investigate if we still need this screen once we finish the migration. +// We might be able to consolidate this into VibrationScreen with PreferenceHierarchy choosing +// between toggle or slider preferences based on device config, depending on how overlays are done. +// LINT.IfChange @ProvidePreferenceScreen -class VibrationIntensityScreen : PreferenceScreenCreator { +class VibrationIntensityScreen : PreferenceScreenCreator, PreferenceAvailabilityProvider { override val key: String get() = KEY override val title: Int get() = R.string.accessibility_vibration_settings_title + override val keywords: Int + get() = R.string.keywords_vibration + + override fun isAvailable(context: Context) = + context.isVibratorAvailable() && context.getSupportedVibrationIntensityLevels() > 1 + override fun isFlagEnabled(context: Context): Boolean = Flags.catalystVibrationIntensityScreen() override fun hasCompleteHierarchy() = false @@ -38,9 +52,12 @@ class VibrationIntensityScreen : PreferenceScreenCreator { override fun fragmentClass(): Class? = VibrationIntensitySettingsFragment::class.java - override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) {} + override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) { + +VibrationMainSwitchPreference() + } companion object { const val KEY = "vibration_intensity_screen" } } +// LINT.ThenChange(VibrationPreferenceController.java) diff --git a/src/com/android/settings/accessibility/VibrationMainSwitchPreference.kt b/src/com/android/settings/accessibility/VibrationMainSwitchPreference.kt new file mode 100644 index 00000000000..70a0033d57a --- /dev/null +++ b/src/com/android/settings/accessibility/VibrationMainSwitchPreference.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.accessibility + +import android.content.Context +import android.os.VibrationAttributes +import android.os.Vibrator +import android.provider.Settings +import android.widget.CompoundButton +import android.widget.CompoundButton.OnCheckedChangeListener +import com.android.settings.R +import com.android.settingslib.datastore.KeyValueStore +import com.android.settingslib.datastore.KeyedObservableDelegate +import com.android.settingslib.datastore.SettingsStore +import com.android.settingslib.datastore.SettingsSystemStore +import com.android.settingslib.metadata.MainSwitchPreference +import com.android.settingslib.metadata.PreferenceLifecycleContext +import com.android.settingslib.metadata.PreferenceLifecycleProvider +import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.preference.MainSwitchPreferenceBinding + +/** + * Accessibility settings for vibration. + */ +// LINT.IfChange +class VibrationMainSwitchPreference : MainSwitchPreference( + key = Settings.System.VIBRATE_ON, + title = R.string.accessibility_vibration_primary_switch_title, +), PreferenceLifecycleProvider, OnCheckedChangeListener { + override val keywords: Int + get() = R.string.keywords_accessibility_vibration_primary_switch + + lateinit var vibrator: Vibrator + + override fun storage(context: Context): KeyValueStore = + VibrationMainSwitchToggleStorage(SettingsSystemStore.get(context)) + + override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) = + ReadWritePermit.ALLOW + + override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) = + ReadWritePermit.ALLOW + + override fun onResume(context: PreferenceLifecycleContext) { + vibrator = context.getSystemService(Vibrator::class.java) + context.findPreference(key) + ?.addOnSwitchChangeListener(this) + } + + override fun onPause(context: PreferenceLifecycleContext) { + context.findPreference(key) + ?.removeOnSwitchChangeListener(this) + } + + override fun onCheckedChanged(button: CompoundButton, isChecked: Boolean) { + if (isChecked) { + // Play a haptic as preview for the main toggle only when touch feedback is enabled. + VibrationPreferenceConfig.playVibrationPreview( + vibrator, VibrationAttributes.USAGE_TOUCH + ) + } + } + + /** Provides SettingsStore for vibration main switch with custom default value. */ + @Suppress("UNCHECKED_CAST") + private class VibrationMainSwitchToggleStorage( + private val settingsStore: SettingsStore, + ) : KeyedObservableDelegate(settingsStore), KeyValueStore { + + override fun contains(key: String) = settingsStore.contains(key) + + override fun getDefaultValue(key: String, valueType: Class) = + DEFAULT_VALUE as T + + override fun getValue(key: String, valueType: Class) = + (settingsStore.getBoolean(key) ?: DEFAULT_VALUE) as T + + override fun setValue(key: String, valueType: Class, value: T?) { + settingsStore.setBoolean(key, value as Boolean?) + } + } + + companion object { + const val DEFAULT_VALUE = true + } +} +// LINT.ThenChange(VibrationMainSwitchPreferenceController.java) diff --git a/src/com/android/settings/accessibility/VibrationMainSwitchPreferenceController.java b/src/com/android/settings/accessibility/VibrationMainSwitchPreferenceController.java index 5b553e3df61..0f2fb77a98a 100644 --- a/src/com/android/settings/accessibility/VibrationMainSwitchPreferenceController.java +++ b/src/com/android/settings/accessibility/VibrationMainSwitchPreferenceController.java @@ -41,6 +41,7 @@ import com.android.settingslib.core.lifecycle.events.OnStop; * will disable the entire settings screen once the settings is turned OFF. All device haptics will * be disabled by this setting, except the flagged alerts and accessibility touch feedback. */ +// LINT.IfChange public class VibrationMainSwitchPreferenceController extends SettingsMainSwitchPreferenceController implements LifecycleObserver, OnStart, OnStop { @@ -106,3 +107,4 @@ public class VibrationMainSwitchPreferenceController extends SettingsMainSwitchP return R.string.menu_key_accessibility; } } +// LINT.ThenChange(VibrationMainSwitchPreference.kt) diff --git a/src/com/android/settings/accessibility/VibrationPreferenceController.java b/src/com/android/settings/accessibility/VibrationPreferenceController.java index 092ff6945d1..e84543db997 100644 --- a/src/com/android/settings/accessibility/VibrationPreferenceController.java +++ b/src/com/android/settings/accessibility/VibrationPreferenceController.java @@ -31,6 +31,7 @@ import com.android.settings.core.BasePreferenceController; import com.android.settings.core.SubSettingLauncher; /** Controller for "Vibration & haptics" settings page. */ +// LINT.IfChange public class VibrationPreferenceController extends BasePreferenceController { private final boolean mHasVibrator; @@ -79,3 +80,7 @@ public class VibrationPreferenceController extends BasePreferenceController { } +// LINT.ThenChange( +// VibrationIntensityScreenTest.kt, +// VibrationScreenTest.kt, +// ) diff --git a/src/com/android/settings/accessibility/VibrationScreen.kt b/src/com/android/settings/accessibility/VibrationScreen.kt new file mode 100644 index 00000000000..63a7c4492a1 --- /dev/null +++ b/src/com/android/settings/accessibility/VibrationScreen.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.accessibility + +import android.content.Context +import android.os.Vibrator +import androidx.fragment.app.Fragment +import com.android.settings.R +import com.android.settings.flags.Flags +import com.android.settingslib.metadata.PreferenceAvailabilityProvider +import com.android.settingslib.metadata.ProvidePreferenceScreen +import com.android.settingslib.metadata.preferenceHierarchy +import com.android.settingslib.preference.PreferenceScreenCreator + +/** + * Accessibility settings for vibration. + */ +// LINT.IfChange +@ProvidePreferenceScreen +class VibrationScreen : PreferenceScreenCreator, PreferenceAvailabilityProvider { + override val key: String + get() = KEY + + override val title: Int + get() = R.string.accessibility_vibration_settings_title + + override val keywords: Int + get() = R.string.keywords_vibration + + override fun isAvailable(context: Context) = + context.isVibratorAvailable() && context.getSupportedVibrationIntensityLevels() == 1 + + override fun isFlagEnabled(context: Context): Boolean = Flags.catalystVibrationIntensityScreen() + + override fun hasCompleteHierarchy() = false + + override fun fragmentClass(): Class? = VibrationSettings::class.java + + override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) { + +VibrationMainSwitchPreference() + } + + companion object { + const val KEY = "vibration_screen" + } +} + +/** Returns true if the device has a system vibrator, false otherwise. */ +fun Context.isVibratorAvailable(): Boolean = + getSystemService(Vibrator::class.java).hasVibrator() + +/** Returns the number of vibration intensity levels supported by this device. */ +fun Context.getSupportedVibrationIntensityLevels(): Int = + resources.getInteger(R.integer.config_vibration_supported_intensity_levels) + +// LINT.ThenChange(VibrationPreferenceController.java) diff --git a/src/com/android/settings/accessibility/VibrationSettings.java b/src/com/android/settings/accessibility/VibrationSettings.java index 48393d9394c..11e1ddde5b4 100644 --- a/src/com/android/settings/accessibility/VibrationSettings.java +++ b/src/com/android/settings/accessibility/VibrationSettings.java @@ -20,6 +20,8 @@ import android.app.settings.SettingsEnums; import android.content.Context; import android.os.Vibrator; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.settings.R; @@ -35,6 +37,11 @@ public class VibrationSettings extends DashboardFragment { private static final String TAG = "VibrationSettings"; + @Override + public @Nullable String getPreferenceScreenBindingKey(@NonNull Context context) { + return VibrationScreen.KEY; + } + @Override public int getMetricsCategory() { return SettingsEnums.ACCESSIBILITY_VIBRATION; diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java index 924ba2c7fa7..f782c6b2a6a 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java @@ -27,7 +27,6 @@ import android.sysprop.BluetoothProperties; import android.text.TextUtils; import android.util.Log; -import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; @@ -109,28 +108,34 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll PreferenceFragmentCompat fragment, LocalBluetoothManager manager, CachedBluetoothDevice device, - Lifecycle lifecycle, - @Nullable List invisibleProfiles, - boolean hasExtraSpace) { + Lifecycle lifecycle) { super(context, fragment, device, lifecycle); mManager = manager; mProfileManager = mManager.getProfileManager(); mCachedDevice = device; mCachedDeviceGroup = Utils.findAllCachedBluetoothDevicesByGroupId(mManager, mCachedDevice); + } + + /** Sets the profiles to be hidden. */ + public void setInvisibleProfiles(List invisibleProfiles) { if (invisibleProfiles != null) { mInvisibleProfiles = Set.copyOf(invisibleProfiles); } - mHasExtraSpace = hasExtraSpace; } - @Override - protected void init(PreferenceScreen screen) { - mProfilesContainer = (PreferenceCategory)screen.findPreference(getPreferenceKey()); - if (mHasExtraSpace) { + /** Sets whether it should show an extra padding on top of the preference. */ + public void setHasExtraSpace(boolean hasExtraSpace) { + if (hasExtraSpace) { mProfilesContainer.setLayoutResource(R.layout.preference_bluetooth_profile_category); } else { mProfilesContainer.setLayoutResource(R.layout.preference_category_bluetooth_no_padding); } + } + + @Override + protected void init(PreferenceScreen screen) { + mProfilesContainer = (PreferenceCategory) screen.findPreference(getPreferenceKey()); + mProfilesContainer.setLayoutResource(R.layout.preference_bluetooth_profile_category); // Call refresh here even though it will get called later in onResume, to avoid the // list of switches appearing to "pop" into the page. refresh(); diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java index 633b44f62e9..403a82429cc 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java @@ -61,7 +61,6 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; -import com.android.settingslib.core.lifecycle.LifecycleObserver; import java.util.ArrayList; import java.util.List; @@ -289,9 +288,12 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment getController( SlicePreferenceController.class, controller -> { - controller.setSliceUri(finalControlUri); - controller.onStart(); - controller.displayPreference(getPreferenceScreen()); + if (getPreferenceScreen().findPreference(controller.getPreferenceKey()) + != null) { + controller.setSliceUri(finalControlUri); + controller.onStart(); + controller.displayPreference(getPreferenceScreen()); + } }); // Temporarily fix the issue that the page will be automatically scrolled to a wrong @@ -352,9 +354,23 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment } @Override - public void onCreatePreferences(@NonNull Bundle savedInstanceState, @NonNull String rootKey) { - super.onCreatePreferences(savedInstanceState, rootKey); + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); if (Flags.enableBluetoothDeviceDetailsPolish()) { + if (mFormatter == null) { + List controllers = getPreferenceControllers().stream() + .flatMap(List::stream) + .toList(); + mFormatter = + FeatureFactory.getFeatureFactory() + .getBluetoothFeatureProvider() + .getDeviceDetailsFragmentFormatter( + requireContext(), + this, + mBluetoothAdapter, + mCachedDevice, + controllers); + } mFormatter.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment.INSTANCE); } } @@ -409,38 +425,8 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment return super.onOptionsItemSelected(menuItem); } - @Override - protected void addPreferenceController(AbstractPreferenceController controller) { - if (Flags.enableBluetoothDeviceDetailsPolish()) { - List keys = - mFormatter.getVisiblePreferenceKeys( - FragmentTypeModel.DeviceDetailsMainFragment.INSTANCE); - Lifecycle lifecycle = getSettingsLifecycle(); - if (keys == null || keys.contains(controller.getPreferenceKey())) { - super.addPreferenceController(controller); - } else if (controller instanceof LifecycleObserver) { - lifecycle.removeObserver((LifecycleObserver) controller); - } - } else { - super.addPreferenceController(controller); - } - } - @Override protected List createPreferenceControllers(Context context) { - List invisibleProfiles = List.of(); - if (Flags.enableBluetoothDeviceDetailsPolish()) { - if (mFormatter == null) { - mFormatter = - FeatureFactory.getFeatureFactory() - .getBluetoothFeatureProvider() - .getDeviceDetailsFragmentFormatter( - requireContext(), this, mBluetoothAdapter, mCachedDevice); - } - invisibleProfiles = - mFormatter.getInvisibleBluetoothProfiles( - FragmentTypeModel.DeviceDetailsMainFragment.INSTANCE); - } ArrayList controllers = new ArrayList<>(); if (mCachedDevice != null) { @@ -459,7 +445,7 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment controllers.add(new BluetoothDetailsSpatialAudioController(context, this, mCachedDevice, lifecycle)); controllers.add(new BluetoothDetailsProfilesController(context, this, mManager, - mCachedDevice, lifecycle, invisibleProfiles, invisibleProfiles == null)); + mCachedDevice, lifecycle)); controllers.add(new BluetoothDetailsMacAddressController(context, this, mCachedDevice, lifecycle)); controllers.add(new StylusDevicesController(context, mInputDevice, mCachedDevice, diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java index d87e6096e92..dae7bb09bd4 100644 --- a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java +++ b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java @@ -26,10 +26,11 @@ import android.net.Uri; import androidx.annotation.NonNull; import androidx.preference.Preference; -import com.android.settings.SettingsPreferenceFragment; import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter; +import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository; +import com.android.settingslib.core.AbstractPreferenceController; import kotlinx.coroutines.CoroutineScope; @@ -100,7 +101,8 @@ public interface BluetoothFeatureProvider { @NonNull DeviceDetailsFragmentFormatter getDeviceDetailsFragmentFormatter( @NonNull Context context, - @NonNull SettingsPreferenceFragment fragment, + @NonNull DashboardFragment fragment, @NonNull BluetoothAdapter bluetoothAdapter, - @NonNull CachedBluetoothDevice cachedDevice); + @NonNull CachedBluetoothDevice cachedDevice, + @NonNull List controllers); } diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt index 082c6932c91..4807899ba49 100644 --- a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt +++ b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt @@ -23,13 +23,14 @@ import android.media.AudioManager import android.media.Spatializer import android.net.Uri import androidx.preference.Preference -import com.android.settings.SettingsPreferenceFragment import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatterImpl +import com.android.settings.dashboard.DashboardFragment import com.android.settingslib.bluetooth.BluetoothUtils import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepositoryImpl +import com.android.settingslib.core.AbstractPreferenceController import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableSet import kotlinx.coroutines.CoroutineScope @@ -78,13 +79,15 @@ open class BluetoothFeatureProviderImpl : BluetoothFeatureProvider { override fun getDeviceDetailsFragmentFormatter( context: Context, - fragment: SettingsPreferenceFragment, + fragment: DashboardFragment, bluetoothAdapter: BluetoothAdapter, - cachedDevice: CachedBluetoothDevice + cachedDevice: CachedBluetoothDevice, + controllers: List, ): DeviceDetailsFragmentFormatter { return DeviceDetailsFragmentFormatterImpl( context, fragment, + controllers, bluetoothAdapter, cachedDevice, Dispatchers.IO diff --git a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt index caa41ef86ef..b093d9c0a64 100644 --- a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt +++ b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt @@ -45,7 +45,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import com.android.settings.R -import com.android.settings.SettingsPreferenceFragment +import com.android.settings.bluetooth.BlockingPrefWithSliceController +import com.android.settings.bluetooth.BluetoothDetailsProfilesController import com.android.settings.bluetooth.ui.composable.Icon import com.android.settings.bluetooth.ui.composable.MultiTogglePreference import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout @@ -54,12 +55,18 @@ import com.android.settings.bluetooth.ui.model.FragmentTypeModel import com.android.settings.bluetooth.ui.view.DeviceDetailsMoreSettingsFragment.Companion.KEY_DEVICE_ADDRESS import com.android.settings.bluetooth.ui.viewmodel.BluetoothDeviceDetailsViewModel import com.android.settings.core.SubSettingLauncher +import com.android.settings.dashboard.DashboardFragment import com.android.settings.overlay.FeatureFactory import com.android.settings.spa.preference.ComposePreference import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingActionModel import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon +import com.android.settingslib.core.AbstractPreferenceController +import com.android.settingslib.core.lifecycle.LifecycleObserver +import com.android.settingslib.core.lifecycle.events.OnPause +import com.android.settingslib.core.lifecycle.events.OnStop import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.widget.preference.Preference as SpaPreference import com.android.settingslib.spa.widget.preference.PreferenceModel @@ -81,16 +88,10 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.launch /** Handles device details fragment layout according to config. */ interface DeviceDetailsFragmentFormatter { - /** Gets keys of visible preferences in built-in preference in xml. */ - fun getVisiblePreferenceKeys(fragmentType: FragmentTypeModel): List? - - /** Updates device details fragment layout. */ - fun getInvisibleBluetoothProfiles(fragmentType: FragmentTypeModel): List? - /** Updates device details fragment layout. */ fun updateLayout(fragmentType: FragmentTypeModel) @@ -104,7 +105,8 @@ interface DeviceDetailsFragmentFormatter { @OptIn(ExperimentalCoroutinesApi::class) class DeviceDetailsFragmentFormatterImpl( private val context: Context, - private val fragment: SettingsPreferenceFragment, + private val fragment: DashboardFragment, + controllers: List, private val bluetoothAdapter: BluetoothAdapter, private val cachedDevice: CachedBluetoothDevice, private val backgroundCoroutineContext: CoroutineContext, @@ -112,40 +114,32 @@ class DeviceDetailsFragmentFormatterImpl( private val metricsFeatureProvider = FeatureFactory.featureFactory.metricsFeatureProvider private val prefVisibility = mutableMapOf>() private val prefVisibilityJobs = mutableListOf() + private var isLoading = false + private var prefKeyToController: Map = + controllers.associateBy { it.preferenceKey } private val viewModel: BluetoothDeviceDetailsViewModel = ViewModelProvider( - fragment, - BluetoothDeviceDetailsViewModel.Factory( - fragment.requireActivity().application, - bluetoothAdapter, - cachedDevice, - backgroundCoroutineContext, - ), - ) + fragment, + BluetoothDeviceDetailsViewModel.Factory( + fragment.requireActivity().application, + bluetoothAdapter, + cachedDevice, + backgroundCoroutineContext, + ), + ) .get(BluetoothDeviceDetailsViewModel::class.java) - override fun getVisiblePreferenceKeys(fragmentType: FragmentTypeModel): List? = - runBlocking { - viewModel - .getItems(fragmentType) - ?.filterIsInstance() - ?.map { it.preferenceKey } - } - - override fun getInvisibleBluetoothProfiles(fragmentType: FragmentTypeModel): List? = - runBlocking { - viewModel - .getItems(fragmentType) - ?.filterIsInstance() - ?.firstOrNull() - ?.invisibleProfiles - } - /** Updates bluetooth device details fragment layout. */ - override fun updateLayout(fragmentType: FragmentTypeModel) = runBlocking { - val items = viewModel.getItems(fragmentType) ?: return@runBlocking - val layout = viewModel.getLayout(fragmentType) ?: return@runBlocking + override fun updateLayout(fragmentType: FragmentTypeModel) { + fragment.setLoading(true, false) + isLoading = true + fragment.lifecycleScope.launch { updateLayoutInternal(fragmentType) } + } + + private suspend fun updateLayoutInternal(fragmentType: FragmentTypeModel) { + val items = viewModel.getItems(fragmentType) ?: return + val layout = viewModel.getLayout(fragmentType) ?: return val prefKeyToSettingId = items @@ -156,21 +150,21 @@ class DeviceDetailsFragmentFormatterImpl( for (i in 0 until fragment.preferenceScreen.preferenceCount) { val pref = fragment.preferenceScreen.getPreference(i) prefKeyToSettingId[pref.key]?.let { id -> settingIdToXmlPreferences[id] = pref } + if (pref.key !in prefKeyToSettingId) { + getController(pref.key)?.let { disableController(it) } + } } fragment.preferenceScreen.removeAll() for (job in prefVisibilityJobs) { job.cancel() } prefVisibilityJobs.clear() - for (row in items.indices) { - val settingId = items[row].settingId + val settingItem = items[row] + val settingId = settingItem.settingId if (settingIdToXmlPreferences.containsKey(settingId)) { - fragment.preferenceScreen.addPreference( - settingIdToXmlPreferences[settingId]!! - .apply { order = row } - .also { logItemShown(it.key, it.isVisible) } - ) + val pref = settingIdToXmlPreferences[settingId]!!.apply { order = row } + fragment.preferenceScreen.addPreference(pref) } else { val prefKey = getPreferenceKey(settingId) prefVisibilityJobs.add( @@ -195,6 +189,29 @@ class DeviceDetailsFragmentFormatterImpl( isSelectable = false setContent { Spacer(modifier = Modifier.height(1.dp)) } }) + + for (row in items.indices) { + val settingItem = items[row] + val settingId = settingItem.settingId + if (settingIdToXmlPreferences.containsKey(settingId)) { + val pref = fragment.preferenceScreen.getPreference(row) + if (settingId == DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES) { + (getController(pref.key) as? BluetoothDetailsProfilesController)?.run { + if (settingItem is DeviceSettingConfigItemModel.BuiltinItem.BluetoothProfilesItem) { + setInvisibleProfiles(settingItem.invisibleProfiles) + setHasExtraSpace(false) + } + } + } + getController(pref.key)?.displayPreference(fragment.preferenceScreen) + logItemShown(pref.key, pref.isVisible) + } + } + + if (isLoading) { + fragment.setLoading(false, false) + isLoading = false + } } override fun getMenuItem( @@ -232,14 +249,14 @@ class DeviceDetailsFragmentFormatterImpl( @Composable private fun buildPreference(layout: DeviceSettingLayout, row: Int, prefKey: String) { val contents by - remember(row) { getDevicesSettingForRow(layout, row) } - .collectAsStateWithLifecycle(initialValue = listOf()) + remember(row) { getDevicesSettingForRow(layout, row) } + .collectAsStateWithLifecycle(initialValue = listOf()) val highlighted by - remember(row) { - layout.rows[row].columns.map { columns -> columns.any { it.highlighted } } - } - .collectAsStateWithLifecycle(initialValue = false) + remember(row) { + layout.rows[row].columns.map { columns -> columns.any { it.highlighted } } + } + .collectAsStateWithLifecycle(initialValue = false) val settings = contents AnimatedVisibility(visible = settings.isNotEmpty(), enter = fadeIn(), exit = fadeOut()) { @@ -454,6 +471,29 @@ class DeviceDetailsFragmentFormatterImpl( } } + private fun getController(key: String): AbstractPreferenceController? { + return prefKeyToController[key] + } + + private fun disableController(controller: AbstractPreferenceController) { + if (controller is LifecycleObserver) { + fragment.settingsLifecycle.removeObserver(controller as LifecycleObserver) + } + + if (controller is BlockingPrefWithSliceController) { + // Make UiBlockListener finished, otherwise UI will flicker. + controller.onChanged(null) + } + + if (controller is OnPause) { + (controller as OnPause).onPause() + } + + if (controller is OnStop) { + (controller as OnStop).onStop() + } + } + private fun getPreferenceKey(settingId: Int) = "DEVICE_SETTING_${settingId}" private companion object { diff --git a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt index 47fda7455bc..cc0fe7bc33a 100644 --- a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt +++ b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt @@ -25,6 +25,7 @@ import android.graphics.PorterDuff import android.os.Bundle import android.view.Menu import android.view.MenuItem +import android.view.View import androidx.lifecycle.lifecycleScope import com.android.settings.R import com.android.settings.bluetooth.BluetoothDetailsAudioDeviceTypeController @@ -33,12 +34,12 @@ import com.android.settings.bluetooth.Utils import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel import com.android.settings.bluetooth.ui.model.FragmentTypeModel import com.android.settings.dashboard.DashboardFragment +import com.android.settings.flags.Flags import com.android.settings.overlay.FeatureFactory.Companion.featureFactory import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon import com.android.settingslib.core.AbstractPreferenceController -import com.android.settingslib.core.lifecycle.LifecycleObserver import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterNotNull @@ -88,17 +89,29 @@ class DeviceDetailsMoreSettingsFragment : DashboardFragment() { return R.xml.bluetooth_device_more_settings_fragment } - override fun addPreferenceController(controller: AbstractPreferenceController) { - val keys: List? = - formatter.getVisiblePreferenceKeys(FragmentTypeModel.DeviceDetailsMoreSettingsFragment) - val lifecycle = settingsLifecycle - if (keys == null || keys.contains(controller.preferenceKey)) { - super.addPreferenceController(controller) - } else if (controller is LifecycleObserver) { - lifecycle.removeObserver((controller as LifecycleObserver)) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + if (!this::formatter.isInitialized) { + val controllers = preferenceControllers.stream() + .flatMap { obj: List -> obj.stream() } + .toList() + val bluetoothManager = requireContext().getSystemService(BluetoothManager::class.java) + formatter = + featureFactory + .bluetoothFeatureProvider + .getDeviceDetailsFragmentFormatter( + requireContext(), this, bluetoothManager.adapter, cachedDevice, controllers + ) } + formatter.updateLayout(FragmentTypeModel.DeviceDetailsMoreSettingsFragment) + helpItem = + formatter + .getMenuItem(FragmentTypeModel.DeviceDetailsMoreSettingsFragment) + .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), initialValue = null) } + + private fun getCachedDevice(): CachedBluetoothDevice? { val bluetoothAddress = arguments?.getString(KEY_DEVICE_ADDRESS) ?: return null localBluetoothManager = Utils.getLocalBtManager(context) ?: return null @@ -107,32 +120,13 @@ class DeviceDetailsMoreSettingsFragment : DashboardFragment() { return Utils.getLocalBtManager(context).cachedDeviceManager.findDevice(remoteDevice) } - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - super.onCreatePreferences(savedInstanceState, rootKey) - formatter.updateLayout(FragmentTypeModel.DeviceDetailsMoreSettingsFragment) - } - override fun createPreferenceControllers(context: Context): List { - val bluetoothManager = context.getSystemService(BluetoothManager::class.java) cachedDevice = getCachedDevice() ?: run { finish() return emptyList() } - if (!this::formatter.isInitialized) { - formatter = - featureFactory.bluetoothFeatureProvider.getDeviceDetailsFragmentFormatter( - requireContext(), - this, - bluetoothManager.adapter, - cachedDevice, - ) - } - helpItem = - formatter - .getMenuItem(FragmentTypeModel.DeviceDetailsMoreSettingsFragment) - .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), initialValue = null) return listOf( BluetoothDetailsProfilesController( context, @@ -140,10 +134,6 @@ class DeviceDetailsMoreSettingsFragment : DashboardFragment() { localBluetoothManager, cachedDevice, settingsLifecycle, - formatter.getInvisibleBluetoothProfiles( - FragmentTypeModel.DeviceDetailsMoreSettingsFragment - ), - false, ), BluetoothDetailsAudioDeviceTypeController( context, diff --git a/src/com/android/settings/datausage/DataSaverMainSwitchPreference.kt b/src/com/android/settings/datausage/DataSaverMainSwitchPreference.kt index de128e6401c..e4e38d42530 100644 --- a/src/com/android/settings/datausage/DataSaverMainSwitchPreference.kt +++ b/src/com/android/settings/datausage/DataSaverMainSwitchPreference.kt @@ -24,6 +24,7 @@ import com.android.settingslib.datastore.NoOpKeyedObservable import com.android.settingslib.metadata.PreferenceLifecycleContext import com.android.settingslib.metadata.PreferenceLifecycleProvider import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.SensitivityLevel class DataSaverMainSwitchPreference(context: Context) : MainSwitchBarMetadata, PreferenceLifecycleProvider { @@ -45,6 +46,9 @@ class DataSaverMainSwitchPreference(context: Context) : override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) = ReadWritePermit.ALLOW + override val sensitivityLevel + get() = SensitivityLevel.NO_SENSITIVITY + override fun onStart(context: PreferenceLifecycleContext) { val listener = DataSaverBackend.Listener { context.notifyPreferenceChange(KEY) } dataSaverBackendListener = listener diff --git a/src/com/android/settings/display/AdaptiveSleepPreference.kt b/src/com/android/settings/display/AdaptiveSleepPreference.kt index 71d7749eb9a..a1600017c35 100644 --- a/src/com/android/settings/display/AdaptiveSleepPreference.kt +++ b/src/com/android/settings/display/AdaptiveSleepPreference.kt @@ -37,6 +37,7 @@ import com.android.settingslib.metadata.PreferenceAvailabilityProvider import com.android.settingslib.metadata.PreferenceLifecycleContext import com.android.settingslib.metadata.PreferenceLifecycleProvider import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.SensitivityLevel import com.android.settingslib.metadata.TwoStatePreference import com.android.settingslib.preference.PreferenceBindingPlaceholder import com.android.settingslib.preference.SwitchPreferenceBinding @@ -82,6 +83,9 @@ class AdaptiveSleepPreference : override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) = ReadWritePermit.ALLOW + override val sensitivityLevel + get() = SensitivityLevel.NO_SENSITIVITY + @Suppress("UNCHECKED_CAST") private class Storage( private val context: Context, diff --git a/src/com/android/settings/display/AmbientDisplayAlwaysOnPreference.kt b/src/com/android/settings/display/AmbientDisplayAlwaysOnPreference.kt index 0537e625159..e50b00b6345 100644 --- a/src/com/android/settings/display/AmbientDisplayAlwaysOnPreference.kt +++ b/src/com/android/settings/display/AmbientDisplayAlwaysOnPreference.kt @@ -36,6 +36,7 @@ import com.android.settingslib.metadata.PreferenceLifecycleContext import com.android.settingslib.metadata.PreferenceLifecycleProvider import com.android.settingslib.metadata.PreferenceSummaryProvider import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.SensitivityLevel import com.android.settingslib.metadata.SwitchPreference // LINT.IfChange @@ -76,6 +77,9 @@ class AmbientDisplayAlwaysOnPreference : override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) = ReadWritePermit.ALLOW + override val sensitivityLevel + get() = SensitivityLevel.NO_SENSITIVITY + override fun onCreate(context: PreferenceLifecycleContext) { val storage = SettingsSecureStore.get(context) keyMappingObserver = diff --git a/src/com/android/settings/display/AutoBrightnessScreen.kt b/src/com/android/settings/display/AutoBrightnessScreen.kt index 385fe2072f4..32d70adabee 100644 --- a/src/com/android/settings/display/AutoBrightnessScreen.kt +++ b/src/com/android/settings/display/AutoBrightnessScreen.kt @@ -35,6 +35,7 @@ import com.android.settingslib.metadata.PreferenceAvailabilityProvider import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.ProvidePreferenceScreen import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.SensitivityLevel import com.android.settingslib.metadata.preferenceHierarchy import com.android.settingslib.preference.PreferenceScreenBinding import com.android.settingslib.preference.PreferenceScreenCreator @@ -70,6 +71,9 @@ class AutoBrightnessScreen : override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) = ReadWritePermit.ALLOW + override val sensitivityLevel + get() = SensitivityLevel.NO_SENSITIVITY + override fun isAvailable(context: Context) = context.resources.getBoolean( com.android.internal.R.bool.config_automatic_brightness_available diff --git a/src/com/android/settings/display/BatteryPercentageSwitchPreference.kt b/src/com/android/settings/display/BatteryPercentageSwitchPreference.kt index fd7b037316f..25623b37681 100644 --- a/src/com/android/settings/display/BatteryPercentageSwitchPreference.kt +++ b/src/com/android/settings/display/BatteryPercentageSwitchPreference.kt @@ -29,6 +29,7 @@ import com.android.settingslib.datastore.SettingsSystemStore import com.android.settingslib.metadata.PreferenceAvailabilityProvider import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.SensitivityLevel import com.android.settingslib.metadata.SwitchPreference import com.android.settingslib.preference.SwitchPreferenceBinding @@ -54,6 +55,9 @@ class BatteryPercentageSwitchPreference : override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) = ReadWritePermit.ALLOW + override val sensitivityLevel + get() = SensitivityLevel.NO_SENSITIVITY + override fun bind(preference: Preference, metadata: PreferenceMetadata) { super.bind(preference, metadata) preference.onPreferenceChangeListener = this diff --git a/src/com/android/settings/display/PeakRefreshRateSwitchPreference.kt b/src/com/android/settings/display/PeakRefreshRateSwitchPreference.kt index 6dc09f6e1c3..3240616cab7 100644 --- a/src/com/android/settings/display/PeakRefreshRateSwitchPreference.kt +++ b/src/com/android/settings/display/PeakRefreshRateSwitchPreference.kt @@ -34,6 +34,7 @@ import com.android.settingslib.metadata.PreferenceLifecycleContext import com.android.settingslib.metadata.PreferenceLifecycleProvider import com.android.settingslib.metadata.PreferenceSummaryProvider import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.SensitivityLevel import com.android.settingslib.metadata.SwitchPreference import kotlin.math.roundToInt @@ -55,6 +56,9 @@ class PeakRefreshRateSwitchPreference : override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) = ReadWritePermit.ALLOW + override val sensitivityLevel + get() = SensitivityLevel.NO_SENSITIVITY + override fun isAvailable(context: Context) = context.resources.getBoolean(R.bool.config_show_smooth_display) && context.peakRefreshRate > DEFAULT_REFRESH_RATE diff --git a/src/com/android/settings/display/darkmode/DarkModeScreen.kt b/src/com/android/settings/display/darkmode/DarkModeScreen.kt index 91667053f69..7f8087a4928 100644 --- a/src/com/android/settings/display/darkmode/DarkModeScreen.kt +++ b/src/com/android/settings/display/darkmode/DarkModeScreen.kt @@ -37,6 +37,7 @@ import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.PreferenceSummaryProvider import com.android.settingslib.metadata.ProvidePreferenceScreen import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.SensitivityLevel import com.android.settingslib.metadata.preferenceHierarchy import com.android.settingslib.preference.PreferenceScreenBinding import com.android.settingslib.preference.PreferenceScreenCreator @@ -76,6 +77,9 @@ class DarkModeScreen : override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) = ReadWritePermit.ALLOW + override val sensitivityLevel + get() = SensitivityLevel.NO_SENSITIVITY + override fun isFlagEnabled(context: Context) = Flags.catalystDarkUiMode() override fun fragmentClass() = DarkModeSettingsFragment::class.java diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderPreference.kt b/src/com/android/settings/fuelgauge/BatteryHeaderPreference.kt index 2a9b0e8284f..95d73dda79c 100644 --- a/src/com/android/settings/fuelgauge/BatteryHeaderPreference.kt +++ b/src/com/android/settings/fuelgauge/BatteryHeaderPreference.kt @@ -22,21 +22,27 @@ import androidx.preference.Preference import com.android.settings.R import com.android.settings.fuelgauge.BatteryBroadcastReceiver.BatteryUpdateType.BATTERY_NOT_PRESENT import com.android.settingslib.Utils +import com.android.settingslib.datastore.KeyValueStore +import com.android.settingslib.datastore.NoOpKeyedObservable import com.android.settingslib.fuelgauge.BatteryUtils +import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.PreferenceLifecycleContext import com.android.settingslib.metadata.PreferenceLifecycleProvider import com.android.settingslib.metadata.PreferenceMetadata +import com.android.settingslib.metadata.RangeValue +import com.android.settingslib.metadata.ReadWritePermit import com.android.settingslib.preference.PreferenceBinding import com.android.settingslib.widget.UsageProgressBarPreference // LINT.IfChange class BatteryHeaderPreference : + PersistentPreference, PreferenceMetadata, PreferenceBinding, - PreferenceLifecycleProvider { + PreferenceLifecycleProvider, + RangeValue { - @VisibleForTesting - var batteryBroadcastReceiver: BatteryBroadcastReceiver? = null + @VisibleForTesting var batteryBroadcastReceiver: BatteryBroadcastReceiver? = null override val key: String get() = KEY @@ -58,25 +64,50 @@ class BatteryHeaderPreference : override fun onCreate(context: PreferenceLifecycleContext) { super.onCreate(context) - batteryBroadcastReceiver = BatteryBroadcastReceiver(context).apply { - setBatteryChangedListener { - if (it != BATTERY_NOT_PRESENT) { - context.notifyPreferenceChange(KEY) + batteryBroadcastReceiver = + BatteryBroadcastReceiver(context).apply { + setBatteryChangedListener { + if (it != BATTERY_NOT_PRESENT) { + context.notifyPreferenceChange(KEY) + } } } - } } override fun onStart(context: PreferenceLifecycleContext) { super.onStart(context) - batteryBroadcastReceiver?.register(); + batteryBroadcastReceiver?.register() } override fun onStop(context: PreferenceLifecycleContext) { super.onStop(context) - batteryBroadcastReceiver?.unRegister(); + batteryBroadcastReceiver?.unRegister() } + override fun storage(context: Context): KeyValueStore = + object : NoOpKeyedObservable(), KeyValueStore { + override fun contains(key: String) = BatteryUtils.getBatteryIntent(context) != null + + @Suppress("UNCHECKED_CAST") + override fun getValue(key: String, valueType: Class): T? { + val batteryIntent = BatteryUtils.getBatteryIntent(context) ?: return null + return Utils.getBatteryLevel(batteryIntent) as T + } + + override fun setValue(key: String, valueType: Class, value: T?) = + throw UnsupportedOperationException() + } + + override fun getMinValue(context: Context): Int = 0 + + override fun getMaxValue(context: Context): Int = 100 + + override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) = + ReadWritePermit.ALLOW + + override fun getWritePermit(context: Context, value: Int?, myUid: Int, callingUid: Int) = + ReadWritePermit.DISALLOW + companion object { private const val KEY = "battery_header" private const val BATTERY_MAX_LEVEL: Long = 100L diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderTextPreference.java b/src/com/android/settings/fuelgauge/BatteryHeaderTextPreference.java index 5c8127731aa..516cc711657 100644 --- a/src/com/android/settings/fuelgauge/BatteryHeaderTextPreference.java +++ b/src/com/android/settings/fuelgauge/BatteryHeaderTextPreference.java @@ -26,9 +26,10 @@ import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; import com.android.settings.R; +import com.android.settingslib.widget.GroupSectionDividerMixin; /** A preference for battery header text. */ -public class BatteryHeaderTextPreference extends Preference { +public class BatteryHeaderTextPreference extends Preference implements GroupSectionDividerMixin { private static final String TAG = "BatteryHeaderTextPreference"; @Nullable private CharSequence mText; diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverPreference.kt b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverPreference.kt index da401c55704..84bba00bab3 100644 --- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverPreference.kt +++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverPreference.kt @@ -32,6 +32,7 @@ import com.android.settingslib.metadata.MainSwitchPreference import com.android.settingslib.metadata.PreferenceLifecycleContext import com.android.settingslib.metadata.PreferenceLifecycleProvider import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.SensitivityLevel // LINT.IfChange class BatterySaverPreference : @@ -49,6 +50,9 @@ class BatterySaverPreference : override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) = ReadWritePermit.ALLOW + override val sensitivityLevel + get() = SensitivityLevel.NO_SENSITIVITY + override fun isEnabled(context: Context) = !BatteryStatus(BatteryUtils.getBatteryIntent(context)).isPluggedIn diff --git a/src/com/android/settings/network/AdaptiveConnectivityTogglePreference.kt b/src/com/android/settings/network/AdaptiveConnectivityTogglePreference.kt index 159ec0febef..c29ec6e7406 100644 --- a/src/com/android/settings/network/AdaptiveConnectivityTogglePreference.kt +++ b/src/com/android/settings/network/AdaptiveConnectivityTogglePreference.kt @@ -26,6 +26,7 @@ import com.android.settingslib.datastore.SettingsSecureStore import com.android.settingslib.datastore.SettingsStore import com.android.settingslib.metadata.MainSwitchPreference import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.SensitivityLevel // LINT.IfChange class AdaptiveConnectivityTogglePreference : @@ -40,6 +41,9 @@ class AdaptiveConnectivityTogglePreference : override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) = ReadWritePermit.ALLOW + override val sensitivityLevel + get() = SensitivityLevel.NO_SENSITIVITY + @Suppress("UNCHECKED_CAST") private class AdaptiveConnectivityToggleStorage( private val context: Context, diff --git a/src/com/android/settings/network/AirplaneModePreference.kt b/src/com/android/settings/network/AirplaneModePreference.kt index 2d9fbe9da3f..d9d1bd8061e 100644 --- a/src/com/android/settings/network/AirplaneModePreference.kt +++ b/src/com/android/settings/network/AirplaneModePreference.kt @@ -23,6 +23,7 @@ import androidx.annotation.DrawableRes import com.android.settings.R import com.android.settingslib.datastore.SettingsGlobalStore import com.android.settingslib.metadata.PreferenceAvailabilityProvider +import com.android.settingslib.metadata.SensitivityLevel import com.android.settingslib.metadata.SwitchPreference // LINT.IfChange @@ -35,6 +36,9 @@ class AirplaneModePreference : override fun storage(context: Context) = SettingsGlobalStore.get(context) + override val sensitivityLevel + get() = SensitivityLevel.HIGH_SENSITIVITY + override fun isAvailable(context: Context) = (context.resources.getBoolean(R.bool.config_show_toggle_airplane) && !context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java index baf16c76c36..1c4b2622716 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java +++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java @@ -288,6 +288,11 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme if (satelliteSettingPreferenceController != null) { satelliteSettingPreferenceController.init(mSubId); } + final SatelliteSettingSosPreferenceController satelliteSettingSosPreferenceController = use( + SatelliteSettingSosPreferenceController.class); + if (satelliteSettingSosPreferenceController != null) { + satelliteSettingSosPreferenceController.init(mSubId); + } use(ApnPreferenceController.class).init(mSubId); use(CarrierPreferenceController.class).init(mSubId); use(DataUsagePreferenceController.class).init(mSubId); diff --git a/src/com/android/settings/network/telephony/SatelliteSettingSosPreferenceController.java b/src/com/android/settings/network/telephony/SatelliteSettingSosPreferenceController.java new file mode 100644 index 00000000000..b9e04d8c034 --- /dev/null +++ b/src/com/android/settings/network/telephony/SatelliteSettingSosPreferenceController.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network.telephony; + +import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ESOS_SUPPORTED_BOOL; + +import android.content.Context; +import android.os.PersistableBundle; + +import com.android.settings.flags.Flags; +import com.android.settings.network.CarrierConfigCache; + +/** A controller for Satellite SOS entry preference. */ +public class SatelliteSettingSosPreferenceController extends TelephonyBasePreferenceController { + private static final String TAG = "SatelliteSettingSosPrefController"; + + public SatelliteSettingSosPreferenceController(Context context, + String preferenceKey) { + super(context, preferenceKey); + } + + /** Setup the subscription Id for the UI with specific UI group. */ + public void init(int subId) { + mSubId = subId; + } + + @Override + public int getAvailabilityStatus(int subId) { + if (Flags.satelliteOemSettingsUxMigration()) { + CarrierConfigCache carrierConfigCache = CarrierConfigCache.getInstance(mContext); + PersistableBundle bundle = carrierConfigCache.getConfigForSubId(subId); + if (bundle == null) { + return CONDITIONALLY_UNAVAILABLE; + } + boolean isCarrierSupport = bundle.getBoolean(KEY_SATELLITE_ESOS_SUPPORTED_BOOL); + return isCarrierSupport ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } + return CONDITIONALLY_UNAVAILABLE; + } +} diff --git a/src/com/android/settings/network/telephony/SatelliteSettingsSosFragment.java b/src/com/android/settings/network/telephony/SatelliteSettingsSosFragment.java new file mode 100644 index 00000000000..911939a9a00 --- /dev/null +++ b/src/com/android/settings/network/telephony/SatelliteSettingsSosFragment.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network.telephony; + +import com.android.settings.SettingsPreferenceFragment; + +public class SatelliteSettingsSosFragment extends SettingsPreferenceFragment { + @Override + public int getMetricsCategory() { + return 0; + } +} diff --git a/src/com/android/settings/notification/CallVolumePreference.kt b/src/com/android/settings/notification/CallVolumePreference.kt index e09dc9bd426..031687f0128 100644 --- a/src/com/android/settings/notification/CallVolumePreference.kt +++ b/src/com/android/settings/notification/CallVolumePreference.kt @@ -32,6 +32,7 @@ import com.android.settingslib.metadata.PreferenceIconProvider import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.RangeValue import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.SensitivityLevel import com.android.settingslib.preference.PreferenceBinding // LINT.IfChange @@ -81,6 +82,9 @@ open class CallVolumePreference : override fun getWritePermit(context: Context, value: Int?, myUid: Int, callingUid: Int) = ReadWritePermit.ALLOW + override val sensitivityLevel + get() = SensitivityLevel.NO_SENSITIVITY + override fun getMinValue(context: Context) = createAudioHelper(context).getMinVolume(getAudioStream(context)) diff --git a/src/com/android/settings/notification/MediaVolumePreference.kt b/src/com/android/settings/notification/MediaVolumePreference.kt index 2533f0a6dc1..a6d9c4111d2 100644 --- a/src/com/android/settings/notification/MediaVolumePreference.kt +++ b/src/com/android/settings/notification/MediaVolumePreference.kt @@ -30,6 +30,7 @@ import com.android.settingslib.metadata.PreferenceIconProvider import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.RangeValue import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.SensitivityLevel import com.android.settingslib.preference.PreferenceBinding // LINT.IfChange @@ -82,6 +83,9 @@ open class MediaVolumePreference : override fun getWritePermit(context: Context, value: Int?, myUid: Int, callingUid: Int) = ReadWritePermit.ALLOW + override val sensitivityLevel + get() = SensitivityLevel.NO_SENSITIVITY + override fun getMinValue(context: Context) = createAudioHelper(context).getMinVolume(STREAM_MUSIC) diff --git a/src/com/android/settings/notification/PhoneRingtonePreferenceController.java b/src/com/android/settings/notification/PhoneRingtonePreferenceController.java index 7bd78feeb3c..2944b7da459 100644 --- a/src/com/android/settings/notification/PhoneRingtonePreferenceController.java +++ b/src/com/android/settings/notification/PhoneRingtonePreferenceController.java @@ -18,6 +18,7 @@ package com.android.settings.notification; import android.content.Context; import android.media.RingtoneManager; +import android.media.audio.Flags; import com.android.settings.Utils; @@ -36,6 +37,9 @@ public class PhoneRingtonePreferenceController extends RingtonePreferenceControl @Override public boolean isAvailable() { + if (isRingtoneVibrationEnabled()) { + return false; + } return Utils.isVoiceCapable(mContext); } @@ -43,4 +47,9 @@ public class PhoneRingtonePreferenceController extends RingtonePreferenceControl public int getRingtoneType() { return RingtoneManager.TYPE_RINGTONE; } + + private boolean isRingtoneVibrationEnabled() { + return Flags.enableRingtoneHapticsCustomization() && mContext.getResources().getBoolean( + com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported); + } } diff --git a/src/com/android/settings/notification/SeparateRingVolumePreference.kt b/src/com/android/settings/notification/SeparateRingVolumePreference.kt index a9c167dbf78..3edb4ac0ed7 100644 --- a/src/com/android/settings/notification/SeparateRingVolumePreference.kt +++ b/src/com/android/settings/notification/SeparateRingVolumePreference.kt @@ -39,6 +39,7 @@ import com.android.settingslib.metadata.PreferenceIconProvider import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.RangeValue import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.SensitivityLevel import com.android.settingslib.preference.PreferenceBinding // LINT.IfChange @@ -91,6 +92,9 @@ open class SeparateRingVolumePreference : override fun getWritePermit(context: Context, value: Int?, myUid: Int, callingUid: Int) = ReadWritePermit.ALLOW + override val sensitivityLevel + get() = SensitivityLevel.NO_SENSITIVITY + override fun getMinValue(context: Context) = createAudioHelper(context).getMinVolume(STREAM_RING) diff --git a/tests/robotests/src/com/android/settings/accessibility/VibrationIntensityScreenTest.kt b/tests/robotests/src/com/android/settings/accessibility/VibrationIntensityScreenTest.kt index 99851de6ac2..40ed9a3dda8 100644 --- a/tests/robotests/src/com/android/settings/accessibility/VibrationIntensityScreenTest.kt +++ b/tests/robotests/src/com/android/settings/accessibility/VibrationIntensityScreenTest.kt @@ -15,15 +15,40 @@ */ package com.android.settings.accessibility +import android.content.Context +import android.content.ContextWrapper +import android.content.res.Resources +import android.os.Vibrator +import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.flags.Flags +import com.android.settings.R import com.android.settingslib.preference.CatalystScreenTestCase import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.stub +// LINT.IfChange @RunWith(AndroidJUnit4::class) class VibrationIntensityScreenTest : CatalystScreenTestCase() { + private lateinit var vibrator: Vibrator + + private val resourcesSpy: Resources = + spy((ApplicationProvider.getApplicationContext() as Context).resources) + + private val context: Context = + object : ContextWrapper(ApplicationProvider.getApplicationContext()) { + override fun getSystemService(name: String): Any? = + when { + name == getSystemServiceName(Vibrator::class.java) -> vibrator + else -> super.getSystemService(name) + } + override fun getResources(): Resources = resourcesSpy + } override val preferenceScreenCreator = VibrationIntensityScreen() @@ -34,4 +59,33 @@ class VibrationIntensityScreenTest : CatalystScreenTestCase() { fun key() { assertThat(preferenceScreenCreator.key).isEqualTo(VibrationIntensityScreen.KEY) } + + @Test + fun isAvailable_noVibrator_unavailable() { + vibrator = mock { on { hasVibrator() } doReturn false } + resourcesSpy.stub { + on { getInteger(R.integer.config_vibration_supported_intensity_levels) } doReturn 3 + } + assertThat(preferenceScreenCreator.isAvailable(context)).isFalse() + } + + @Test + fun isAvailable_hasVibratorAndSingleIntensityLevel_unavailable() { + vibrator = mock { on { hasVibrator() } doReturn true } + resourcesSpy.stub { + on { getInteger(R.integer.config_vibration_supported_intensity_levels) } doReturn 1 + } + assertThat(preferenceScreenCreator.isAvailable(context)).isFalse() + } + + @Test + fun isAvailable_hasVibratorAndMultipleIntensityLevels_available() { + vibrator = mock { on { hasVibrator() } doReturn true } + resourcesSpy.stub { + on { getInteger(R.integer.config_vibration_supported_intensity_levels) } doReturn 2 + } + assertThat(preferenceScreenCreator.isAvailable(context)).isTrue() + } } +// LINT.ThenChange(VibrationPreferenceControllerTest.java) + diff --git a/tests/robotests/src/com/android/settings/accessibility/VibrationMainSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/VibrationMainSwitchPreferenceControllerTest.java index 6f5700347a7..9caa211ebfa 100644 --- a/tests/robotests/src/com/android/settings/accessibility/VibrationMainSwitchPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/VibrationMainSwitchPreferenceControllerTest.java @@ -41,6 +41,7 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; /** Tests for {@link VibrationMainSwitchPreferenceController}. */ +// LINT.IfChange @RunWith(RobolectricTestRunner.class) public class VibrationMainSwitchPreferenceControllerTest { @@ -104,3 +105,4 @@ public class VibrationMainSwitchPreferenceControllerTest { return Settings.System.getInt(mContext.getContentResolver(), settingKey); } } +// LINT.ThenChange(VibrationMainSwitchPreferenceTest.kt) diff --git a/tests/robotests/src/com/android/settings/accessibility/VibrationMainSwitchPreferenceTest.kt b/tests/robotests/src/com/android/settings/accessibility/VibrationMainSwitchPreferenceTest.kt new file mode 100644 index 00000000000..fd781bc33d5 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/VibrationMainSwitchPreferenceTest.kt @@ -0,0 +1,77 @@ +/* + * 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.accessibility + +import android.content.Context +import android.provider.Settings.System.VIBRATE_ON +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.preference.createAndBindWidget +import com.android.settingslib.widget.MainSwitchPreference +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +// LINT.IfChange +@RunWith(AndroidJUnit4::class) +class VibrationMainSwitchPreferenceTest { + private val context: Context = ApplicationProvider.getApplicationContext() + private val preference = VibrationMainSwitchPreference() + + @Test + fun checked_valueUnset_returnDefaultTrue() { + setVibrateOn(null) + + assertThat(getMainSwitchPreference().isChecked).isTrue() + } + + @Test + fun checked_valueEnabled_returnTrue() { + setVibrateOn(true) + + assertThat(getMainSwitchPreference().isChecked).isTrue() + } + + @Test + fun checked_valueDisabled_returnFalse() { + setVibrateOn(false) + + assertThat(getMainSwitchPreference().isChecked).isFalse() + } + + @Test + fun click_updatesCorrectly() { + setVibrateOn(null) + val widget = getMainSwitchPreference() + + assertThat(widget.isChecked).isTrue() + + widget.performClick() + + assertThat(widget.isChecked).isFalse() + + widget.performClick() + + assertThat(widget.isChecked).isTrue() + } + + private fun getMainSwitchPreference(): MainSwitchPreference = + preference.createAndBindWidget(context) + + private fun setVibrateOn(enabled: Boolean?) = + preference.storage(context).setValue(VIBRATE_ON, Boolean::class.javaObjectType, enabled) +} +// LINT.ThenChange(VibrationMainSwitchPreferenceControllerTest.java) diff --git a/tests/robotests/src/com/android/settings/accessibility/VibrationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/VibrationPreferenceControllerTest.java index 59ed48638d0..a0481e5eda5 100644 --- a/tests/robotests/src/com/android/settings/accessibility/VibrationPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/VibrationPreferenceControllerTest.java @@ -46,6 +46,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; +// LINT.IfChange @RunWith(RobolectricTestRunner.class) public class VibrationPreferenceControllerTest { private static final String PREFERENCE_KEY = "preference_key"; @@ -158,3 +159,7 @@ public class VibrationPreferenceControllerTest { return controller; } } +// LINT.ThenChange( +// VibrationIntensityScreenTest.kt, +// VibrationScreenTest.kt, +// ) diff --git a/tests/robotests/src/com/android/settings/accessibility/VibrationScreenTest.kt b/tests/robotests/src/com/android/settings/accessibility/VibrationScreenTest.kt new file mode 100644 index 00000000000..c6e5265a52c --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/VibrationScreenTest.kt @@ -0,0 +1,90 @@ +/* + * 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.accessibility + +import android.content.Context +import android.content.ContextWrapper +import android.content.res.Resources +import android.os.Vibrator +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.flags.Flags +import com.android.settings.R +import com.android.settingslib.preference.CatalystScreenTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.stub + +// LINT.IfChange +@RunWith(AndroidJUnit4::class) +class VibrationScreenTest : CatalystScreenTestCase() { + private lateinit var vibrator: Vibrator + + private val resourcesSpy: Resources = + spy((ApplicationProvider.getApplicationContext() as Context).resources) + + private val context: Context = + object : ContextWrapper(ApplicationProvider.getApplicationContext()) { + override fun getSystemService(name: String): Any? = + when { + name == getSystemServiceName(Vibrator::class.java) -> vibrator + else -> super.getSystemService(name) + } + override fun getResources(): Resources = resourcesSpy + } + + override val preferenceScreenCreator = VibrationScreen() + + override val flagName: String + get() = Flags.FLAG_CATALYST_VIBRATION_INTENSITY_SCREEN + + @Test + fun key() { + assertThat(preferenceScreenCreator.key).isEqualTo(VibrationScreen.KEY) + } + + @Test + fun isAvailable_noVibrator_unavailable() { + vibrator = mock { on { hasVibrator() } doReturn false } + resourcesSpy.stub { + on { getInteger(R.integer.config_vibration_supported_intensity_levels) } doReturn 1 + } + assertThat(preferenceScreenCreator.isAvailable(context)).isFalse() + } + + @Test + fun isAvailable_hasVibratorAndMultipleIntensityLevels_unavailable() { + vibrator = mock { on { hasVibrator() } doReturn true } + resourcesSpy.stub { + on { getInteger(R.integer.config_vibration_supported_intensity_levels) } doReturn 3 + } + assertThat(preferenceScreenCreator.isAvailable(context)).isFalse() + } + + @Test + fun isAvailable_hasVibratorAndSingleIntensityLevel_available() { + vibrator = mock { on { hasVibrator() } doReturn true } + resourcesSpy.stub { + on { getInteger(R.integer.config_vibration_supported_intensity_levels) } doReturn 1 + } + assertThat(preferenceScreenCreator.isAvailable(context)).isTrue() + } +} +// LINT.ThenChange(VibrationPreferenceControllerTest.java) diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java index e21bf9a605e..2d007e1b134 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java @@ -120,7 +120,12 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont .thenAnswer(invocation -> ImmutableList.of(mConnectableProfiles)); setupDevice(mDeviceConfig); - initController(List.of()); + mController = new BluetoothDetailsProfilesController(mContext, mFragment, mLocalManager, + mCachedDevice, mLifecycle); + mProfiles.setKey(mController.getPreferenceKey()); + mController.mProfilesContainer = mProfiles; + mScreen.removeAll(); + mScreen.addPreference(mProfiles); BluetoothProperties.le_audio_allow_list(Lists.newArrayList(LE_DEVICE_MODEL)); } @@ -550,7 +555,8 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont @Test public void prefKeyInBlockingList_hideToggle() { - initController(List.of("A2DP")); + mController.setInvisibleProfiles(List.of("A2DP")); + mController.setHasExtraSpace(true); setupDevice(makeDefaultDeviceConfig()); addA2dpProfileToDevice(true, true, true); @@ -565,7 +571,6 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont @Test public void prefKeyNotInBlockingList_showToggle() { - initController(List.of()); setupDevice(makeDefaultDeviceConfig()); addA2dpProfileToDevice(true, true, true); @@ -653,13 +658,4 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont assertThat(switches.getFirst().getTitle()).isEqualTo( mContext.getString(mLeAudioProfile.getNameResource(mDevice))); } - - private void initController(List invisibleProfiles) { - mController = new BluetoothDetailsProfilesController(mContext, mFragment, mLocalManager, - mCachedDevice, mLifecycle, invisibleProfiles, true); - mProfiles.setKey(mController.getPreferenceKey()); - mController.mProfilesContainer = mProfiles; - mScreen.removeAll(); - mScreen.addPreference(mProfiles); - } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java index 7e9017167d9..0e052ab154f 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java @@ -52,7 +52,6 @@ import androidx.fragment.app.FragmentTransaction; import androidx.preference.PreferenceScreen; import com.android.settings.R; -import com.android.settings.bluetooth.ui.model.FragmentTypeModel; import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter; import com.android.settings.flags.Flags; import com.android.settings.testutils.FakeFeatureFactory; @@ -122,10 +121,7 @@ public class BluetoothDeviceDetailsFragmentTest { removeInputDeviceWithMatchingBluetoothAddress(); FakeFeatureFactory fakeFeatureFactory = FakeFeatureFactory.setupForTest(); when(fakeFeatureFactory.mBluetoothFeatureProvider.getDeviceDetailsFragmentFormatter(any(), - any(), any(), eq(mCachedDevice))).thenReturn(mFormatter); - when(mFormatter.getVisiblePreferenceKeys( - FragmentTypeModel.DeviceDetailsMainFragment.INSTANCE)) - .thenReturn(null); + any(), any(), eq(mCachedDevice), any())).thenReturn(mFormatter); mFragment = setupFragment(); mFragment.onAttach(mContext); diff --git a/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt b/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt index 28eaeaaa194..d0bd27d7a6e 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt +++ b/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt @@ -39,6 +39,7 @@ import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSetti import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel +import com.android.settingslib.core.AbstractPreferenceController import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay @@ -73,6 +74,9 @@ class DeviceDetailsFragmentFormatterTest { @Mock private lateinit var cachedDevice: CachedBluetoothDevice @Mock private lateinit var bluetoothAdapter: BluetoothAdapter @Mock private lateinit var repository: DeviceSettingRepository + @Mock private lateinit var profileController: AbstractPreferenceController + @Mock private lateinit var headerController: AbstractPreferenceController + @Mock private lateinit var buttonController: AbstractPreferenceController private lateinit var context: Context private lateinit var fragment: TestFragment @@ -98,55 +102,22 @@ class DeviceDetailsFragmentFormatterTest { fragment.preferenceScreen.run { addPreference(Preference(context).apply { key = "bluetooth_device_header" }) addPreference(Preference(context).apply { key = "action_buttons" }) - addPreference(Preference(context).apply { key = "keyboard_settings" }) + addPreference(Preference(context).apply { key = "bluetooth_profiles" }) } + `when`(profileController.preferenceKey).thenReturn("bluetooth_profiles") + `when`(headerController.preferenceKey).thenReturn("bluetooth_device_header") + `when`(buttonController.preferenceKey).thenReturn("action_buttons") underTest = DeviceDetailsFragmentFormatterImpl( context, fragment, + listOf(profileController, headerController, buttonController), bluetoothAdapter, cachedDevice, testScope.testScheduler) } - @Test - fun getVisiblePreferenceKeysForMainPage_hasConfig_returnList() { - testScope.runTest { - `when`(repository.getDeviceSettingsConfig(cachedDevice)) - .thenReturn( - DeviceSettingConfigModel( - listOf( - DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem( - DeviceSettingId.DEVICE_SETTING_ID_HEADER, - highlighted = false, - preferenceKey = "bluetooth_device_header" - ), - DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem( - DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS, highlighted = false, preferenceKey = "action_buttons"), - ), - listOf(), - null)) - - val keys = - underTest.getVisiblePreferenceKeys(FragmentTypeModel.DeviceDetailsMainFragment) - - assertThat(keys).containsExactly("bluetooth_device_header", "action_buttons") - } - } - - @Test - fun getVisiblePreferenceKeysForMainPage_noConfig_returnNull() { - testScope.runTest { - `when`(repository.getDeviceSettingsConfig(cachedDevice)).thenReturn(null) - - val keys = - underTest.getVisiblePreferenceKeys(FragmentTypeModel.DeviceDetailsMainFragment) - - assertThat(keys).isNull() - } - } - @Test fun getMenuItem_returnItem() { testScope.runTest { @@ -187,7 +158,7 @@ class DeviceDetailsFragmentFormatterTest { underTest.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment) assertThat(getDisplayedPreferences().mapNotNull { it.key }) - .containsExactly("bluetooth_device_header", "action_buttons", "keyboard_settings") + .containsExactly("bluetooth_device_header", "action_buttons", "bluetooth_profiles") } } @@ -202,8 +173,8 @@ class DeviceDetailsFragmentFormatterTest { DeviceSettingId.DEVICE_SETTING_ID_HEADER, highlighted = false, preferenceKey = "bluetooth_device_header"), DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem( - DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS, - highlighted = false, preferenceKey = "keyboard_settings"), + DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES, + highlighted = false, preferenceKey = "bluetooth_profiles"), ), listOf(), null)) @@ -212,7 +183,7 @@ class DeviceDetailsFragmentFormatterTest { runCurrent() assertThat(getDisplayedPreferences().mapNotNull { it.key }) - .containsExactly("bluetooth_device_header", "keyboard_settings") + .containsExactly("bluetooth_device_header", "bluetooth_profiles") verify(featureFactory.metricsFeatureProvider) .action( SettingsEnums.PAGE_UNKNOWN, @@ -224,7 +195,7 @@ class DeviceDetailsFragmentFormatterTest { SettingsEnums.PAGE_UNKNOWN, SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_ITEM_SHOWN, 0, - "keyboard_settings", 1) + "bluetooth_profiles", 1) } } @@ -242,9 +213,9 @@ class DeviceDetailsFragmentFormatterTest { DeviceSettingConfigItemModel.AppProvidedItem( DeviceSettingId.DEVICE_SETTING_ID_ANC, highlighted = false), DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem( - DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS, + DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES, highlighted = false, - preferenceKey = "keyboard_settings"), + preferenceKey = "bluetooth_profiles"), ), listOf(), null)) @@ -273,7 +244,7 @@ class DeviceDetailsFragmentFormatterTest { .containsExactly( "bluetooth_device_header", "DEVICE_SETTING_${DeviceSettingId.DEVICE_SETTING_ID_ANC}", - "keyboard_settings") + "bluetooth_profiles") verify(featureFactory.metricsFeatureProvider) .action( SettingsEnums.PAGE_UNKNOWN, diff --git a/tests/robotests/src/com/android/settings/notification/PhoneRingtonePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/PhoneRingtonePreferenceControllerTest.java index 24e845851a4..9e49653c233 100644 --- a/tests/robotests/src/com/android/settings/notification/PhoneRingtonePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/PhoneRingtonePreferenceControllerTest.java @@ -21,17 +21,22 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.res.Resources; import android.media.RingtoneManager; +import android.media.audio.Flags; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.telephony.TelephonyManager; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.shadows.ShadowApplication; @RunWith(RobolectricTestRunner.class) public class PhoneRingtonePreferenceControllerTest { @@ -39,32 +44,59 @@ public class PhoneRingtonePreferenceControllerTest { @Mock private TelephonyManager mTelephonyManager; - private Context mContext; + @Mock + private Context mMockContext; + + @Mock + private Resources mMockResources; + private PhoneRingtonePreferenceController mController; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Before public void setUp() { MockitoAnnotations.initMocks(this); - ShadowApplication shadowContext = ShadowApplication.getInstance(); - shadowContext.setSystemService(Context.TELEPHONY_SERVICE, mTelephonyManager); - mContext = RuntimeEnvironment.application; - mController = new PhoneRingtonePreferenceController(mContext); + when(mMockContext.getResources()).thenReturn(mMockResources); + when(mMockContext.getSystemService( + Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager); + mController = new PhoneRingtonePreferenceController(mMockContext); } @Test + @DisableFlags(Flags.FLAG_ENABLE_RINGTONE_HAPTICS_CUSTOMIZATION) public void isAvailable_notVoiceCapable_shouldReturnFalse() { + when(mMockResources + .getBoolean(com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported)) + .thenReturn(false); when(mTelephonyManager.isVoiceCapable()).thenReturn(false); assertThat(mController.isAvailable()).isFalse(); } @Test + @DisableFlags(Flags.FLAG_ENABLE_RINGTONE_HAPTICS_CUSTOMIZATION) public void isAvailable_VoiceCapable_shouldReturnTrue() { + when(mMockResources + .getBoolean(com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported)) + .thenReturn(false); when(mTelephonyManager.isVoiceCapable()).thenReturn(true); assertThat(mController.isAvailable()).isTrue(); } + @Test + @EnableFlags(Flags.FLAG_ENABLE_RINGTONE_HAPTICS_CUSTOMIZATION) + public void isAvailable_vibrationSupported_shouldReturnFalse() { + when(mMockResources + .getBoolean(com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported)) + .thenReturn(true); + when(mTelephonyManager.isVoiceCapable()).thenReturn(true); + + assertThat(mController.isAvailable()).isFalse(); + } + @Test public void getRingtoneType_shouldReturnRingtone() { assertThat(mController.getRingtoneType()).isEqualTo(RingtoneManager.TYPE_RINGTONE); diff --git a/tests/unit/src/com/android/settings/network/telephony/SatelliteSettingsSosPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/SatelliteSettingsSosPreferenceControllerTest.java new file mode 100644 index 00000000000..5c78a142834 --- /dev/null +++ b/tests/unit/src/com/android/settings/network/telephony/SatelliteSettingsSosPreferenceControllerTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network.telephony; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Looper; +import android.os.PersistableBundle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.telephony.CarrierConfigManager; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.internal.telephony.flags.Flags; +import com.android.settings.network.CarrierConfigCache; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(AndroidJUnit4.class) +public class SatelliteSettingsSosPreferenceControllerTest { + private static final String KEY = "key"; + private static final int TEST_SUB_ID = 0; + + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Mock + private CarrierConfigCache mCarrierConfigCache; + + private Context mContext = null; + private SatelliteSettingSosPreferenceController mController = null; + private PersistableBundle mCarrierConfig = new PersistableBundle(); + + @Before + public void setUp() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + mContext = spy(ApplicationProvider.getApplicationContext()); + mController = new SatelliteSettingSosPreferenceController(mContext, KEY); + CarrierConfigCache.setTestInstance(mContext, mCarrierConfigCache); + } + + @Test + @EnableFlags(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public void getAvailabilityStatus_carrierNotSupport_returnUnAvailable() { + mCarrierConfig.putBoolean( + CarrierConfigManager.KEY_SATELLITE_ESOS_SUPPORTED_BOOL, + false); + when(mCarrierConfigCache.getConfigForSubId(TEST_SUB_ID)).thenReturn(mCarrierConfig); + mController.init(TEST_SUB_ID); + + int result = mController.getAvailabilityStatus(TEST_SUB_ID); + + assertThat(result).isEqualTo(CONDITIONALLY_UNAVAILABLE); + } + + @Ignore("Avoid post submit test failed.") + @Test + @EnableFlags(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public void getAvailabilityStatus_carrierSupported_returnAvailable() { + mCarrierConfig.putBoolean( + CarrierConfigManager.KEY_SATELLITE_ESOS_SUPPORTED_BOOL, + true); + when(mCarrierConfigCache.getConfigForSubId(TEST_SUB_ID)).thenReturn(mCarrierConfig); + mController.init(TEST_SUB_ID); + + int result = mController.getAvailabilityStatus(TEST_SUB_ID); + + assertThat(result).isEqualTo(AVAILABLE); + } + + @Test + @DisableFlags(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public void getAvailabilityStatus_featureDisabled_returnAvailable() { + int result = mController.getAvailabilityStatus(TEST_SUB_ID); + + assertThat(result).isEqualTo(CONDITIONALLY_UNAVAILABLE); + } +}