From 20558e21bd6451c0a6ac752ec7ffcd405ee53190 Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Sat, 12 Oct 2024 16:09:15 +0800 Subject: [PATCH 1/3] [Catalyst] Support hybrid mode Bug: 332202168 Flag: com.android.settings.flags.catalyst Test: Manual Change-Id: Ib3607a91345bb0d94cd0d63cf7f9434c5603dd91 --- .../settings/dashboard/DashboardFragment.java | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java index 4d53772cf66..92e99cf12f9 100644 --- a/src/com/android/settings/dashboard/DashboardFragment.java +++ b/src/com/android/settings/dashboard/DashboardFragment.java @@ -53,6 +53,7 @@ import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.Tile; +import com.android.settingslib.preference.PreferenceScreenCreator; import com.android.settingslib.search.Indexable; import java.util.ArrayList; @@ -60,6 +61,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -98,7 +100,8 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment mDashboardFeatureProvider = FeatureFactory.getFeatureFactory().getDashboardFeatureProvider(); - if (!isCatalystEnabled()) { + PreferenceScreenCreator preferenceScreenCreator = getPreferenceScreenCreator(); + if (preferenceScreenCreator == null || !preferenceScreenCreator.hasCompleteHierarchy()) { // Load preference controllers from code final List controllersFromCode = createPreferenceControllers(context); @@ -383,8 +386,12 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment return; } PreferenceScreen screen; - if (isCatalystEnabled()) { + PreferenceScreenCreator preferenceScreenCreator = getPreferenceScreenCreator(); + if (preferenceScreenCreator != null) { screen = createPreferenceScreen(); + if (!preferenceScreenCreator.hasCompleteHierarchy()) { + removeControllersForHybridMode(); + } setPreferenceScreen(screen); requireActivity().setTitle(screen.getTitle()); } else { @@ -395,13 +402,42 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment displayResourceTilesToScreen(screen); } + /** + * Removes preference controllers that have been migrated to catalyst. + * + * In hybrid mode, preference screen is inflated from XML resource, while preference metadata + * in the preference hierarchy are used to update preference widget UI. To avoid conflict, + * remove the preference controllers. + */ + private void removeControllersForHybridMode() { + Set keys = getPreferenceKeysInHierarchy(); + Iterator iterator = mControllers.iterator(); + while (iterator.hasNext()) { + AbstractPreferenceController controller = iterator.next(); + String key = controller.getPreferenceKey(); + if (keys.contains(key)) { + Log.i(TAG, "Remove preference controller for " + key); + iterator.remove(); + List controllers = mPreferenceControllers.get( + controller.getClass()); + if (controllers != null) { + controllers.remove(controller); + } + } + } + } + /** Returns if catalyst is enabled on current screen. */ protected final boolean isCatalystEnabled() { + return getPreferenceScreenCreator() != null; + } + + private @Nullable PreferenceScreenCreator getPreferenceScreenCreator() { if (!Flags.catalyst()) { - return false; + return null; } Context context = getContext(); - return context != null ? getPreferenceScreenCreator(context) != null : false; + return context != null ? getPreferenceScreenCreator(context) : null; } /** From 92ce82809ee81fe21d58f9531fa29b0b091eb816 Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Sat, 12 Oct 2024 16:16:44 +0800 Subject: [PATCH 2/3] [Catalyst] Use hybrid mode for display screen Bug: 368359268 Flag: com.android.settings.flags.catalyst_display_settings_screen Test: atest DisplayScreenTest Change-Id: I5eed12ee5c596bee4c21c6e3aa9f3db0e0227bff --- .../android/settings/display/DisplayScreen.kt | 2 ++ .../settings/display/DisplayScreenTest.kt | 34 +++++++++++++++---- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/com/android/settings/display/DisplayScreen.kt b/src/com/android/settings/display/DisplayScreen.kt index fceb18ae2d6..9886e4ae727 100644 --- a/src/com/android/settings/display/DisplayScreen.kt +++ b/src/com/android/settings/display/DisplayScreen.kt @@ -34,6 +34,8 @@ class DisplayScreen : PreferenceScreenCreator, PreferenceAvailabilityProvider { override fun isFlagEnabled(context: Context) = Flags.catalystDisplaySettingsScreen() + override fun hasCompleteHierarchy() = false + override fun fragmentClass() = DisplaySettings::class.java override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) {} diff --git a/tests/robotests/src/com/android/settings/display/DisplayScreenTest.kt b/tests/robotests/src/com/android/settings/display/DisplayScreenTest.kt index 50c706fa0bb..d869b84da4b 100644 --- a/tests/robotests/src/com/android/settings/display/DisplayScreenTest.kt +++ b/tests/robotests/src/com/android/settings/display/DisplayScreenTest.kt @@ -17,24 +17,32 @@ package com.android.settings.display import android.content.ContextWrapper import android.content.res.Resources -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.internal.widget.LockPatternUtils +import com.android.settings.flags.Flags +import com.android.settings.testutils.FakeFeatureFactory +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.ArgumentMatchers.anyInt +import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.stub @RunWith(AndroidJUnit4::class) -class DisplayScreenTest { - val preferenceScreenCreator = DisplayScreen() +class DisplayScreenTest : CatalystScreenTestCase() { + + override val preferenceScreenCreator = DisplayScreen() + + override val flagName: String + get() = Flags.FLAG_CATALYST_DISPLAY_SETTINGS_SCREEN private val mockResources = mock() - private val context = - object : ContextWrapper(ApplicationProvider.getApplicationContext()) { + private val contextWrapper = + object : ContextWrapper(context) { override fun getResources(): Resources = mockResources } @@ -47,13 +55,25 @@ class DisplayScreenTest { fun isAvailable_configTrue_shouldReturnTrue() { mockResources.stub { on { getBoolean(anyInt()) } doReturn true } - assertThat(preferenceScreenCreator.isAvailable(context)).isTrue() + assertThat(preferenceScreenCreator.isAvailable(contextWrapper)).isTrue() } @Test fun isAvailable_configFalse_shouldReturnFalse() { mockResources.stub { on { getBoolean(anyInt()) } doReturn false } - assertThat(preferenceScreenCreator.isAvailable(context)).isFalse() + assertThat(preferenceScreenCreator.isAvailable(contextWrapper)).isFalse() + } + + override fun migration() { + // avoid UnsupportedOperationException when getDisplay from context + System.setProperty("robolectric.createActivityContexts", "true") + + val lockPatternUtils = mock { on { isSecure(anyInt()) } doReturn true } + FakeFeatureFactory.setupForTest().securityFeatureProvider.stub { + on { getLockPatternUtils(any()) } doReturn lockPatternUtils + } + + super.migration() } } From d38549d60b11af83cc99ae81d54ce90b56fb0912 Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Sat, 12 Oct 2024 16:17:37 +0800 Subject: [PATCH 3/3] [Catalyst] Use hybrid mode for sound screen Bug: 360015496 Flag: com.android.settings.flags.catalyst_sound_screen Test: atest SoundScreenTest Change-Id: Ifd685f8e3a546e8750a593201362540f60a3c031 --- .../settings/notification/SoundScreen.kt | 7 ++-- .../settings/notification/SoundScreenTest.kt | 34 +++++-------------- 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/src/com/android/settings/notification/SoundScreen.kt b/src/com/android/settings/notification/SoundScreen.kt index ecb0c85ed5d..f1f27497e38 100644 --- a/src/com/android/settings/notification/SoundScreen.kt +++ b/src/com/android/settings/notification/SoundScreen.kt @@ -36,12 +36,13 @@ class SoundScreen : PreferenceScreenCreator { override fun isFlagEnabled(context: Context): Boolean = Flags.catalystSoundScreen() + override fun hasCompleteHierarchy() = false + override fun fragmentClass(): Class? = SoundSettings::class.java - override fun getPreferenceHierarchy(context: Context) = - preferenceHierarchy(this) {} + override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) {} companion object { const val KEY = "sound_screen" } -} \ No newline at end of file +} diff --git a/tests/robotests/src/com/android/settings/notification/SoundScreenTest.kt b/tests/robotests/src/com/android/settings/notification/SoundScreenTest.kt index 83b29d2907f..1333ed5e4cc 100644 --- a/tests/robotests/src/com/android/settings/notification/SoundScreenTest.kt +++ b/tests/robotests/src/com/android/settings/notification/SoundScreenTest.kt @@ -15,39 +15,23 @@ */ package com.android.settings.notification -import android.content.Context -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags -import android.platform.test.flag.junit.SetFlagsRule -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.flags.Flags +import com.android.settingslib.preference.CatalystScreenTestCase import com.google.common.truth.Truth.assertThat -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class SoundScreenTest { - @get:Rule val setFlagsRule = SetFlagsRule() - private val context: Context = ApplicationProvider.getApplicationContext() - private val soundScreen = SoundScreen() +class SoundScreenTest : CatalystScreenTestCase() { + + override val preferenceScreenCreator = SoundScreen() + + override val flagName: String + get() = Flags.FLAG_CATALYST_SOUND_SCREEN @Test fun key() { - assertThat(soundScreen.key).isEqualTo(SoundScreen.KEY) + assertThat(preferenceScreenCreator.key).isEqualTo(SoundScreen.KEY) } - - @Test - @EnableFlags(Flags.FLAG_CATALYST_SOUND_SCREEN) - fun isFlagEnabled_returnTrue() { - assertThat(soundScreen.isFlagEnabled(context)).isTrue() - } - - @Test - @DisableFlags(Flags.FLAG_CATALYST_SOUND_SCREEN) - fun isFlagEnabled_returnFalse() { - assertThat(soundScreen.isFlagEnabled(context)).isFalse() - } - -} \ No newline at end of file +}