1 /* 2 * Copyright (C) 2021 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 static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS; 20 21 import static java.util.Objects.requireNonNull; 22 23 import android.content.res.Resources; 24 25 import androidx.annotation.NonNull; 26 27 import com.android.systemui.dagger.SysUISingleton; 28 import com.android.systemui.dagger.qualifiers.Main; 29 30 import org.jetbrains.annotations.NotNull; 31 32 import java.io.PrintWriter; 33 import java.util.HashMap; 34 import java.util.Map; 35 36 import javax.inject.Inject; 37 import javax.inject.Named; 38 39 /** 40 * Default implementation of the a Flag manager that returns default values for release builds 41 * 42 * There's a version of this file in src-debug which allows overriding, and has documentation about 43 * how to set flags. 44 */ 45 @SysUISingleton 46 public class FeatureFlagsClassicRelease implements FeatureFlagsClassic { 47 static final String TAG = "SysUIFlags"; 48 49 private final Resources mResources; 50 private final SystemPropertiesHelper mSystemProperties; 51 private final ServerFlagReader mServerFlagReader; 52 private final Restarter mRestarter; 53 private final Map<String, Flag<?>> mAllFlags; 54 private final Map<String, Boolean> mBooleanCache = new HashMap<>(); 55 private final Map<String, String> mStringCache = new HashMap<>(); 56 private final Map<String, Integer> mIntCache = new HashMap<>(); 57 58 private final ServerFlagReader.ChangeListener mOnPropertiesChanged = 59 new ServerFlagReader.ChangeListener() { 60 @Override 61 public void onChange(Flag<?> flag, String value) { 62 boolean shouldRestart = false; 63 if (mBooleanCache.containsKey(flag.getName())) { 64 boolean newValue = value == null ? false : Boolean.parseBoolean(value); 65 if (mBooleanCache.get(flag.getName()) != newValue) { 66 shouldRestart = true; 67 } 68 } else if (mStringCache.containsKey(flag.getName())) { 69 String newValue = value == null ? "" : value; 70 if (!mStringCache.get(flag.getName()).equals(newValue)) { 71 shouldRestart = true; 72 } 73 } else if (mIntCache.containsKey(flag.getName())) { 74 int newValue = 0; 75 try { 76 newValue = value == null ? 0 : Integer.parseInt(value); 77 } catch (NumberFormatException e) { 78 } 79 if (mIntCache.get(flag.getName()) != newValue) { 80 shouldRestart = true; 81 } 82 } 83 if (shouldRestart) { 84 mRestarter.restartSystemUI( 85 "Server flag change: " + flag.getNamespace() + "." 86 + flag.getName()); 87 } 88 } 89 }; 90 91 @Inject FeatureFlagsClassicRelease( @ain Resources resources, SystemPropertiesHelper systemProperties, ServerFlagReader serverFlagReader, @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags, Restarter restarter)92 public FeatureFlagsClassicRelease( 93 @Main Resources resources, 94 SystemPropertiesHelper systemProperties, 95 ServerFlagReader serverFlagReader, 96 @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags, 97 Restarter restarter) { 98 mResources = resources; 99 mSystemProperties = systemProperties; 100 mServerFlagReader = serverFlagReader; 101 mAllFlags = allFlags; 102 mRestarter = restarter; 103 } 104 105 /** Call after construction to setup listeners. */ init()106 void init() { 107 mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged); 108 } 109 110 @Override addListener(@onNull Flag<?> flag, @NonNull Listener listener)111 public void addListener(@NonNull Flag<?> flag, @NonNull Listener listener) { 112 } 113 114 @Override removeListener(@onNull Listener listener)115 public void removeListener(@NonNull Listener listener) { 116 } 117 118 @Override isEnabled(@otNull UnreleasedFlag flag)119 public boolean isEnabled(@NotNull UnreleasedFlag flag) { 120 return false; 121 } 122 123 @Override isEnabled(@otNull ReleasedFlag flag)124 public boolean isEnabled(@NotNull ReleasedFlag flag) { 125 // Fill the cache. 126 return isEnabledInternal(flag.getName(), 127 mServerFlagReader.readServerOverride(flag.getNamespace(), flag.getName(), true)); 128 } 129 130 @Override isEnabled(ResourceBooleanFlag flag)131 public boolean isEnabled(ResourceBooleanFlag flag) { 132 // Fill the cache. 133 return isEnabledInternal(flag.getName(), mResources.getBoolean(flag.getResourceId())); 134 } 135 136 @Override isEnabled(SysPropBooleanFlag flag)137 public boolean isEnabled(SysPropBooleanFlag flag) { 138 // Fill the cache. 139 return isEnabledInternal( 140 flag.getName(), 141 mSystemProperties.getBoolean(flag.getName(), flag.getDefault())); 142 } 143 144 /** 145 * Checks and fills the boolean cache. This is important, Always call through to this method! 146 * 147 * We use the cache as a way to decide if we need to restart the process when server-side 148 * changes occur. 149 */ isEnabledInternal(String name, boolean defaultValue)150 private boolean isEnabledInternal(String name, boolean defaultValue) { 151 // Fill the cache. 152 if (!mBooleanCache.containsKey(name)) { 153 mBooleanCache.put(name, defaultValue); 154 } 155 156 return mBooleanCache.get(name); 157 } 158 159 @NonNull 160 @Override getString(@onNull StringFlag flag)161 public String getString(@NonNull StringFlag flag) { 162 // Fill the cache. 163 return getStringInternal(flag.getName(), flag.getDefault()); 164 } 165 166 @NonNull 167 @Override getString(@onNull ResourceStringFlag flag)168 public String getString(@NonNull ResourceStringFlag flag) { 169 // Fill the cache. 170 return getStringInternal(flag.getName(), 171 requireNonNull(mResources.getString(flag.getResourceId()))); 172 } 173 174 /** 175 * Checks and fills the String cache. This is important, Always call through to this method! 176 * 177 * We use the cache as a way to decide if we need to restart the process when server-side 178 * changes occur. 179 */ getStringInternal(String name, String defaultValue)180 private String getStringInternal(String name, String defaultValue) { 181 if (!mStringCache.containsKey(name)) { 182 mStringCache.put(name, defaultValue); 183 } 184 185 return mStringCache.get(name); 186 } 187 188 @Override getInt(@onNull IntFlag flag)189 public int getInt(@NonNull IntFlag flag) { 190 // Fill the cache. 191 return getIntInternal(flag.getName(), flag.getDefault()); 192 } 193 194 @Override getInt(@onNull ResourceIntFlag flag)195 public int getInt(@NonNull ResourceIntFlag flag) { 196 // Fill the cache. 197 return mResources.getInteger(flag.getResourceId()); 198 } 199 200 /** 201 * Checks and fills the integer cache. This is important, Always call through to this method! 202 * 203 * We use the cache as a way to decide if we need to restart the process when server-side 204 * changes occur. 205 */ getIntInternal(String name, int defaultValue)206 private int getIntInternal(String name, int defaultValue) { 207 if (!mIntCache.containsKey(name)) { 208 mIntCache.put(name, defaultValue); 209 } 210 211 return mIntCache.get(name); 212 } 213 214 @Override dump(@onNull PrintWriter pw, @NonNull String[] args)215 public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { 216 pw.println("can override: false"); 217 Map<String, Flag<?>> knownFlags = FlagsFactory.INSTANCE.getKnownFlags(); 218 pw.println("Booleans: "); 219 for (Map.Entry<String, Flag<?>> nameToFlag : knownFlags.entrySet()) { 220 Flag<?> flag = nameToFlag.getValue(); 221 if (!(flag instanceof BooleanFlag) 222 || !(flag instanceof ResourceBooleanFlag) 223 || !(flag instanceof SysPropBooleanFlag)) { 224 continue; 225 } 226 227 boolean def = false; 228 if (!mBooleanCache.containsKey(flag.getName())) { 229 if (flag instanceof SysPropBooleanFlag) { 230 SysPropBooleanFlag f = (SysPropBooleanFlag) flag; 231 def = mSystemProperties.getBoolean(f.getName(), f.getDefault()); 232 } else if (flag instanceof ResourceBooleanFlag) { 233 ResourceBooleanFlag f = (ResourceBooleanFlag) flag; 234 def = mResources.getBoolean(f.getResourceId()); 235 } else if (flag instanceof BooleanFlag) { 236 BooleanFlag f = (BooleanFlag) flag; 237 def = f.getDefault(); 238 } 239 } 240 pw.println( 241 " " + flag.getName() + ": " 242 + (mBooleanCache.getOrDefault(flag.getName(), def))); 243 } 244 245 pw.println("Strings: "); 246 for (Map.Entry<String, Flag<?>> nameToFlag : knownFlags.entrySet()) { 247 Flag<?> flag = nameToFlag.getValue(); 248 if (!(flag instanceof StringFlag) 249 || !(flag instanceof ResourceStringFlag)) { 250 continue; 251 } 252 253 String def = ""; 254 if (!mBooleanCache.containsKey(flag.getName())) { 255 if (flag instanceof ResourceStringFlag) { 256 ResourceStringFlag f = (ResourceStringFlag) flag; 257 def = mResources.getString(f.getResourceId()); 258 } else if (flag instanceof StringFlag) { 259 StringFlag f = (StringFlag) flag; 260 def = f.getDefault(); 261 } 262 } 263 String value = mStringCache.getOrDefault(flag.getName(), def); 264 pw.println( 265 " " + flag.getName() + ": [length=" + value.length() + "] \"" + value + "\""); 266 } 267 } 268 } 269