summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jeff DeCew <jeffdq@google.com> 2023-10-31 14:00:49 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-10-31 14:00:49 +0000
commitb665746a9ee06a6313eb4f03ca450a9c3e51e019 (patch)
tree20ad62c96963ad6f98223ba5be20a0ccd266d5e0
parent1f90210c73a5eebbf8436657ef682c12582593b5 (diff)
parent627e370c1c7bdf4f4af63eb2302ee27dac64c1b8 (diff)
Merge "Add flag dependencies test and notification" into main
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt158
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/OWNERS3
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationIconContainerRefactor.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FlagDependenciesTest.kt70
8 files changed, 288 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 04b2852db9e2..a41bb2f67ad2 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -54,6 +54,7 @@ import com.android.systemui.doze.dagger.DozeComponent;
import com.android.systemui.dreams.dagger.DreamModule;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FlagDependenciesModule;
import com.android.systemui.flags.FlagsModule;
import com.android.systemui.keyboard.KeyboardModule;
import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule;
@@ -182,6 +183,7 @@ import javax.inject.Named;
DreamModule.class,
FalsingModule.class,
FlagsModule.class,
+ FlagDependenciesModule.class,
FooterActionsModule.class,
KeyEventRepositoryModule.class,
KeyboardModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
new file mode 100644
index 000000000000..730a7a60d5c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ * 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.systemui.flags
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.Flags as Classic
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
+import javax.inject.Inject
+
+/** A class in which engineers can define flag dependencies */
+@SysUISingleton
+class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, handler: Handler) :
+ FlagDependenciesBase(featureFlags, handler) {
+ override fun defineDependencies() {
+ FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
+ NotificationIconContainerRefactor.token dependsOn Classic.NOTIFICATION_SHELF_REFACTOR
+
+ // These two flags are effectively linked. We should migrate them to a single aconfig flag.
+ Classic.MIGRATE_NSSL dependsOn Classic.MIGRATE_KEYGUARD_STATUS_VIEW
+ Classic.MIGRATE_KEYGUARD_STATUS_VIEW dependsOn Classic.MIGRATE_NSSL
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
new file mode 100644
index 000000000000..ae3b501d3006
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
@@ -0,0 +1,158 @@
+/*
+ * 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.
+ * 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.systemui.flags
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Context
+import android.util.Log
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.Compile
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.withIncreasedIndent
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * This base class provides the helpers necessary to define dependencies between flags from the
+ * different flagging systems; classic and aconfig. This class is to be extended
+ */
+abstract class FlagDependenciesBase(
+ private val featureFlags: FeatureFlagsClassic,
+ private val handler: Handler
+) : CoreStartable {
+ protected abstract fun defineDependencies()
+
+ private val workingDependencies = mutableListOf<Dependency>()
+ private var allDependencies = emptyList<Dependency>()
+ private var unmetDependencies = emptyList<Dependency>()
+
+ override fun start() {
+ defineDependencies()
+ allDependencies = workingDependencies.toList()
+ unmetDependencies = workingDependencies.filter { !it.isMet }
+ workingDependencies.clear()
+ if (unmetDependencies.isNotEmpty()) {
+ handler.warnAboutBadFlagConfiguration(all = allDependencies, unmet = unmetDependencies)
+ }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.asIndenting().run {
+ println("allDependencies: ${allDependencies.size}")
+ withIncreasedIndent { allDependencies.forEach(::println) }
+ println("unmetDependencies: ${unmetDependencies.size}")
+ withIncreasedIndent { unmetDependencies.forEach(::println) }
+ }
+ }
+
+ /** A dependency where enabling the `alpha` feature depends on enabling the `beta` feature */
+ class Dependency(
+ private val alphaName: String,
+ private val alphaEnabled: Boolean,
+ private val betaName: String,
+ private val betaEnabled: Boolean
+ ) {
+ val isMet = !alphaEnabled || betaEnabled
+ override fun toString(): String {
+ val isMetBullet = if (isMet) "+" else "-"
+ return "$isMetBullet $alphaName ($alphaEnabled) DEPENDS ON $betaName ($betaEnabled)"
+ }
+ }
+
+ protected infix fun UnreleasedFlag.dependsOn(other: UnreleasedFlag) =
+ addDependency(this.token, other.token)
+ protected infix fun ReleasedFlag.dependsOn(other: UnreleasedFlag) =
+ addDependency(this.token, other.token)
+ protected infix fun ReleasedFlag.dependsOn(other: ReleasedFlag) =
+ addDependency(this.token, other.token)
+ protected infix fun FlagToken.dependsOn(other: UnreleasedFlag) =
+ addDependency(this, other.token)
+ protected infix fun FlagToken.dependsOn(other: ReleasedFlag) = addDependency(this, other.token)
+ protected infix fun FlagToken.dependsOn(other: FlagToken) = addDependency(this, other)
+
+ private val UnreleasedFlag.token
+ get() = FlagToken("classic.$name", featureFlags.isEnabled(this))
+ private val ReleasedFlag.token
+ get() = FlagToken("classic.$name", featureFlags.isEnabled(this))
+
+ /** Add a dependency to the working list */
+ private fun addDependency(first: FlagToken, second: FlagToken) {
+ if (!Compile.IS_DEBUG) return // `user` builds should omit all this code
+ workingDependencies.add(
+ Dependency(first.name, first.isEnabled, second.name, second.isEnabled)
+ )
+ }
+
+ /** An interface which handles a warning about a bad flag configuration. */
+ interface Handler {
+ fun warnAboutBadFlagConfiguration(all: List<Dependency>, unmet: List<Dependency>)
+ }
+}
+
+/**
+ * A flag dependencies handler which posts a notification and logs to logcat that the configuration
+ * is invalid.
+ */
+@SysUISingleton
+class FlagDependenciesNotifier
+@Inject
+constructor(
+ private val context: Context,
+ private val notifManager: NotificationManager,
+) : FlagDependenciesBase.Handler {
+ override fun warnAboutBadFlagConfiguration(
+ all: List<FlagDependenciesBase.Dependency>,
+ unmet: List<FlagDependenciesBase.Dependency>
+ ) {
+ val title = "Invalid flag dependencies: ${unmet.size} of ${all.size}"
+ val details = unmet.joinToString("\n")
+ Log.e("FlagDependencies", "$title:\n$details")
+ val channel = NotificationChannel("FLAGS", "Flags", NotificationManager.IMPORTANCE_DEFAULT)
+ val notification =
+ Notification.Builder(context, channel.id)
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+ .setContentTitle(title)
+ .setContentText(details)
+ .setStyle(Notification.BigTextStyle().bigText(details))
+ .build()
+ notifManager.createNotificationChannel(channel)
+ notifManager.notify("flags", 0, notification)
+ }
+}
+
+@Module
+abstract class FlagDependenciesModule {
+
+ /** Inject into FlagDependencies. */
+ @Binds
+ @IntoMap
+ @ClassKey(FlagDependencies::class)
+ abstract fun bindFlagDependencies(sysui: FlagDependencies): CoreStartable
+
+ /** Bind the flag dependencies handler */
+ @Binds
+ abstract fun bindFlagDependenciesHandler(
+ handler: FlagDependenciesNotifier
+ ): FlagDependenciesBase.Handler
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/OWNERS b/packages/SystemUI/src/com/android/systemui/flags/OWNERS
index c9d2db1d8acb..57ebccbd6b8b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/flags/OWNERS
@@ -10,3 +10,6 @@ cinek@google.com
alexflo@google.com
dsandler@android.com
adamcohen@google.com
+
+# Anyone in System UI can declare dependencies between flags
+per-file FlagDependencies.kt = file:../../../../../OWNERS
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt
index db31e36a9625..ae67e60b75f7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt
@@ -26,6 +26,7 @@ import android.util.Log
* ```
* object SomeRefactor {
* const val FLAG_NAME = Flags.SOME_REFACTOR
+ * val token: FlagToken get() = FlagToken(FLAG_NAME, isEnabled)
* @JvmStatic inline val isEnabled get() = Flags.someRefactor()
* @JvmStatic inline fun isUnexpectedlyInLegacyMode() =
* RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
@@ -105,3 +106,8 @@ object RefactorFlagUtils {
/** Tag used for non-crashing logs or when the [ASSERT_TAG] has been silenced. */
private const val STANDARD_TAG = "RefactorFlag"
}
+
+/** An object which allows dependency tracking */
+data class FlagToken(val name: String, val isEnabled: Boolean) {
+ override fun toString(): String = "$name (${if (isEnabled) "enabled" else "disabled"})"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt
index 94e70e5521c3..7e6044eb6869 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt
@@ -17,13 +17,19 @@
package com.android.systemui.statusbar.notification.footer.shared
import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the FooterView refactor flag state. */
@Suppress("NOTHING_TO_INLINE")
object FooterViewRefactor {
+ /** The aconfig flag name */
const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
/** Is the refactor enabled */
@JvmStatic
inline val isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationIconContainerRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationIconContainerRefactor.kt
index dee609cd8105..a08af7554467 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationIconContainerRefactor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationIconContainerRefactor.kt
@@ -16,13 +16,19 @@
package com.android.systemui.statusbar.notification.shared
import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the NotificationIconContainer refactor flag state. */
@Suppress("NOTHING_TO_INLINE")
object NotificationIconContainerRefactor {
+ /** The aconfig flag name */
const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_ICON_CONTAINER_REFACTOR
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
/** Is the refactor enabled? */
@JvmStatic
inline val isEnabled
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagDependenciesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagDependenciesTest.kt
new file mode 100644
index 000000000000..936aa8bb819c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagDependenciesTest.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ * 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.systemui.flags
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import java.io.PrintWriter
+import kotlin.test.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FlagDependenciesTest : SysuiTestCase() {
+ @Test
+ fun testRelease() {
+ testFlagDependencies(teamfood = false).start()
+ }
+
+ @Test
+ fun testTeamfood() {
+ testFlagDependencies(teamfood = true).start()
+ }
+
+ private fun testFlagDependencies(teamfood: Boolean) =
+ FlagDependencies(TestFeatureFlags(teamfood = teamfood), TestHandler())
+
+ private class TestHandler : FlagDependenciesBase.Handler {
+ override fun warnAboutBadFlagConfiguration(
+ all: List<FlagDependenciesBase.Dependency>,
+ unmet: List<FlagDependenciesBase.Dependency>
+ ) {
+ val title = "${unmet.size} invalid of ${all.size} flag dependencies"
+ val details = unmet.joinToString("\n")
+ fail("$title:\n$details")
+ }
+ }
+
+ private class TestFeatureFlags(val teamfood: Boolean) : FeatureFlagsClassic {
+ private val unsupported: Nothing
+ get() = fail("Unsupported")
+
+ override fun isEnabled(flag: ReleasedFlag): Boolean = true
+ override fun isEnabled(flag: UnreleasedFlag): Boolean = teamfood && flag.teamfood
+ override fun isEnabled(flag: ResourceBooleanFlag): Boolean = unsupported
+ override fun isEnabled(flag: SysPropBooleanFlag): Boolean = unsupported
+ override fun getString(flag: StringFlag): String = unsupported
+ override fun getString(flag: ResourceStringFlag): String = unsupported
+ override fun getInt(flag: IntFlag): Int = unsupported
+ override fun getInt(flag: ResourceIntFlag): Int = unsupported
+ override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) = unsupported
+ override fun removeListener(listener: FlagListenable.Listener) = unsupported
+ override fun dump(writer: PrintWriter, args: Array<out String>?) = unsupported
+ }
+}