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