1 /*
2  * Copyright (C) 2022 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 java.io.PrintWriter
20 
21 class FakeFeatureFlags : FeatureFlags {
22     private val booleanFlags = mutableMapOf<String, Boolean>()
23     private val stringFlags = mutableMapOf<String, String>()
24     private val intFlags = mutableMapOf<String, Int>()
25     private val knownFlagNames = mutableMapOf<String, String>()
26     private val flagListeners = mutableMapOf<String, MutableSet<FlagListenable.Listener>>()
27     private val listenerflagNames = mutableMapOf<FlagListenable.Listener, MutableSet<String>>()
28 
29     init {
30         FlagsFactory.knownFlags.forEach { entry: Map.Entry<String, Flag<*>> ->
31             knownFlagNames[entry.value.name] = entry.key
32         }
33     }
34 
35     fun set(flag: BooleanFlag, value: Boolean) {
36         if (booleanFlags.put(flag.name, value)?.let { value != it } != false) {
37             notifyFlagChanged(flag)
38         }
39     }
40 
41     fun set(flag: ResourceBooleanFlag, value: Boolean) {
42         if (booleanFlags.put(flag.name, value)?.let { value != it } != false) {
43             notifyFlagChanged(flag)
44         }
45     }
46 
47     fun set(flag: SysPropBooleanFlag, value: Boolean) {
48         if (booleanFlags.put(flag.name, value)?.let { value != it } != false) {
49             notifyFlagChanged(flag)
50         }
51     }
52 
53     fun set(flag: StringFlag, value: String) {
54         if (stringFlags.put(flag.name, value)?.let { value != it } == null) {
55             notifyFlagChanged(flag)
56         }
57     }
58 
59     fun set(flag: ResourceStringFlag, value: String) {
60         if (stringFlags.put(flag.name, value)?.let { value != it } == null) {
61             notifyFlagChanged(flag)
62         }
63     }
64 
65     /**
66      * Set the given flag's default value if no other value has been set.
67      *
68      * REMINDER: You should always test your code with your flag in both configurations, so
69      *  generally you should be setting a particular value.  This method should be reserved for
70      *  situations where the flag needs to be read (e.g. in the class constructor), but its
71      *  value shouldn't affect the actual test cases. In those cases, it's mildly safer to use
72      *  this method than to hard-code `false` or `true` because then at least if you're wrong,
73      *  and the flag value *does* matter, you'll notice when the flag is flipped and tests
74      *  start failing.
75      */
76     fun setDefault(flag: BooleanFlag) = booleanFlags.putIfAbsent(flag.name, flag.default)
77 
78     /**
79      * Set the given flag's default value if no other value has been set.
80      *
81      * REMINDER: You should always test your code with your flag in both configurations, so
82      *  generally you should be setting a particular value.  This method should be reserved for
83      *  situations where the flag needs to be read (e.g. in the class constructor), but its
84      *  value shouldn't affect the actual test cases. In those cases, it's mildly safer to use
85      *  this method than to hard-code `false` or `true` because then at least if you're wrong,
86      *  and the flag value *does* matter, you'll notice when the flag is flipped and tests
87      *  start failing.
88      */
89     fun setDefault(flag: SysPropBooleanFlag) = booleanFlags.putIfAbsent(flag.name, flag.default)
90 
91     private fun notifyFlagChanged(flag: Flag<*>) {
92         flagListeners[flag.name]?.let { listeners ->
93             listeners.forEach { listener ->
94                 listener.onFlagChanged(
95                     object : FlagListenable.FlagEvent {
96                         override val flagName = flag.name
97                         override fun requestNoRestart() {}
98                     }
99                 )
100             }
101         }
102     }
103 
104     override fun isEnabled(flag: UnreleasedFlag): Boolean = requireBooleanValue(flag.name)
105 
106     override fun isEnabled(flag: ReleasedFlag): Boolean = requireBooleanValue(flag.name)
107 
108     override fun isEnabled(flag: ResourceBooleanFlag): Boolean = requireBooleanValue(flag.name)
109 
110     override fun isEnabled(flag: SysPropBooleanFlag): Boolean = requireBooleanValue(flag.name)
111 
112     override fun getString(flag: StringFlag): String = requireStringValue(flag.name)
113 
114     override fun getString(flag: ResourceStringFlag): String = requireStringValue(flag.name)
115 
116     override fun getInt(flag: IntFlag): Int = requireIntValue(flag.name)
117 
118     override fun getInt(flag: ResourceIntFlag): Int = requireIntValue(flag.name)
119 
120     override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {
121         flagListeners.getOrPut(flag.name) { mutableSetOf() }.add(listener)
122         listenerflagNames.getOrPut(listener) { mutableSetOf() }.add(flag.name)
123     }
124 
125     override fun removeListener(listener: FlagListenable.Listener) {
126         listenerflagNames.remove(listener)?.let {
127                 flagNames -> flagNames.forEach {
128                         id -> flagListeners[id]?.remove(listener)
129                 }
130         }
131     }
132 
133     override fun dump(writer: PrintWriter, args: Array<out String>?) {
134         // no-op
135     }
136 
137     private fun flagName(flagName: String): String {
138         return knownFlagNames[flagName] ?: "UNKNOWN($flagName)"
139     }
140 
141     private fun requireBooleanValue(flagName: String): Boolean {
142         return booleanFlags[flagName]
143             ?: error("Flag ${flagName(flagName)} was accessed as boolean but not specified.")
144     }
145 
146     private fun requireStringValue(flagName: String): String {
147         return stringFlags[flagName]
148             ?: error("Flag ${flagName(flagName)} was accessed as string but not specified.")
149     }
150 
151     private fun requireIntValue(flagName: String): Int {
152         return intFlags[flagName]
153             ?: error("Flag ${flagName(flagName)} was accessed as int but not specified.")
154     }
155 }
156