• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.flags
18 
19 import android.app.Notification
20 import android.app.NotificationChannel
21 import android.app.NotificationManager
22 import android.app.NotificationManager.IMPORTANCE_DEFAULT
23 import android.content.Context
24 import android.util.Log
25 import com.android.systemui.CoreStartable
26 import com.android.systemui.dagger.SysUISingleton
27 import com.android.systemui.util.Compile
28 import com.android.systemui.util.asIndenting
29 import com.android.systemui.util.printCollection
30 import dagger.Binds
31 import dagger.Module
32 import dagger.multibindings.ClassKey
33 import dagger.multibindings.IntoMap
34 import java.io.PrintWriter
35 import javax.inject.Inject
36 
37 /**
38  * This base class provides the helpers necessary to define dependencies between flags from the
39  * different flagging systems; classic and aconfig. This class is to be extended
40  */
41 abstract class FlagDependenciesBase(
42     private val featureFlags: FeatureFlagsClassic,
43     private val handler: Handler
44 ) : CoreStartable {
defineDependenciesnull45     protected abstract fun defineDependencies()
46 
47     private val workingDependencies = mutableListOf<Dependency>()
48     private var allDependencies = emptyList<Dependency>()
49     private var unmetDependencies = emptyList<Dependency>()
50 
51     override fun start() {
52         if (!handler.enableDependencies) {
53             return
54         }
55         defineDependencies()
56         allDependencies = workingDependencies.toList()
57         unmetDependencies = workingDependencies.filter { !it.isMet }
58         workingDependencies.clear()
59         handler.onCollected(allDependencies)
60         if (unmetDependencies.isNotEmpty()) {
61             handler.warnAboutBadFlagConfiguration(all = allDependencies, unmet = unmetDependencies)
62         }
63     }
64 
dumpnull65     override fun dump(pw: PrintWriter, args: Array<out String>) {
66         pw.asIndenting().run {
67             printCollection("allDependencies", allDependencies)
68             printCollection("unmetDependencies", unmetDependencies)
69         }
70     }
71 
72     /** A dependency where enabling the `alpha` feature depends on enabling the `beta` feature */
73     class Dependency(
74         private val alphaName: String,
75         private val alphaEnabled: Boolean,
76         private val betaName: String,
77         private val betaEnabled: Boolean
78     ) {
79         val isMet = !alphaEnabled || betaEnabled
toStringnull80         override fun toString(): String {
81             val prefix =
82                 when {
83                     !isMet -> "  [NOT MET]"
84                     alphaEnabled -> "      [met]"
85                     betaEnabled -> "    [ready]"
86                     else -> "[not ready]"
87                 }
88             val alphaState = if (alphaEnabled) "enabled" else "disabled"
89             val betaState = if (betaEnabled) "enabled" else "disabled"
90             return "$prefix $alphaName ($alphaState) DEPENDS ON $betaName ($betaState)"
91         }
92         /** Used whe posting a notification of unmet dependencies */
shortUnmetStringnull93         fun shortUnmetString(): String = "$alphaName DEPENDS ON $betaName"
94     }
95 
96     protected infix fun UnreleasedFlag.dependsOn(other: UnreleasedFlag) =
97         addDependency(this.token, other.token)
98     protected infix fun ReleasedFlag.dependsOn(other: UnreleasedFlag) =
99         addDependency(this.token, other.token)
100     protected infix fun ReleasedFlag.dependsOn(other: ReleasedFlag) =
101         addDependency(this.token, other.token)
102     protected infix fun FlagToken.dependsOn(other: UnreleasedFlag) =
103         addDependency(this, other.token)
104     protected infix fun FlagToken.dependsOn(other: ReleasedFlag) = addDependency(this, other.token)
105     protected infix fun FlagToken.dependsOn(other: FlagToken) = addDependency(this, other)
106 
107     private val UnreleasedFlag.token
108         get() = FlagToken("classic.$name", featureFlags.isEnabled(this))
109     private val ReleasedFlag.token
110         get() = FlagToken("classic.$name", featureFlags.isEnabled(this))
111 
112     /** Add a dependency to the working list */
113     private fun addDependency(first: FlagToken, second: FlagToken) {
114         if (!handler.enableDependencies) return
115         workingDependencies.add(
116             Dependency(first.name, first.isEnabled, second.name, second.isEnabled)
117         )
118     }
119 
120     /** An interface which handles dependency collection. */
121     interface Handler {
122         /**
123          * Should FlagDependencies do anything?
124          *
125          * @return false for user builds so that we skip this overhead.
126          */
127         val enableDependencies: Boolean
128             get() = Compile.IS_DEBUG
129         /** Handle the complete list of dependencies. */
onCollectednull130         fun onCollected(all: List<Dependency>) {}
131         /** Handle a bad flag configuration. */
warnAboutBadFlagConfigurationnull132         fun warnAboutBadFlagConfiguration(all: List<Dependency>, unmet: List<Dependency>)
133     }
134 }
135 
136 /**
137  * A flag dependencies handler which posts a notification and logs to logcat that the configuration
138  * is invalid.
139  */
140 @SysUISingleton
141 class FlagDependenciesNotifier
142 @Inject
143 constructor(
144     private val context: Context,
145     private val notifManager: NotificationManager,
146 ) : FlagDependenciesBase.Handler {
147     override fun warnAboutBadFlagConfiguration(
148         all: List<FlagDependenciesBase.Dependency>,
149         unmet: List<FlagDependenciesBase.Dependency>
150     ) {
151         val title = "Invalid flag dependencies: ${unmet.size}"
152         val details = unmet.joinToString("\n") { it.shortUnmetString() }
153         Log.e("FlagDependencies", "$title:\n$details")
154         val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, IMPORTANCE_DEFAULT)
155         val notification =
156             Notification.Builder(context, channel.id)
157                 .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
158                 .setContentTitle(title)
159                 .setContentText(details)
160                 .setStyle(Notification.BigTextStyle().bigText(details))
161                 .setVisibility(Notification.VISIBILITY_PUBLIC)
162                 .build()
163         notifManager.createNotificationChannel(channel)
164         notifManager.notify(NOTIF_TAG, NOTIF_ID, notification)
165     }
166 
167     override fun onCollected(all: List<FlagDependenciesBase.Dependency>) {
168         notifManager.cancel(NOTIF_TAG, NOTIF_ID)
169     }
170 
171     companion object {
172         private const val CHANNEL_ID = "FLAGS"
173         private const val CHANNEL_NAME = "Flags"
174         private const val NOTIF_TAG = "FlagDependenciesNotifier"
175         private const val NOTIF_ID = 0
176     }
177 }
178 
179 @Module
180 abstract class FlagDependenciesModule {
181 
182     /** Inject into FlagDependencies. */
183     @Binds
184     @IntoMap
185     @ClassKey(FlagDependencies::class)
bindFlagDependenciesnull186     abstract fun bindFlagDependencies(sysui: FlagDependencies): CoreStartable
187 
188     /** Bind the flag dependencies handler */
189     @Binds
190     abstract fun bindFlagDependenciesHandler(
191         handler: FlagDependenciesNotifier
192     ): FlagDependenciesBase.Handler
193 }
194