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 android.flags; 18 19 import android.annotation.NonNull; 20 import android.content.Context; 21 import android.os.RemoteException; 22 import android.os.ServiceManager; 23 import android.util.ArraySet; 24 import android.util.Log; 25 26 import com.android.internal.annotations.VisibleForTesting; 27 28 import java.util.ArrayList; 29 import java.util.HashMap; 30 import java.util.HashSet; 31 import java.util.List; 32 import java.util.Map; 33 import java.util.Set; 34 35 /** 36 * A class for querying constants from the system - primarily booleans. 37 * 38 * Clients using this class can define their flags and their default values in one place, 39 * can override those values on running devices for debugging and testing purposes, and can control 40 * what flags are available to be used on release builds. 41 * 42 * TODO(b/279054964): A lot. This is skeleton code right now. 43 * @hide 44 */ 45 public class FeatureFlags { 46 private static final String TAG = "FeatureFlags"; 47 private static FeatureFlags sInstance; 48 private static final Object sInstanceLock = new Object(); 49 50 private final Set<Flag<?>> mKnownFlags = new ArraySet<>(); 51 private final Set<Flag<?>> mDirtyFlags = new ArraySet<>(); 52 53 private IFeatureFlags mIFeatureFlags; 54 private final Map<String, Map<String, Boolean>> mBooleanOverrides = new HashMap<>(); 55 private final Set<ChangeListener> mListeners = new HashSet<>(); 56 57 /** 58 * Obtain a per-process instance of FeatureFlags. 59 * @return A singleton instance of {@link FeatureFlags}. 60 */ 61 @NonNull getInstance()62 public static FeatureFlags getInstance() { 63 synchronized (sInstanceLock) { 64 if (sInstance == null) { 65 sInstance = new FeatureFlags(); 66 } 67 } 68 69 return sInstance; 70 } 71 72 /** See {@link FeatureFlagsFake}. */ 73 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) setInstance(FeatureFlags instance)74 public static void setInstance(FeatureFlags instance) { 75 synchronized (sInstanceLock) { 76 sInstance = instance; 77 } 78 } 79 80 private final IFeatureFlagsCallback mIFeatureFlagsCallback = new IFeatureFlagsCallback.Stub() { 81 @Override 82 public void onFlagChange(SyncableFlag flag) { 83 for (Flag<?> f : mKnownFlags) { 84 if (flagEqualsSyncableFlag(f, flag)) { 85 if (f instanceof DynamicFlag<?>) { 86 if (f instanceof DynamicBooleanFlag) { 87 String value = flag.getValue(); 88 if (value == null) { // Null means any existing overrides were erased. 89 value = ((DynamicBooleanFlag) f).getDefault().toString(); 90 } 91 addBooleanOverride(flag.getNamespace(), flag.getName(), value); 92 } 93 FeatureFlags.this.onFlagChange((DynamicFlag<?>) f); 94 } 95 break; 96 } 97 } 98 } 99 }; 100 FeatureFlags()101 private FeatureFlags() { 102 this(null); 103 } 104 105 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) FeatureFlags(IFeatureFlags iFeatureFlags)106 public FeatureFlags(IFeatureFlags iFeatureFlags) { 107 mIFeatureFlags = iFeatureFlags; 108 109 if (mIFeatureFlags != null) { 110 try { 111 mIFeatureFlags.registerCallback(mIFeatureFlagsCallback); 112 } catch (RemoteException e) { 113 // Shouldn't happen with things passed into tests. 114 Log.e(TAG, "Could not register callbacks!", e); 115 } 116 } 117 } 118 119 /** 120 * Construct a new {@link BooleanFlag}. 121 * 122 * Use this instead of constructing a {@link BooleanFlag} directly, as it registers the flag 123 * with the internals of the flagging system. 124 */ 125 @NonNull booleanFlag( @onNull String namespace, @NonNull String name, boolean def)126 public static BooleanFlag booleanFlag( 127 @NonNull String namespace, @NonNull String name, boolean def) { 128 return getInstance().addFlag(new BooleanFlag(namespace, name, def)); 129 } 130 131 /** 132 * Construct a new {@link FusedOffFlag}. 133 * 134 * Use this instead of constructing a {@link FusedOffFlag} directly, as it registers the 135 * flag with the internals of the flagging system. 136 */ 137 @NonNull fusedOffFlag(@onNull String namespace, @NonNull String name)138 public static FusedOffFlag fusedOffFlag(@NonNull String namespace, @NonNull String name) { 139 return getInstance().addFlag(new FusedOffFlag(namespace, name)); 140 } 141 142 /** 143 * Construct a new {@link FusedOnFlag}. 144 * 145 * Use this instead of constructing a {@link FusedOnFlag} directly, as it registers the flag 146 * with the internals of the flagging system. 147 */ 148 @NonNull fusedOnFlag(@onNull String namespace, @NonNull String name)149 public static FusedOnFlag fusedOnFlag(@NonNull String namespace, @NonNull String name) { 150 return getInstance().addFlag(new FusedOnFlag(namespace, name)); 151 } 152 153 /** 154 * Construct a new {@link DynamicBooleanFlag}. 155 * 156 * Use this instead of constructing a {@link DynamicBooleanFlag} directly, as it registers 157 * the flag with the internals of the flagging system. 158 */ 159 @NonNull dynamicBooleanFlag( @onNull String namespace, @NonNull String name, boolean def)160 public static DynamicBooleanFlag dynamicBooleanFlag( 161 @NonNull String namespace, @NonNull String name, boolean def) { 162 return getInstance().addFlag(new DynamicBooleanFlag(namespace, name, def)); 163 } 164 165 /** 166 * Add a listener to be alerted when a {@link DynamicFlag} changes. 167 * 168 * See also {@link #removeChangeListener(ChangeListener)}. 169 * 170 * @param listener The listener to add. 171 */ addChangeListener(@onNull ChangeListener listener)172 public void addChangeListener(@NonNull ChangeListener listener) { 173 mListeners.add(listener); 174 } 175 176 /** 177 * Remove a listener that was added earlier. 178 * 179 * See also {@link #addChangeListener(ChangeListener)}. 180 * 181 * @param listener The listener to remove. 182 */ removeChangeListener(@onNull ChangeListener listener)183 public void removeChangeListener(@NonNull ChangeListener listener) { 184 mListeners.remove(listener); 185 } 186 onFlagChange(@onNull DynamicFlag<?> flag)187 protected void onFlagChange(@NonNull DynamicFlag<?> flag) { 188 for (ChangeListener l : mListeners) { 189 l.onFlagChanged(flag); 190 } 191 } 192 193 /** 194 * Returns whether the supplied flag is true or not. 195 * 196 * {@link BooleanFlag} should only be used in debug builds. They do not get optimized out. 197 * 198 * The first time a flag is read, its value is cached for the lifetime of the process. 199 */ isEnabled(@onNull BooleanFlag flag)200 public boolean isEnabled(@NonNull BooleanFlag flag) { 201 return getBooleanInternal(flag); 202 } 203 204 /** 205 * Returns whether the supplied flag is true or not. 206 * 207 * Always returns false. 208 */ isEnabled(@onNull FusedOffFlag flag)209 public boolean isEnabled(@NonNull FusedOffFlag flag) { 210 return false; 211 } 212 213 /** 214 * Returns whether the supplied flag is true or not. 215 * 216 * Always returns true; 217 */ isEnabled(@onNull FusedOnFlag flag)218 public boolean isEnabled(@NonNull FusedOnFlag flag) { 219 return true; 220 } 221 222 /** 223 * Returns whether the supplied flag is true or not. 224 * 225 * Can return a different value for the flag each time it is called if an override comes in. 226 */ isCurrentlyEnabled(@onNull DynamicBooleanFlag flag)227 public boolean isCurrentlyEnabled(@NonNull DynamicBooleanFlag flag) { 228 return getBooleanInternal(flag); 229 } 230 getBooleanInternal(Flag<Boolean> flag)231 private boolean getBooleanInternal(Flag<Boolean> flag) { 232 sync(); 233 Map<String, Boolean> ns = mBooleanOverrides.get(flag.getNamespace()); 234 Boolean value = null; 235 if (ns != null) { 236 value = ns.get(flag.getName()); 237 } 238 if (value == null) { 239 throw new IllegalStateException("Boolean flag being read but was not synced: " + flag); 240 } 241 242 return value; 243 } 244 addFlag(T flag)245 private <T extends Flag<?>> T addFlag(T flag) { 246 synchronized (FeatureFlags.class) { 247 mDirtyFlags.add(flag); 248 mKnownFlags.add(flag); 249 } 250 return flag; 251 } 252 253 /** 254 * Sync any known flags that have not yet been synced. 255 * 256 * This is called implicitly when any flag is read, and is not generally needed except in 257 * exceptional circumstances. 258 */ sync()259 public void sync() { 260 synchronized (FeatureFlags.class) { 261 if (mDirtyFlags.isEmpty()) { 262 return; 263 } 264 syncInternal(mDirtyFlags); 265 mDirtyFlags.clear(); 266 } 267 } 268 269 /** 270 * Called when new flags have been declared. Gives the implementation a chance to act on them. 271 * 272 * Guaranteed to be called from a synchronized, thread-safe context. 273 */ syncInternal(Set<Flag<?>> dirtyFlags)274 protected void syncInternal(Set<Flag<?>> dirtyFlags) { 275 IFeatureFlags iFeatureFlags = bind(); 276 List<SyncableFlag> syncableFlags = new ArrayList<>(); 277 for (Flag<?> f : dirtyFlags) { 278 syncableFlags.add(flagToSyncableFlag(f)); 279 } 280 281 List<SyncableFlag> serverFlags = List.of(); // Need to initialize the list with something. 282 try { 283 // New values come back from the service. 284 serverFlags = iFeatureFlags.syncFlags(syncableFlags); 285 } catch (RemoteException e) { 286 e.rethrowFromSystemServer(); 287 } 288 289 for (Flag<?> f : dirtyFlags) { 290 boolean found = false; 291 for (SyncableFlag sf : serverFlags) { 292 if (flagEqualsSyncableFlag(f, sf)) { 293 if (f instanceof BooleanFlag || f instanceof DynamicBooleanFlag) { 294 addBooleanOverride(sf.getNamespace(), sf.getName(), sf.getValue()); 295 } 296 found = true; 297 break; 298 } 299 } 300 if (!found) { 301 if (f instanceof BooleanFlag) { 302 addBooleanOverride( 303 f.getNamespace(), 304 f.getName(), 305 ((BooleanFlag) f).getDefault() ? "true" : "false"); 306 } 307 } 308 } 309 } 310 addBooleanOverride(String namespace, String name, String override)311 private void addBooleanOverride(String namespace, String name, String override) { 312 Map<String, Boolean> nsOverrides = mBooleanOverrides.get(namespace); 313 if (nsOverrides == null) { 314 nsOverrides = new HashMap<>(); 315 mBooleanOverrides.put(namespace, nsOverrides); 316 } 317 nsOverrides.put(name, parseBoolean(override)); 318 } 319 flagToSyncableFlag(Flag<?> f)320 private SyncableFlag flagToSyncableFlag(Flag<?> f) { 321 return new SyncableFlag( 322 f.getNamespace(), 323 f.getName(), 324 f.getDefault().toString(), 325 f instanceof DynamicFlag<?>); 326 } 327 bind()328 private IFeatureFlags bind() { 329 if (mIFeatureFlags == null) { 330 mIFeatureFlags = IFeatureFlags.Stub.asInterface( 331 ServiceManager.getService(Context.FEATURE_FLAGS_SERVICE)); 332 try { 333 mIFeatureFlags.registerCallback(mIFeatureFlagsCallback); 334 } catch (RemoteException e) { 335 Log.e(TAG, "Failed to listen for flag changes!"); 336 } 337 } 338 339 return mIFeatureFlags; 340 } 341 parseBoolean(String value)342 static boolean parseBoolean(String value) { 343 // Check for a truish string. 344 boolean result = value.equalsIgnoreCase("true") 345 || value.equals("1") 346 || value.equalsIgnoreCase("t") 347 || value.equalsIgnoreCase("on"); 348 if (!result) { // Expect a falsish string, else log an error. 349 if (!(value.equalsIgnoreCase("false") 350 || value.equals("0") 351 || value.equalsIgnoreCase("f") 352 || value.equalsIgnoreCase("off"))) { 353 Log.e(TAG, 354 "Tried parsing " + value + " as boolean but it doesn't look like one. " 355 + "Value expected to be one of true|false, 1|0, t|f, on|off."); 356 } 357 } 358 return result; 359 } 360 flagEqualsSyncableFlag(Flag<?> f, SyncableFlag sf)361 private static boolean flagEqualsSyncableFlag(Flag<?> f, SyncableFlag sf) { 362 return f.getName().equals(sf.getName()) && f.getNamespace().equals(sf.getNamespace()); 363 } 364 365 366 /** 367 * A simpler listener that is alerted when a {@link DynamicFlag} changes. 368 * 369 * See {@link #addChangeListener(ChangeListener)} 370 */ 371 public interface ChangeListener { 372 /** 373 * Called when a {@link DynamicFlag} changes. 374 * 375 * @param flag The flag that has changed. 376 */ onFlagChanged(DynamicFlag<?> flag)377 void onFlagChanged(DynamicFlag<?> flag); 378 } 379 } 380