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