Skip to content
Snippets Groups Projects
Commit c9d6d894 authored by Jeff DeCew's avatar Jeff DeCew Committed by Android (Google) Code Review
Browse files

Merge "Allow static access to SceneContainerFlag.isEnabled" into main

parents 2f52f7c7 182829bd
No related branches found
No related tags found
No related merge requests found
Showing with 153 additions and 231 deletions
......@@ -965,14 +965,6 @@
<!-- Whether to show bottom sheets edge to edge -->
<bool name="config_edgeToEdgeBottomSheetDialog">true</bool>
<!--
Whether the scene container framework is enabled.
The scene container framework is a newer (2023) way to organize the various "scenes" between the
bouncer, lockscreen, shade, and quick settings.
-->
<bool name="config_sceneContainerFrameworkEnabled">true</bool>
<!--
Time in milliseconds the user has to touch the side FPS sensor to successfully authenticate
TODO(b/302332976) Get this value from the HAL if they can provide an API for it.
......
......@@ -25,7 +25,9 @@ import com.android.server.notification.Flags.vibrateWhileUnlocked
import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
......@@ -36,20 +38,28 @@ import javax.inject.Inject
class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, handler: Handler) :
FlagDependenciesBase(featureFlags, handler) {
override fun defineDependencies() {
// Internal notification backend dependencies
crossAppPoliteNotifications dependsOn politeNotifications
vibrateWhileUnlockedToken dependsOn politeNotifications
// Internal notification frontend dependencies
NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token
FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
val keyguardBottomAreaRefactor = FlagToken(
FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
// Internal keyguard dependencies
KeyguardShadeMigrationNssl.token dependsOn keyguardBottomAreaRefactor
val crossAppPoliteNotifToken =
FlagToken(FLAG_CROSS_APP_POLITE_NOTIFICATIONS, crossAppPoliteNotifications())
val politeNotifToken = FlagToken(FLAG_POLITE_NOTIFICATIONS, politeNotifications())
crossAppPoliteNotifToken dependsOn politeNotifToken
val vibrateWhileUnlockedToken =
FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
vibrateWhileUnlockedToken dependsOn politeNotifToken
// SceneContainer dependencies
SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
SceneContainerFlag.getMainStaticFlag() dependsOn MIGRATE_KEYGUARD_STATUS_BAR_VIEW
}
private inline val politeNotifications
get() = FlagToken(FLAG_POLITE_NOTIFICATIONS, politeNotifications())
private inline val crossAppPoliteNotifications
get() = FlagToken(FLAG_CROSS_APP_POLITE_NOTIFICATIONS, crossAppPoliteNotifications())
private inline val vibrateWhileUnlockedToken: FlagToken
get() = FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
private inline val keyguardBottomAreaRefactor
get() = FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
}
......@@ -17,6 +17,7 @@
package com.android.systemui.media.controls.util
import com.android.systemui.Flags
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the media_in_scene_container flag state. */
......@@ -25,6 +26,10 @@ object MediaInSceneContainerFlag {
/** The aconfig flag name */
const val FLAG_NAME = Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
/** A token used for dependency declaration */
val token: FlagToken
get() = FlagToken(FLAG_NAME, isEnabled)
/** Is the flag enabled? */
@JvmStatic
inline val isEnabled
......
......@@ -14,30 +14,94 @@
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE")
package com.android.systemui.scene.shared.flag
import android.content.Context
import androidx.annotation.VisibleForTesting
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.Flags.sceneContainer
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flag
import com.android.systemui.flags.Flags
import com.android.systemui.flags.ReleasedFlag
import com.android.systemui.flags.ResourceBooleanFlag
import com.android.systemui.flags.UnreleasedFlag
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.Flags.SCENE_CONTAINER_ENABLED
import com.android.systemui.flags.RefactorFlagUtils
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
import com.android.systemui.res.R
import dagger.Module
import dagger.Provides
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
/** Helper for reading or using the scene container flag state. */
object SceneContainerFlag {
/** The flag description -- not an aconfig flag name */
const val DESCRIPTION = "SceneContainerFlag"
inline val isEnabled
get() =
SCENE_CONTAINER_ENABLED && // mainStaticFlag
sceneContainer() && // mainAconfigFlag
keyguardBottomAreaRefactor() &&
KeyguardShadeMigrationNssl.isEnabled &&
MediaInSceneContainerFlag.isEnabled &&
ComposeFacade.isComposeAvailable()
/**
* The main static flag, SCENE_CONTAINER_ENABLED. This is an explicit static flag check that
* helps with downstream optimizations (like unused code stripping) in builds where aconfig
* flags are still writable. Do not remove!
*/
inline fun getMainStaticFlag() =
FlagToken("Flags.SCENE_CONTAINER_ENABLED", SCENE_CONTAINER_ENABLED)
/** The main aconfig flag. */
inline fun getMainAconfigFlag() = FlagToken(FLAG_SCENE_CONTAINER, sceneContainer())
/** The set of secondary flags which must be enabled for scene container to work properly */
inline fun getSecondaryFlags(): Sequence<FlagToken> =
sequenceOf(
FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor()),
KeyguardShadeMigrationNssl.token,
MediaInSceneContainerFlag.token,
)
/** The full set of requirements for SceneContainer */
inline fun getAllRequirements(): Sequence<FlagToken> {
val composeRequirement =
FlagToken("ComposeFacade.isComposeAvailable()", ComposeFacade.isComposeAvailable())
return sequenceOf(getMainStaticFlag(), getMainAconfigFlag()) +
getSecondaryFlags() +
composeRequirement
}
/** Return all dependencies of this flag in pairs where [Pair.first] depends on [Pair.second] */
inline fun getFlagDependencies(): Sequence<Pair<FlagToken, FlagToken>> {
val mainStaticFlag = getMainStaticFlag()
val mainAconfigFlag = getMainAconfigFlag()
return sequence {
// The static and aconfig flags should be equal; make them co-dependent
yield(mainAconfigFlag to mainStaticFlag)
yield(mainStaticFlag to mainAconfigFlag)
// all other flags depend on the static flag for brevity
} + getSecondaryFlags().map { mainStaticFlag to it }
}
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
* unintended behaviors caused by accidentally running new logic, while also crashing on an eng
* build to ensure that the refactor author catches issues in testing.
*/
@JvmStatic
inline fun isUnexpectedlyInLegacyMode() =
RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, DESCRIPTION)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
* the flag is enabled to ensure that the refactor author catches issues in testing.
*/
@JvmStatic
inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, DESCRIPTION)
}
/**
* Defines interface for classes that can check whether the scene container framework feature is
......@@ -52,133 +116,25 @@ interface SceneContainerFlags {
fun requirementDescription(): String
}
class SceneContainerFlagsImpl
@AssistedInject
constructor(
@Application private val context: Context,
private val featureFlagsClassic: FeatureFlagsClassic,
@Assisted private val isComposeAvailable: Boolean,
) : SceneContainerFlags {
companion object {
@VisibleForTesting
val classicFlagTokens: List<Flag<Boolean>> =
listOf(
Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW,
)
}
/** The list of requirements, all must be met for the feature to be enabled. */
private val requirements =
listOf(
AconfigFlagMustBeEnabled(
flagName = AConfigFlags.FLAG_SCENE_CONTAINER,
flagValue = sceneContainer(),
),
AconfigFlagMustBeEnabled(
flagName = AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
flagValue = keyguardBottomAreaRefactor(),
),
AconfigFlagMustBeEnabled(
flagName = KeyguardShadeMigrationNssl.FLAG_NAME,
flagValue = KeyguardShadeMigrationNssl.isEnabled,
),
AconfigFlagMustBeEnabled(
flagName = MediaInSceneContainerFlag.FLAG_NAME,
flagValue = MediaInSceneContainerFlag.isEnabled,
),
) +
classicFlagTokens.map { flagToken -> FlagMustBeEnabled(flagToken) } +
listOf(
ComposeMustBeAvailable(),
CompileTimeFlagMustBeEnabled(),
ResourceConfigMustBeEnabled()
)
class SceneContainerFlagsImpl : SceneContainerFlags {
override fun isEnabled(): Boolean {
// SCENE_CONTAINER_ENABLED is an explicit static flag check that helps with downstream
// optimizations, e.g., unused code stripping. Do not remove!
return Flags.SCENE_CONTAINER_ENABLED && requirements.all { it.isMet() }
return SceneContainerFlag.isEnabled
}
override fun requirementDescription(): String {
return buildString {
requirements.forEach { requirement ->
SceneContainerFlag.getAllRequirements().forEach { requirement ->
append('\n')
append(if (requirement.isMet()) " [MET]" else "[NOT MET]")
append(if (requirement.isEnabled) " [MET]" else "[NOT MET]")
append(" ${requirement.name}")
}
}
}
private interface Requirement {
val name: String
fun isMet(): Boolean
}
private inner class ComposeMustBeAvailable : Requirement {
override val name = "Jetpack Compose must be available"
override fun isMet(): Boolean {
return isComposeAvailable
}
}
private inner class CompileTimeFlagMustBeEnabled : Requirement {
override val name = "Flags.SCENE_CONTAINER_ENABLED must be enabled in code"
override fun isMet(): Boolean {
return Flags.SCENE_CONTAINER_ENABLED
}
}
private inner class FlagMustBeEnabled<FlagType : Flag<*>>(
private val flag: FlagType,
) : Requirement {
override val name = "Flag ${flag.name} must be enabled"
override fun isMet(): Boolean {
return when (flag) {
is ResourceBooleanFlag -> featureFlagsClassic.isEnabled(flag)
is ReleasedFlag -> featureFlagsClassic.isEnabled(flag)
is UnreleasedFlag -> featureFlagsClassic.isEnabled(flag)
else -> error("Unsupported flag type ${flag.javaClass}")
}
}
}
private inner class AconfigFlagMustBeEnabled(
flagName: String,
private val flagValue: Boolean,
) : Requirement {
override val name: String = "Aconfig flag $flagName must be enabled"
override fun isMet(): Boolean {
return flagValue
}
}
private inner class ResourceConfigMustBeEnabled : Requirement {
override val name: String = "R.bool.config_sceneContainerFrameworkEnabled must be true"
override fun isMet(): Boolean {
return context.resources.getBoolean(R.bool.config_sceneContainerFrameworkEnabled)
}
}
@AssistedFactory
interface Factory {
fun create(isComposeAvailable: Boolean): SceneContainerFlagsImpl
}
}
@Module
object SceneContainerFlagsModule {
@Provides
@SysUISingleton
fun impl(factory: SceneContainerFlagsImpl.Factory): SceneContainerFlags {
return factory.create(ComposeFacade.isComposeAvailable())
}
@Provides @SysUISingleton fun impl(): SceneContainerFlags = SceneContainerFlagsImpl()
}
......@@ -17,12 +17,17 @@
package com.android.systemui.statusbar.notification.interruption
import com.android.systemui.Flags
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the visual interruptions refactor flag state. */
object VisualInterruptionRefactor {
const val FLAG_NAME = Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR
/** A token used for dependency declaration */
val token: FlagToken
get() = FlagToken(FLAG_NAME, isEnabled)
/** Whether the refactor is enabled */
@JvmStatic
inline val isEnabled
......
......@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.row.shared
import com.android.systemui.Flags
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the async hybrid view inflation flag state. */
......@@ -24,6 +25,10 @@ import com.android.systemui.flags.RefactorFlagUtils
object AsyncHybridViewInflation {
const val FLAG_NAME = Flags.FLAG_NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION
/** A token used for dependency declaration */
val token: FlagToken
get() = FlagToken(FLAG_NAME, isEnabled)
/** Is async hybrid (single-line) view inflation enabled */
@JvmStatic
inline val isEnabled
......
......@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.stack.shared
import com.android.systemui.Flags
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the DisplaySwitchNotificationsHider flag state. */
......@@ -24,6 +25,10 @@ import com.android.systemui.flags.RefactorFlagUtils
object DisplaySwitchNotificationsHiderFlag {
const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_HIDE_ON_DISPLAY_SWITCH
/** A token used for dependency declaration */
val token: FlagToken
get() = FlagToken(FLAG_NAME, isEnabled)
/** Is the hiding enabled? */
@JvmStatic
inline val isEnabled
......
/*
* Copyright 2023 The Android Open Source Project
* Copyright (C) 2023 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.
......@@ -16,35 +16,23 @@
package com.android.systemui.scene.shared.flag
import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.FakeFeatureFlagsImpl
import com.android.systemui.Flags as AconfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.flags.Flags
import com.android.systemui.flags.ReleasedFlag
import com.android.systemui.flags.ResourceBooleanFlag
import com.android.systemui.flags.UnreleasedFlag
import com.android.systemui.flags.setFlagValue
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@SmallTest
@RunWith(Parameterized::class)
internal class SceneContainerFlagsTest(
private val testCase: TestCase,
) : SysuiTestCase() {
@Rule @JvmField val setFlagsRule: SetFlagsRule = SetFlagsRule()
private lateinit var underTest: SceneContainerFlags
@RunWith(AndroidJUnit4::class)
internal class SceneContainerFlagsTest : SysuiTestCase() {
@Before
fun setUp() {
......@@ -52,83 +40,39 @@ internal class SceneContainerFlagsTest(
// Flags.SCENE_CONTAINER_ENABLED is no longer needed.
val field = Flags::class.java.getField("SCENE_CONTAINER_ENABLED")
field.isAccessible = true
field.set(null, true)
val featureFlags =
FakeFeatureFlagsClassic().apply {
SceneContainerFlagsImpl.classicFlagTokens.forEach { flagToken ->
when (flagToken) {
is ResourceBooleanFlag -> set(flagToken, testCase.areAllFlagsSet)
is ReleasedFlag -> set(flagToken, testCase.areAllFlagsSet)
is UnreleasedFlag -> set(flagToken, testCase.areAllFlagsSet)
else -> error("Unsupported flag type ${flagToken.javaClass}")
}
}
}
// TODO(b/306421592): get the aconfig FeatureFlags from the SetFlagsRule.
val aconfigFlags = FakeFeatureFlagsImpl()
field.set(null, true) // note: this does not work with multivalent tests
}
private fun setAconfigFlagsEnabled(enabled: Boolean) {
listOf(
AconfigFlags.FLAG_SCENE_CONTAINER,
AconfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
com.android.systemui.Flags.FLAG_SCENE_CONTAINER,
com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
KeyguardShadeMigrationNssl.FLAG_NAME,
MediaInSceneContainerFlag.FLAG_NAME,
)
.forEach { flagToken ->
setFlagsRule.enableFlags(flagToken)
aconfigFlags.setFlag(flagToken, testCase.areAllFlagsSet)
overrideResource(
R.bool.config_sceneContainerFrameworkEnabled,
testCase.isResourceConfigEnabled
)
}
underTest =
SceneContainerFlagsImpl(
context = context,
featureFlagsClassic = featureFlags,
isComposeAvailable = testCase.isComposeAvailable,
)
.forEach { flagName -> mSetFlagsRule.setFlagValue(flagName, enabled) }
}
@Test
fun isEnabled() {
assertThat(underTest.isEnabled()).isEqualTo(testCase.expectedEnabled)
fun isNotEnabled_withoutAconfigFlags() {
setAconfigFlagsEnabled(false)
Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
}
internal data class TestCase(
val isComposeAvailable: Boolean,
val areAllFlagsSet: Boolean,
val isResourceConfigEnabled: Boolean,
val expectedEnabled: Boolean,
) {
override fun toString(): String {
return "(compose=$isComposeAvailable + flags=$areAllFlagsSet) + XML" +
" config=$isResourceConfigEnabled -> expected=$expectedEnabled"
}
@Test
fun isEnabled_withAconfigFlags_withCompose() {
Assume.assumeTrue(ComposeFacade.isComposeAvailable())
setAconfigFlagsEnabled(true)
Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(true)
Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(true)
}
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun testCases() = buildList {
repeat(8) { combination ->
val isComposeAvailable = combination and 0b100 != 0
val areAllFlagsSet = combination and 0b010 != 0
val isResourceConfigEnabled = combination and 0b001 != 0
val expectedEnabled =
isComposeAvailable && areAllFlagsSet && isResourceConfigEnabled
add(
TestCase(
isComposeAvailable = isComposeAvailable,
areAllFlagsSet = areAllFlagsSet,
expectedEnabled = expectedEnabled,
isResourceConfigEnabled = isResourceConfigEnabled,
)
)
}
}
@Test
fun isNotEnabled_withAconfigFlags_withoutCompose() {
Assume.assumeFalse(ComposeFacade.isComposeAvailable())
setAconfigFlagsEnabled(true)
Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment