Loading src/com/android/settings/CatalystSettingsActivity.kt +12 −3 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package com.android.settings import android.content.Intent import android.os.Bundle import com.android.settings.dashboard.DashboardFragment import com.android.settingslib.core.instrumentation.Instrumentable import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY import com.android.settingslib.preference.PreferenceFragment Loading Loading @@ -45,11 +47,18 @@ constructor( /** * Fragment to load catalyst preference screen. * * `PreferenceFragment` class is not used as it does not support highlighting specific preference. * Use [DashboardFragment] as base class instead of [PreferenceFragment] to support injection and * highlighting specific preference. */ class CatalystFragment : SettingsPreferenceFragment() { class CatalystFragment : DashboardFragment() { override fun getMetricsCategory() = 0 override fun getPreferenceScreenResId() = 0 override fun getLogTag(): String = javaClass.simpleName override fun getMetricsCategory() = context?.let { getPreferenceScreenCreator(it) as? Instrumentable }?.metricsCategory ?: METRICS_CATEGORY_UNKNOWN override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { preferenceScreen = createPreferenceScreen() Loading src/com/android/settings/core/PreferenceScreenMixin.kt 0 → 100644 +23 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.core import com.android.settingslib.core.instrumentation.Instrumentable import com.android.settingslib.preference.PreferenceScreenCreator /** Mixin for settings preference screen. */ interface PreferenceScreenMixin : PreferenceScreenCreator, Instrumentable src/com/android/settings/metrics/SettingsMetricsLogger.kt +10 −6 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.settings.metrics import android.content.Context import com.android.settings.overlay.FeatureFactory import com.android.settingslib.core.instrumentation.Instrumentable import com.android.settingslib.core.instrumentation.MetricsFeatureProvider import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.PreferenceScreenMetadata Loading @@ -37,11 +38,14 @@ constructor( preference: PreferenceMetadata, value: Any?, ) { if (preference !is PreferenceActionMetricsProvider) return val metricsCategory = (screen as? Instrumentable)?.metricsCategory ?: Instrumentable.METRICS_CATEGORY_UNKNOWN val intValue = when (value) { is Boolean -> metricsFeatureProvider.action(context, preference.preferenceActionMetrics, value) else -> {} is Boolean -> if (value == true) 1 else 0 is Int -> value else -> return } metricsFeatureProvider.changed(metricsCategory, preference.key, intValue) } } tests/robotests/src/com/android/settings/network/AirplaneModePreferenceTest.kt +17 −10 Original line number Diff line number Diff line Loading @@ -16,17 +16,18 @@ package com.android.settings.network import android.app.settings.SettingsEnums.ACTION_AIRPLANE_TOGGLE import android.app.settings.SettingsEnums.SETTINGS_NETWORK_CATEGORY import android.content.Context import android.content.ContextWrapper import android.content.pm.PackageManager import android.content.pm.PackageManager.FEATURE_LEANBACK import android.content.res.Resources import android.provider.Settings import android.provider.Settings.Global.AIRPLANE_MODE_ON import android.telephony.TelephonyManager import androidx.preference.SwitchPreferenceCompat import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.core.PreferenceScreenMixin import com.android.settings.testutils.MetricsRule import com.android.settingslib.datastore.SettingsGlobalStore import com.android.settingslib.preference.createAndBindWidget Loading @@ -46,7 +47,11 @@ class AirplaneModePreferenceTest { private val mockResources = mock<Resources>() private val mockPackageManager = mock<PackageManager>() private var mockTelephonyManager = mock<TelephonyManager>() private val mockTelephonyManager = mock<TelephonyManager>() private val mockScreenMetadata = mock<PreferenceScreenMixin> { on { getMetricsCategory() } doReturn SETTINGS_NETWORK_CATEGORY } private val context = ApplicationProvider.getApplicationContext<Context>() private val contextWrapper = Loading Loading @@ -90,7 +95,7 @@ class AirplaneModePreferenceTest { @Test fun getValue_defaultOn_returnOn() { SettingsGlobalStore.get(context).setInt(Settings.Global.AIRPLANE_MODE_ON, 1) SettingsGlobalStore.get(context).setInt(AIRPLANE_MODE_ON, 1) val getValue = airplaneModePreference.storage(context).getBoolean(AirplaneModePreference.KEY) Loading @@ -100,7 +105,7 @@ class AirplaneModePreferenceTest { @Test fun getValue_defaultOff_returnOff() { SettingsGlobalStore.get(context).setInt(Settings.Global.AIRPLANE_MODE_ON, 0) SettingsGlobalStore.get(context).setInt(AIRPLANE_MODE_ON, 0) val getValue = airplaneModePreference.storage(context).getBoolean(AirplaneModePreference.KEY) Loading @@ -110,24 +115,26 @@ class AirplaneModePreferenceTest { @Test fun performClick_defaultOn_checkedIsFalse() { SettingsGlobalStore.get(context).setInt(Settings.Global.AIRPLANE_MODE_ON, 1) SettingsGlobalStore.get(context).setInt(AIRPLANE_MODE_ON, 1) val preference = getSwitchPreference().apply { performClick() } assertThat(preference.isChecked).isFalse() verify(metricsRule.metricsFeatureProvider).action(context, ACTION_AIRPLANE_TOGGLE, false) verify(metricsRule.metricsFeatureProvider) .changed(SETTINGS_NETWORK_CATEGORY, AIRPLANE_MODE_ON, 0) } @Test fun performClick_defaultOff_checkedIsTrue() { SettingsGlobalStore.get(context).setInt(Settings.Global.AIRPLANE_MODE_ON, 0) SettingsGlobalStore.get(context).setInt(AIRPLANE_MODE_ON, 0) val preference = getSwitchPreference().apply { performClick() } assertThat(preference.isChecked).isTrue() verify(metricsRule.metricsFeatureProvider).action(context, ACTION_AIRPLANE_TOGGLE, true) verify(metricsRule.metricsFeatureProvider) .changed(SETTINGS_NETWORK_CATEGORY, AIRPLANE_MODE_ON, 1) } private fun getSwitchPreference(): SwitchPreferenceCompat = airplaneModePreference.createAndBindWidget(context) airplaneModePreference.createAndBindWidget(context, null, mockScreenMetadata) } Loading
src/com/android/settings/CatalystSettingsActivity.kt +12 −3 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package com.android.settings import android.content.Intent import android.os.Bundle import com.android.settings.dashboard.DashboardFragment import com.android.settingslib.core.instrumentation.Instrumentable import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY import com.android.settingslib.preference.PreferenceFragment Loading Loading @@ -45,11 +47,18 @@ constructor( /** * Fragment to load catalyst preference screen. * * `PreferenceFragment` class is not used as it does not support highlighting specific preference. * Use [DashboardFragment] as base class instead of [PreferenceFragment] to support injection and * highlighting specific preference. */ class CatalystFragment : SettingsPreferenceFragment() { class CatalystFragment : DashboardFragment() { override fun getMetricsCategory() = 0 override fun getPreferenceScreenResId() = 0 override fun getLogTag(): String = javaClass.simpleName override fun getMetricsCategory() = context?.let { getPreferenceScreenCreator(it) as? Instrumentable }?.metricsCategory ?: METRICS_CATEGORY_UNKNOWN override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { preferenceScreen = createPreferenceScreen() Loading
src/com/android/settings/core/PreferenceScreenMixin.kt 0 → 100644 +23 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.core import com.android.settingslib.core.instrumentation.Instrumentable import com.android.settingslib.preference.PreferenceScreenCreator /** Mixin for settings preference screen. */ interface PreferenceScreenMixin : PreferenceScreenCreator, Instrumentable
src/com/android/settings/metrics/SettingsMetricsLogger.kt +10 −6 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.settings.metrics import android.content.Context import com.android.settings.overlay.FeatureFactory import com.android.settingslib.core.instrumentation.Instrumentable import com.android.settingslib.core.instrumentation.MetricsFeatureProvider import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.PreferenceScreenMetadata Loading @@ -37,11 +38,14 @@ constructor( preference: PreferenceMetadata, value: Any?, ) { if (preference !is PreferenceActionMetricsProvider) return val metricsCategory = (screen as? Instrumentable)?.metricsCategory ?: Instrumentable.METRICS_CATEGORY_UNKNOWN val intValue = when (value) { is Boolean -> metricsFeatureProvider.action(context, preference.preferenceActionMetrics, value) else -> {} is Boolean -> if (value == true) 1 else 0 is Int -> value else -> return } metricsFeatureProvider.changed(metricsCategory, preference.key, intValue) } }
tests/robotests/src/com/android/settings/network/AirplaneModePreferenceTest.kt +17 −10 Original line number Diff line number Diff line Loading @@ -16,17 +16,18 @@ package com.android.settings.network import android.app.settings.SettingsEnums.ACTION_AIRPLANE_TOGGLE import android.app.settings.SettingsEnums.SETTINGS_NETWORK_CATEGORY import android.content.Context import android.content.ContextWrapper import android.content.pm.PackageManager import android.content.pm.PackageManager.FEATURE_LEANBACK import android.content.res.Resources import android.provider.Settings import android.provider.Settings.Global.AIRPLANE_MODE_ON import android.telephony.TelephonyManager import androidx.preference.SwitchPreferenceCompat import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.core.PreferenceScreenMixin import com.android.settings.testutils.MetricsRule import com.android.settingslib.datastore.SettingsGlobalStore import com.android.settingslib.preference.createAndBindWidget Loading @@ -46,7 +47,11 @@ class AirplaneModePreferenceTest { private val mockResources = mock<Resources>() private val mockPackageManager = mock<PackageManager>() private var mockTelephonyManager = mock<TelephonyManager>() private val mockTelephonyManager = mock<TelephonyManager>() private val mockScreenMetadata = mock<PreferenceScreenMixin> { on { getMetricsCategory() } doReturn SETTINGS_NETWORK_CATEGORY } private val context = ApplicationProvider.getApplicationContext<Context>() private val contextWrapper = Loading Loading @@ -90,7 +95,7 @@ class AirplaneModePreferenceTest { @Test fun getValue_defaultOn_returnOn() { SettingsGlobalStore.get(context).setInt(Settings.Global.AIRPLANE_MODE_ON, 1) SettingsGlobalStore.get(context).setInt(AIRPLANE_MODE_ON, 1) val getValue = airplaneModePreference.storage(context).getBoolean(AirplaneModePreference.KEY) Loading @@ -100,7 +105,7 @@ class AirplaneModePreferenceTest { @Test fun getValue_defaultOff_returnOff() { SettingsGlobalStore.get(context).setInt(Settings.Global.AIRPLANE_MODE_ON, 0) SettingsGlobalStore.get(context).setInt(AIRPLANE_MODE_ON, 0) val getValue = airplaneModePreference.storage(context).getBoolean(AirplaneModePreference.KEY) Loading @@ -110,24 +115,26 @@ class AirplaneModePreferenceTest { @Test fun performClick_defaultOn_checkedIsFalse() { SettingsGlobalStore.get(context).setInt(Settings.Global.AIRPLANE_MODE_ON, 1) SettingsGlobalStore.get(context).setInt(AIRPLANE_MODE_ON, 1) val preference = getSwitchPreference().apply { performClick() } assertThat(preference.isChecked).isFalse() verify(metricsRule.metricsFeatureProvider).action(context, ACTION_AIRPLANE_TOGGLE, false) verify(metricsRule.metricsFeatureProvider) .changed(SETTINGS_NETWORK_CATEGORY, AIRPLANE_MODE_ON, 0) } @Test fun performClick_defaultOff_checkedIsTrue() { SettingsGlobalStore.get(context).setInt(Settings.Global.AIRPLANE_MODE_ON, 0) SettingsGlobalStore.get(context).setInt(AIRPLANE_MODE_ON, 0) val preference = getSwitchPreference().apply { performClick() } assertThat(preference.isChecked).isTrue() verify(metricsRule.metricsFeatureProvider).action(context, ACTION_AIRPLANE_TOGGLE, true) verify(metricsRule.metricsFeatureProvider) .changed(SETTINGS_NETWORK_CATEGORY, AIRPLANE_MODE_ON, 1) } private fun getSwitchPreference(): SwitchPreferenceCompat = airplaneModePreference.createAndBindWidget(context) airplaneModePreference.createAndBindWidget(context, null, mockScreenMetadata) }