Loading packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +2 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -182,6 +183,7 @@ import javax.inject.Named; DreamModule.class, FalsingModule.class, FlagsModule.class, FlagDependenciesModule.class, FooterActionsModule.class, KeyEventRepositoryModule.class, KeyboardModule.class, Loading packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt 0 → 100644 +37 −0 Original line number Diff line number Diff line /* * 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 } } packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt 0 → 100644 +158 −0 Original line number Diff line number Diff line /* * 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 } packages/SystemUI/src/com/android/systemui/flags/OWNERS +3 −0 Original line number Diff line number Diff line Loading @@ -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 packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt +6 −0 Original line number Diff line number Diff line Loading @@ -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) Loading Loading @@ -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"})" } Loading
packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +2 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -182,6 +183,7 @@ import javax.inject.Named; DreamModule.class, FalsingModule.class, FlagsModule.class, FlagDependenciesModule.class, FooterActionsModule.class, KeyEventRepositoryModule.class, KeyboardModule.class, Loading
packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt 0 → 100644 +37 −0 Original line number Diff line number Diff line /* * 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 } }
packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt 0 → 100644 +158 −0 Original line number Diff line number Diff line /* * 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 }
packages/SystemUI/src/com/android/systemui/flags/OWNERS +3 −0 Original line number Diff line number Diff line Loading @@ -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
packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt +6 −0 Original line number Diff line number Diff line Loading @@ -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) Loading Loading @@ -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"})" }