• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.android.adservices.shared.testing;
17 
18 import static com.android.adservices.shared.common.flags.Constants.ARRAY_SPLITTER_COMMA;
19 
20 import com.android.adservices.shared.testing.Logger.LogLevel;
21 import com.android.adservices.shared.testing.Logger.RealLogger;
22 import com.android.adservices.shared.testing.NameValuePair.Matcher;
23 import com.android.adservices.shared.testing.annotations.DisableDebugFlag;
24 import com.android.adservices.shared.testing.annotations.DisableDebugFlags;
25 import com.android.adservices.shared.testing.annotations.EnableDebugFlag;
26 import com.android.adservices.shared.testing.annotations.EnableDebugFlags;
27 import com.android.adservices.shared.testing.annotations.SetDoubleFlag;
28 import com.android.adservices.shared.testing.annotations.SetDoubleFlags;
29 import com.android.adservices.shared.testing.annotations.SetFlagDisabled;
30 import com.android.adservices.shared.testing.annotations.SetFlagEnabled;
31 import com.android.adservices.shared.testing.annotations.SetFlagFalse;
32 import com.android.adservices.shared.testing.annotations.SetFlagTrue;
33 import com.android.adservices.shared.testing.annotations.SetFlagsDisabled;
34 import com.android.adservices.shared.testing.annotations.SetFlagsEnabled;
35 import com.android.adservices.shared.testing.annotations.SetFlagsFalse;
36 import com.android.adservices.shared.testing.annotations.SetFlagsTrue;
37 import com.android.adservices.shared.testing.annotations.SetFloatFlag;
38 import com.android.adservices.shared.testing.annotations.SetFloatFlags;
39 import com.android.adservices.shared.testing.annotations.SetIntegerFlag;
40 import com.android.adservices.shared.testing.annotations.SetIntegerFlags;
41 import com.android.adservices.shared.testing.annotations.SetLogcatTag;
42 import com.android.adservices.shared.testing.annotations.SetLogcatTags;
43 import com.android.adservices.shared.testing.annotations.SetLongDebugFlag;
44 import com.android.adservices.shared.testing.annotations.SetLongDebugFlags;
45 import com.android.adservices.shared.testing.annotations.SetLongFlag;
46 import com.android.adservices.shared.testing.annotations.SetLongFlags;
47 import com.android.adservices.shared.testing.annotations.SetStringArrayFlag;
48 import com.android.adservices.shared.testing.annotations.SetStringArrayFlags;
49 import com.android.adservices.shared.testing.annotations.SetStringFlag;
50 import com.android.adservices.shared.testing.annotations.SetStringFlags;
51 import com.android.adservices.shared.testing.concurrency.SyncCallback;
52 import com.android.adservices.shared.testing.device.DeviceConfig;
53 
54 import com.google.errorprone.annotations.FormatMethod;
55 import com.google.errorprone.annotations.FormatString;
56 
57 import org.junit.runner.Description;
58 import org.junit.runners.model.Statement;
59 
60 import java.lang.annotation.Annotation;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.Collection;
64 import java.util.LinkedHashSet;
65 import java.util.List;
66 import java.util.Map;
67 import java.util.Objects;
68 import java.util.Set;
69 
70 // TODO(b/294423183): add unit tests for the most relevant / less repetitive stuff (don't need to
71 // test all setters / getters, for example)
72 /**
73  * Rule used to properly set "Android flags"- it will take care of permissions, restoring values at
74  * the end, setting {@link android.provider.DeviceConfig} or {@link android.os.SystemProperties},
75  * etc...
76  *
77  * @param <T> type of the concrete rule
78  */
79 public abstract class AbstractFlagsSetterRule<T extends AbstractFlagsSetterRule<T>>
80         extends AbstractRethrowerRule {
81 
82     protected static final String SYSTEM_PROPERTY_FOR_LOGCAT_TAGS_PREFIX = "log.tag.";
83 
84     // TODO(b/331781012): add all of them
85     // TODO(b/331781012): include itself (mLog.getTag()), although it would require a new method
86     // to make sure the tag is set before logging (as the initial command to set the logcat tag is
87     // cached)
88     private static final String[] INFRA_TAGS = {SyncCallback.LOG_TAG};
89     private static final Matcher INFRA_LOGTAG_MATCHER =
90             (prop) ->
91                     Arrays.stream(INFRA_TAGS)
92                             .anyMatch(
93                                     tag ->
94                                             prop.name.equals(
95                                                     SYSTEM_PROPERTY_FOR_LOGCAT_TAGS_PREFIX + tag));
96 
97     private final String mDeviceConfigNamespace;
98     private final DeviceConfigHelper mDeviceConfig;
99     // TODO(b/338067482): move system properties to its own rule?
100     // Prefix used on SystemProperties used for DebugFlags
101     private final String mDebugFlagPrefix;
102     private final SystemPropertiesHelper mSystemProperties;
103 
104     // Cache methods that were called before the test started, so the rule can be
105     // instantiated using a builder-like approach.
106     // NOTE: they MUST be cached and only executed after the test starts, because there's no
107     // guarantee that the rule will be executed at all (for example, a rule executed earlier might
108     // throw an AssumptionViolatedException)
109     private final List<Command> mInitialCommands = new ArrayList<>();
110 
111     // Name of flags that were changed by the test
112     private final Set<NameValuePair> mChangedFlags = new LinkedHashSet<>();
113     // Name of system properties that were changed by the test
114     private final Set<NameValuePair> mChangedSystemProperties = new LinkedHashSet<>();
115     private final Matcher mSystemPropertiesMatcher;
116 
117     private final List<NameValuePair> mPreTestFlags = new ArrayList<>();
118     private final List<NameValuePair> mPreTestSystemProperties = new ArrayList<>();
119 
120     private DeviceConfig.SyncDisabledModeForTest mPreviousSyncDisabledModeForTest;
121 
122     private boolean mIsRunning;
123     private boolean mFlagsClearedByTest;
124 
125     private final NameValuePairSetter mFlagsSetter;
126 
127     private final boolean mSkipStuffWhenObjectsAreNullOnUnitTests;
128 
AbstractFlagsSetterRule( RealLogger logger, String deviceConfigNamespace, String debugFlagPrefix, DeviceConfigHelper.InterfaceFactory deviceConfigInterfaceFactory, SystemPropertiesHelper.Interface systemPropertiesInterface)129     protected AbstractFlagsSetterRule(
130             RealLogger logger,
131             String deviceConfigNamespace,
132             String debugFlagPrefix,
133             DeviceConfigHelper.InterfaceFactory deviceConfigInterfaceFactory,
134             SystemPropertiesHelper.Interface systemPropertiesInterface) {
135         this(
136                 logger,
137                 deviceConfigNamespace,
138                 debugFlagPrefix,
139                 // TODO(b/294423183, 328682831): should not be necessary, but integrated with
140                 // setLogcatTag()
141                 (prop) ->
142                         prop.name.startsWith(debugFlagPrefix)
143                                 || prop.name.startsWith(SYSTEM_PROPERTY_FOR_LOGCAT_TAGS_PREFIX),
144                 deviceConfigInterfaceFactory,
145                 systemPropertiesInterface);
146     }
147 
AbstractFlagsSetterRule( RealLogger logger, String deviceConfigNamespace, String debugFlagPrefix, Matcher systemPropertiesMatcher, DeviceConfigHelper.InterfaceFactory deviceConfigInterfaceFactory, SystemPropertiesHelper.Interface systemPropertiesInterface)148     protected AbstractFlagsSetterRule(
149             RealLogger logger,
150             String deviceConfigNamespace,
151             String debugFlagPrefix,
152             Matcher systemPropertiesMatcher,
153             DeviceConfigHelper.InterfaceFactory deviceConfigInterfaceFactory,
154             SystemPropertiesHelper.Interface systemPropertiesInterface) {
155 
156         super(logger);
157 
158         mDeviceConfigNamespace =
159                 Objects.requireNonNull(
160                         deviceConfigNamespace, "deviceConfigNamespace cannot be null");
161         mDebugFlagPrefix =
162                 Objects.requireNonNull(debugFlagPrefix, "debugFlagPrefix cannot be null");
163         Objects.requireNonNull(systemPropertiesMatcher, "systemPropertiesMatcher cannot be null");
164         mSystemPropertiesMatcher =
165                 (prop) ->
166                         systemPropertiesMatcher.matches(prop) || INFRA_LOGTAG_MATCHER.matches(prop);
167         mDeviceConfig =
168                 new DeviceConfigHelper(deviceConfigInterfaceFactory, deviceConfigNamespace, logger);
169         mSystemProperties = new SystemPropertiesHelper(systemPropertiesInterface, logger);
170         storeSyncDisabledMode();
171         // Must set right away to avoid race conditions (for example, backend setting flags before
172         // apply() is called)
173         setSyncDisabledMode(DeviceConfig.SyncDisabledModeForTest.PERSISTENT);
174 
175         mFlagsSetter = nvp -> defaultFlagsSetterImplementation(nvp);
176         mSkipStuffWhenObjectsAreNullOnUnitTests = false;
177 
178         mLog.v(
179                 "Constructor: mDeviceConfigNamespace=%s,"
180                         + " mDebugFlagPrefix=%s,mDeviceConfig=%s, mSystemProperties=%s",
181                 mDeviceConfigNamespace, mDebugFlagPrefix, mDeviceConfig, mSystemProperties);
182     }
183 
184     // TODO(b/340882758): this constructor is only used by AbstractFlagsSetterRuleTestCase, which
185     // for now is only testing that the flags are set (it's not testing other stuff like checking
186     // they're reset, system properties, etc...), hence it sets some non-null fields as null. This
187     // is temporary, as this class should be refactored to use the new DeviceConfig class and be
188     // split into multiple rules (for example, to set DebugFlags and Logcat tags) - as more features
189     // are tested and/or refactored, these references should be properly set (and eventually the
190     // constructors merged);
AbstractFlagsSetterRule(RealLogger logger, NameValuePairSetter flagsSetter)191     protected AbstractFlagsSetterRule(RealLogger logger, NameValuePairSetter flagsSetter) {
192         super(logger);
193         mFlagsSetter = flagsSetter;
194 
195         mSkipStuffWhenObjectsAreNullOnUnitTests = true;
196         mSystemPropertiesMatcher = null;
197         mDeviceConfigNamespace = null;
198         mDeviceConfig = null;
199         mDebugFlagPrefix = null;
200         mSystemProperties = null;
201     }
202 
203     @Override
preTest(Statement base, Description description, List<Throwable> cleanUpErrors)204     protected void preTest(Statement base, Description description, List<Throwable> cleanUpErrors) {
205         String testName = TestHelper.getTestName(description);
206         mIsRunning = true;
207 
208         if (!mSkipStuffWhenObjectsAreNullOnUnitTests) {
209             // TODO(b/294423183): ideally should be "setupErrors", but it's not used yet (other
210             // than logging), so it doesn't matter
211             runSafely(cleanUpErrors, () -> mPreTestFlags.addAll(mDeviceConfig.getAll()));
212         }
213         // Log flags set on the device prior to test execution. Useful for verifying if flag state
214         // is correct for flag-ramp / AOAO testing.
215         log(mPreTestFlags, "pre-test flags");
216         if (!mSkipStuffWhenObjectsAreNullOnUnitTests) {
217             runSafely(
218                     cleanUpErrors,
219                     () ->
220                             mPreTestSystemProperties.addAll(
221                                     mSystemProperties.getAll(mSystemPropertiesMatcher)));
222         }
223 
224         runInitialCommands(testName);
225         setAnnotatedFlags(description);
226     }
227 
228     @Override
postTest( Statement base, Description description, List<Throwable> cleanUpErrors)229     protected void postTest(
230             Statement base, Description description, List<Throwable> cleanUpErrors) {
231         String testName = TestHelper.getTestName(description);
232         runSafely(cleanUpErrors, () -> resetFlags(testName));
233         runSafely(cleanUpErrors, () -> resetSystemProperties(testName));
234         restoreSyncDisabledMode(cleanUpErrors);
235         mIsRunning = false;
236     }
237 
restoreSyncDisabledMode(List<Throwable> cleanUpErrors)238     private void restoreSyncDisabledMode(List<Throwable> cleanUpErrors) {
239         if (mPreviousSyncDisabledModeForTest != null) {
240             mLog.v(
241                     "mPreviousSyncDisabledModeForTest=%s; restoring flag sync mode",
242                     mPreviousSyncDisabledModeForTest);
243             runSafely(cleanUpErrors, () -> setSyncDisabledMode(mPreviousSyncDisabledModeForTest));
244         } else {
245             mLog.v("mPreviousSyncDisabledModeForTest=null; not restoring flag sync mode");
246         }
247     }
248 
setSyncDisabledMode(DeviceConfig.SyncDisabledModeForTest mode)249     private void setSyncDisabledMode(DeviceConfig.SyncDisabledModeForTest mode) {
250         runOrCache(
251                 "setSyncDisabledMode(" + mode + ")", () -> mDeviceConfig.setSyncDisabledMode(mode));
252     }
253 
storeSyncDisabledMode()254     private void storeSyncDisabledMode() {
255         runOrCache(
256                 "storeSyncDisabledMode()",
257                 () -> mPreviousSyncDisabledModeForTest = mDeviceConfig.getSyncDisabledMode());
258     }
259 
260     @Override
throwTestFailure(Throwable testError, List<Throwable> cleanUpErrors)261     protected void throwTestFailure(Throwable testError, List<Throwable> cleanUpErrors)
262             throws Throwable {
263         StringBuilder extraInfo = new StringBuilder("*** Flags / system properties state ***\n");
264         if (mFlagsClearedByTest) {
265             extraInfo.append("(NOTE: test explicitly cleared all flags.)\n");
266         } else if (mChangedFlags.isEmpty() && mChangedSystemProperties.isEmpty()) {
267             mLog.v(
268                     "throwTestFailure(): rethrowing %s because no flag (or system property)"
269                             + " changed",
270                     testError);
271             throw testError;
272         }
273 
274         // NOTE: currently mChangedFlags is a Set, so if the test changed the same flag multiple
275         // times, only the last value will be logged. It might be useful to change it to be a List
276         // instead (so it reports all changes), but that would require changing the message as well
277         // (as currently it's just flag_name: before=value, after=value)
278 
279         logAllAndDumpDiff("flags", extraInfo, mChangedFlags, mPreTestFlags);
280         logAllAndDumpDiff(
281                 "system properties", extraInfo, mChangedSystemProperties, mPreTestSystemProperties);
282 
283         TestFailure.throwTestFailure(testError, extraInfo.toString());
284     }
285 
logAllAndDumpDiff( String what, StringBuilder dump, Set<NameValuePair> changedFlags, List<NameValuePair> preTest)286     private void logAllAndDumpDiff(
287             String what,
288             StringBuilder dump,
289             Set<NameValuePair> changedFlags,
290             List<NameValuePair> preTest) {
291         // Log all values
292         log(preTest, "%s before the test", what);
293         log(changedFlags, "%s after the test", what);
294 
295         // Dump only what was changed
296         appendChanges(dump, what, changedFlags, preTest);
297     }
298 
appendChanges( StringBuilder dump, String what, Set<NameValuePair> changedFlags, List<NameValuePair> preTest)299     private void appendChanges(
300             StringBuilder dump,
301             String what,
302             Set<NameValuePair> changedFlags,
303             List<NameValuePair> preTest) {
304         if (changedFlags.isEmpty()) {
305             dump.append("Test didn't change any ").append(what).append('\n');
306             return;
307         }
308         dump.append("Test changed ")
309                 .append(changedFlags.size())
310                 .append(' ')
311                 .append(what)
312                 .append(" (see log for all changes):\n");
313 
314         for (var flag : changedFlags) {
315             String name = flag.name;
316             String before = getValue(preTest, name);
317             String after = flag.value;
318             dump.append('\t')
319                     .append(name)
320                     .append(": ")
321                     .append("before=")
322                     .append(before)
323                     .append(", after=")
324                     .append(Objects.equals(before, after) ? "<<unchanged>>" : after)
325                     .append('\n');
326         }
327     }
328 
getValue(List<NameValuePair> list, String name)329     private String getValue(List<NameValuePair> list, String name) {
330         for (NameValuePair candidate : list) {
331             if (candidate.name.equals(name)) {
332                 return candidate.value;
333             }
334         }
335         return null;
336     }
337 
338     /**
339      * Dumps all flags using the {@value #TAG} tag.
340      *
341      * <p>Typically use for temporary debugging purposes like {@code dumpFlags("getFoo(%s)", bar)}.
342      */
343     @FormatMethod
dumpFlags(@ormatString String reasonFmt, @Nullable Object... reasonArgs)344     public final void dumpFlags(@FormatString String reasonFmt, @Nullable Object... reasonArgs) {
345         log(mDeviceConfig.getAll(), "flags (Reason: %s)", String.format(reasonFmt, reasonArgs));
346     }
347 
348     /**
349      * Dumps all system properties using the {@value #TAG} tag.
350      *
351      * <p>Typically use for temporary debugging purposes like {@code
352      * dumpSystemProperties("getFoo(%s)", bar)}.
353      */
354     @FormatMethod
dumpSystemProperties( @ormatString String reasonFmt, @Nullable Object... reasonArgs)355     public final void dumpSystemProperties(
356             @FormatString String reasonFmt, @Nullable Object... reasonArgs) {
357         log(
358                 mSystemProperties.getAll(mSystemPropertiesMatcher),
359                 "system properties (Reason: %s)",
360                 String.format(reasonFmt, reasonArgs));
361     }
362 
363     @FormatMethod
log( Collection<NameValuePair> values, @FormatString String whatFmt, @Nullable Object... whatArgs)364     private void log(
365             Collection<NameValuePair> values,
366             @FormatString String whatFmt,
367             @Nullable Object... whatArgs) {
368         String what = String.format(whatFmt, whatArgs);
369         if (values.isEmpty()) {
370             mLog.d("%s: empty", what);
371             return;
372         }
373         mLog.d("Logging (on VERBOSE) name/value of %d %s", values.size(), what);
374         values.forEach(value -> mLog.v("\t%s", value));
375     }
376 
377     /** Clears all flags from the namespace */
clearFlags()378     public final T clearFlags() {
379         return runOrCache(
380                 "clearFlags()",
381                 () -> {
382                     mLog.i("Clearing all flags. mIsRunning=%b", mIsRunning);
383                     mDeviceConfig.clearFlags();
384                     // TODO(b/294423183): ideally we should save the flags and restore - possibly
385                     // using DeviceConfig properties - but for now let's just clear it.
386                     mFlagsClearedByTest = true;
387                 });
388     }
389 
390     /** Sets the flag with the given value. */
setFlag(String name, boolean value)391     public final T setFlag(String name, boolean value) {
392         return setOrCacheFlag(name, Boolean.toString(value));
393     }
394 
395     /** Sets the flag with the given value. */
setFlag(String name, int value)396     public final T setFlag(String name, int value) {
397         return setOrCacheFlag(name, Integer.toString(value));
398     }
399 
400     /** Sets the flag with the given value. */
setFlag(String name, long value)401     public final T setFlag(String name, long value) {
402         return setOrCacheFlag(name, Long.toString(value));
403     }
404 
405     /** Sets the flag with the given value. */
setFlag(String name, float value)406     public final T setFlag(String name, float value) {
407         return setOrCacheFlag(name, Float.toString(value));
408     }
409 
410     /** Sets the flag with the given value. */
setFlag(String name, double value)411     public final T setFlag(String name, double value) {
412         return setOrCacheFlag(name, Double.toString(value));
413     }
414 
415     /** Sets the flag with the given value. */
setFlag(String name, String value)416     public final T setFlag(String name, String value) {
417         Objects.requireNonNull(value, "value cannot be null");
418         return setOrCacheFlag(name, value);
419     }
420 
421     /**
422      * Sets the flag with the {@link Object#toString() string representation} of the given value.
423      */
setFlag(String name, Object value)424     public final T setFlag(String name, Object value) {
425         Objects.requireNonNull(value, "value cannot be null");
426         return setOrCacheFlag(name, value.toString());
427     }
428 
429     /**
430      * Sets the flag with the given values and the {@link #ARRAY_SPLITTER_COMMA} separator.
431      *
432      * <p>This method could also be used to set a simple (i.e., no array) String flag, as the
433      * separator is not added after the last element.
434      */
setArrayFlag(String name, String... values)435     public final T setArrayFlag(String name, String... values) {
436         return setArrayFlagWithExplicitSeparator(name, ARRAY_SPLITTER_COMMA, values);
437     }
438 
439     /**
440      * Sets a string array flag with the given elements, separated by {@code separator}.
441      *
442      * <p>Use the method when you need to pass a explicitly {@code separator} - otherwise, just use
443      * {@link #setFlag(String, String...)}, it's simpler.
444      */
setArrayFlagWithExplicitSeparator( String name, String separator, String... values)445     public final T setArrayFlagWithExplicitSeparator(
446             String name, String separator, String... values) {
447         Objects.requireNonNull(separator, "separator cannot be null");
448         Objects.requireNonNull(values, "values cannot be null");
449         if (values.length == 0) {
450             throw new IllegalArgumentException("no values (name=" + name + ")");
451         }
452         if (values.length == 1) {
453             return setOrCacheFlag(name, values[0]);
454         }
455 
456         // TODO(b/303901926): use some existing helper / utility to flatten it - or a stream like
457         // list.stream().map(Object::toString).collect(Collectors.joining(delimiter) - once it's
458         // unit tested
459         StringBuilder flattenedValue = new StringBuilder().append(values[0]);
460         for (int i = 1; i < values.length; i++) {
461             String nextValue = values[i];
462             if (i < values.length) {
463                 flattenedValue.append(separator);
464             }
465             flattenedValue.append(nextValue);
466         }
467         return setOrCacheFlag(name, flattenedValue.toString(), separator);
468     }
469 
470     /**
471      * Sets a {@code logcat} tag.
472      *
473      * <p><b>Note: </b> it's clearer to use the {@link SetLogcatTag} annotation instead.
474      */
setLogcatTag(String tag, LogLevel level)475     public final T setLogcatTag(String tag, LogLevel level) {
476         setOrCacheLogtagSystemProperty(tag, level.name());
477         return getThis();
478     }
479 
480     // TODO(b/331781012): create @SetInfraLogcatTags as well
481     /** Sets the {@code logcat} tags for the (shared) infra classes. */
setInfraLogcatTags()482     public final T setInfraLogcatTags() {
483         for (String tag : INFRA_TAGS) {
484             setLogcatTag(tag, LogLevel.VERBOSE);
485         }
486         return getThis();
487     }
488 
489     /** Gets the value of the given flag. */
490     @Nullable
getFlag(String flag)491     public final String getFlag(String flag) {
492         return mDeviceConfig.get(flag);
493     }
494 
495     // TODO(295007931): abstract SDK-related methods in a new SdkLevelHelper and reuse them on
496     // SdkLevelSupportRule
497     /** Gets the device's SDK level. */
getDeviceSdk()498     protected abstract int getDeviceSdk();
499 
isAtLeastR()500     protected boolean isAtLeastR() {
501         return getDeviceSdk() >= 30;
502     }
503 
isAtLeastS()504     protected boolean isAtLeastS() {
505         return getDeviceSdk() >= 31;
506     }
507 
isAtLeastT()508     protected boolean isAtLeastT() {
509         return getDeviceSdk() > 32;
510     }
511 
isRunning()512     protected final boolean isRunning() {
513         return mIsRunning;
514     }
515 
516     // Helper to get a reference to this object, taking care of the generic casting.
517     @SuppressWarnings("unchecked")
getThis()518     protected final T getThis() {
519         return (T) this;
520     }
521 
522     // Set the annotated flags with the specified value for a particular test method.
523     // NOTE: when adding an annotation here, you also need to add it on isFlagAnnotationPresent()
setAnnotatedFlags(Description description)524     private void setAnnotatedFlags(Description description) {
525         List<Annotation> annotations = getAllFlagAnnotations(description);
526 
527         // Apply the annotations in the reverse order. First apply from the super classes, test
528         // class and then test method. If same annotated flag is present in class and test
529         // method, test method takes higher priority.
530         // NOTE: add annotations sorted by "most likely usage" and "groups"
531         for (int i = annotations.size() - 1; i >= 0; i--) {
532             Annotation annotation = annotations.get(i);
533 
534             // Boolean
535             if (annotation instanceof SetFlagEnabled) {
536                 setAnnotatedFlag((SetFlagEnabled) annotation);
537             } else if (annotation instanceof SetFlagsEnabled) {
538                 setAnnotatedFlag((SetFlagsEnabled) annotation);
539             } else if (annotation instanceof SetFlagDisabled) {
540                 setAnnotatedFlag((SetFlagDisabled) annotation);
541             } else if (annotation instanceof SetFlagsDisabled) {
542                 setAnnotatedFlag((SetFlagsDisabled) annotation);
543             } else if (annotation instanceof SetFlagTrue) {
544                 setAnnotatedFlag((SetFlagTrue) annotation);
545             } else if (annotation instanceof SetFlagsTrue) {
546                 setAnnotatedFlag((SetFlagsTrue) annotation);
547             } else if (annotation instanceof SetFlagFalse) {
548                 setAnnotatedFlag((SetFlagFalse) annotation);
549             } else if (annotation instanceof SetFlagsFalse) {
550                 setAnnotatedFlag((SetFlagsFalse) annotation);
551 
552                 // Numbers
553             } else if (annotation instanceof SetIntegerFlag) {
554                 setAnnotatedFlag((SetIntegerFlag) annotation);
555             } else if (annotation instanceof SetIntegerFlags) {
556                 setAnnotatedFlag((SetIntegerFlags) annotation);
557             } else if (annotation instanceof SetLongFlag) {
558                 setAnnotatedFlag((SetLongFlag) annotation);
559             } else if (annotation instanceof SetLongFlags) {
560                 setAnnotatedFlag((SetLongFlags) annotation);
561             } else if (annotation instanceof SetFloatFlag) {
562                 setAnnotatedFlag((SetFloatFlag) annotation);
563             } else if (annotation instanceof SetFloatFlags) {
564                 setAnnotatedFlag((SetFloatFlags) annotation);
565             } else if (annotation instanceof SetDoubleFlag) {
566                 setAnnotatedFlag((SetDoubleFlag) annotation);
567             } else if (annotation instanceof SetDoubleFlags) {
568                 setAnnotatedFlag((SetDoubleFlags) annotation);
569 
570                 // String
571             } else if (annotation instanceof SetStringFlag) {
572                 setAnnotatedFlag((SetStringFlag) annotation);
573             } else if (annotation instanceof SetStringFlags) {
574                 setAnnotatedFlag((SetStringFlags) annotation);
575             } else if (annotation instanceof SetStringArrayFlag) {
576                 setAnnotatedFlag((SetStringArrayFlag) annotation);
577             } else if (annotation instanceof SetStringArrayFlags) {
578                 setAnnotatedFlag((SetStringArrayFlags) annotation);
579 
580                 // Debug flags
581             } else if (annotation instanceof EnableDebugFlag) {
582                 setAnnotatedFlag((EnableDebugFlag) annotation);
583             } else if (annotation instanceof EnableDebugFlags) {
584                 setAnnotatedFlag((EnableDebugFlags) annotation);
585             } else if (annotation instanceof DisableDebugFlag) {
586                 setAnnotatedFlag((DisableDebugFlag) annotation);
587             } else if (annotation instanceof DisableDebugFlags) {
588                 setAnnotatedFlag((DisableDebugFlags) annotation);
589             } else if (annotation instanceof SetLongDebugFlag) {
590                 setAnnotatedFlag((SetLongDebugFlag) annotation);
591             } else if (annotation instanceof SetLongDebugFlags) {
592                 setAnnotatedFlag((SetLongDebugFlags) annotation);
593 
594                 // Logcat flags
595             } else if (annotation instanceof SetLogcatTag) {
596                 setAnnotatedFlag((SetLogcatTag) annotation);
597             } else if (annotation instanceof SetLogcatTags) {
598                 setAnnotatedFlag((SetLogcatTags) annotation);
599             } else {
600                 processAnnotation(description, annotation);
601             }
602         }
603     }
604 
setOrCacheFlag(String name, String value)605     private T setOrCacheFlag(String name, String value) {
606         return setOrCacheFlag(name, value, /* separator= */ null);
607     }
608 
609     // TODO(b/294423183): need to add unit test for setters that call this
setOrCacheFlag(String name, String value, @Nullable String separator)610     protected final T setOrCacheFlag(String name, String value, @Nullable String separator) {
611         Objects.requireNonNull(name, "name cannot be null");
612         NameValuePair flag = new NameValuePair(name, value, separator);
613         if (!mIsRunning) {
614             if (isFlagManagedByRunner(name)) {
615                 return getThis();
616             }
617             cacheCommand(new SetFlagCommand(flag));
618             return getThis();
619         }
620         return setFlag(flag);
621     }
622 
623     // TODO(b/295321663): need to provide a more elegant way to integrate it with the custom runners
isFlagManagedByRunner(String flag)624     protected boolean isFlagManagedByRunner(String flag) {
625         return false;
626     }
627 
628     // TODO(b/384798806): add unit test and/or javadoc
setFlag(NameValuePair flag)629     protected final T setFlag(NameValuePair flag) {
630         // TODO(b/384798806): log as well? Or would it be too verbose?
631         mFlagsSetter.set(flag);
632         mChangedFlags.add(flag);
633         return getThis();
634     }
635 
636     // Only used by the default mFlagsSetter - other methods should call setFlag()
637     @Nullable
defaultFlagsSetterImplementation(NameValuePair flag)638     private NameValuePair defaultFlagsSetterImplementation(NameValuePair flag) {
639         mLog.d("Setting flag: %s", flag);
640         if (flag.separator == null) {
641             mDeviceConfig.set(flag.name, flag.value);
642         } else {
643             mDeviceConfig.setWithSeparator(flag.name, flag.value, flag.separator);
644         }
645         // TODO(b/340882758, 338067482): need to set a proper NameValuePairSetter (for example, by
646         // implementing remove() and returning the previous value instad of null), but for now it's
647         // fine as it's only used by unit tests of the new rules (like FlagsPreparerClassRule and
648         // DebugFlagsSetterForUnitTests)
649         return null;
650     }
651 
resetFlags(String testName)652     private void resetFlags(String testName) {
653         if (mSkipStuffWhenObjectsAreNullOnUnitTests) {
654             mLog.w("resetFlags(%s): skipping (should only happen on rule test itself)", testName);
655             return;
656         }
657         mLog.d("Resetting flags after %s", testName);
658         mDeviceConfig.reset();
659     }
660 
661     /** Sets the value of the given {@link com.android.adservices.service.DebugFlag}. */
setDebugFlag(String name, boolean value)662     public final T setDebugFlag(String name, boolean value) {
663         return setDebugFlag(name, Boolean.toString(value));
664     }
665 
666     /** Sets the value of the given {@link com.android.adservices.service.DebugFlag}. */
setDebugFlag(String name, int value)667     public final T setDebugFlag(String name, int value) {
668         return setDebugFlag(name, Integer.toString(value));
669     }
670 
setOrCacheLogtagSystemProperty(String name, String value)671     private T setOrCacheLogtagSystemProperty(String name, String value) {
672         return setOrCacheSystemProperty(SYSTEM_PROPERTY_FOR_LOGCAT_TAGS_PREFIX + name, value);
673     }
674 
675     /** Sets the value of the given {@link com.android.adservices.service.DebugFlag}. */
setDebugFlag(String name, String value)676     public final T setDebugFlag(String name, String value) {
677         return setOrCacheSystemProperty(mDebugFlagPrefix + name, value);
678     }
679 
setOrCacheSystemProperty(String name, String value)680     private T setOrCacheSystemProperty(String name, String value) {
681         if (mSkipStuffWhenObjectsAreNullOnUnitTests) {
682             mLog.w(
683                     "setOrCacheSystemProperty(%s, %s): skipping (should only happen on rule test"
684                             + " itself)",
685                     name, value);
686             return getThis();
687         }
688         NameValuePair systemProperty = new NameValuePair(name, value);
689         if (!mIsRunning) {
690             cacheCommand(new SetSystemPropertyCommand(systemProperty));
691             return getThis();
692         }
693         return setSystemProperty(systemProperty);
694     }
695 
setSystemProperty(NameValuePair systemProperty)696     private T setSystemProperty(NameValuePair systemProperty) {
697         mLog.d("Setting system property: %s", systemProperty);
698         mSystemProperties.set(systemProperty.name, systemProperty.value);
699         mChangedSystemProperties.add(systemProperty);
700         return getThis();
701     }
702 
resetSystemProperties(String testName)703     private void resetSystemProperties(String testName) {
704         if (mSkipStuffWhenObjectsAreNullOnUnitTests) {
705             mLog.w(
706                     "resetSystemProperties(%s): skipping (should only happen on rule test itself)",
707                     testName);
708             return;
709         }
710         mLog.d("Resetting SystemProperties after %s", testName);
711         mSystemProperties.reset();
712     }
713 
runOrCache(String description, Runnable r)714     protected T runOrCache(String description, Runnable r) {
715         RunnableCommand command = new RunnableCommand(description, r);
716         if (!mIsRunning) {
717             cacheCommand(command);
718             return getThis();
719         }
720         command.execute();
721         return getThis();
722     }
723 
cacheCommand(Command command)724     private void cacheCommand(Command command) {
725         if (mIsRunning) {
726             throw new IllegalStateException(
727                     "Cannot cache " + command + " as test is already running");
728         }
729         mLog.v("Caching %s as test is not running yet", command);
730         mInitialCommands.add(command);
731     }
732 
runCommand(String description, Runnable runnable)733     private void runCommand(String description, Runnable runnable) {
734         mLog.v("Running runnable for %s", description);
735         runnable.run();
736     }
737 
738     // TODO(b/294423183): make private once not used by subclass for legacy methods
runInitialCommands(String testName)739     protected final void runInitialCommands(String testName) {
740         if (mInitialCommands.isEmpty()) {
741             mLog.d("Not executing any command before %s", testName);
742         } else {
743             int size = mInitialCommands.size();
744             mLog.d("Executing %d commands before %s", size, testName);
745             for (int i = 0; i < mInitialCommands.size(); i++) {
746                 Command command = mInitialCommands.get(i);
747                 mLog.v("\t%d: %s", i, command);
748                 command.execute();
749             }
750         }
751     }
752 
753     // TODO(b/294423183): improve logic used here and on setAnnotatedFlags()
754     // NOTE: when adding an annotation here, you also need to add it on setAnnotatedFlags()
isFlagAnnotationPresent(Annotation annotation)755     private boolean isFlagAnnotationPresent(Annotation annotation) {
756         // NOTE: add annotations sorted by "most likely usage" and "groups"
757         boolean processedHere =
758                 // Boolean
759                 (annotation instanceof SetFlagEnabled)
760                         || (annotation instanceof SetFlagsEnabled)
761                         || (annotation instanceof SetFlagDisabled)
762                         || (annotation instanceof SetFlagsDisabled)
763                         || (annotation instanceof SetFlagTrue)
764                         || (annotation instanceof SetFlagsTrue)
765                         || (annotation instanceof SetFlagFalse)
766                         || (annotation instanceof SetFlagsFalse)
767                         // Numbers
768                         || (annotation instanceof SetIntegerFlag)
769                         || (annotation instanceof SetIntegerFlags)
770                         || (annotation instanceof SetLongFlag)
771                         || (annotation instanceof SetLongFlags)
772                         || (annotation instanceof SetFloatFlag)
773                         || (annotation instanceof SetFloatFlags)
774                         || (annotation instanceof SetDoubleFlag)
775                         || (annotation instanceof SetDoubleFlags)
776                         // Strings
777                         || (annotation instanceof SetStringFlag)
778                         || (annotation instanceof SetStringFlags)
779                         || (annotation instanceof SetStringArrayFlag)
780                         || (annotation instanceof SetStringArrayFlags)
781                         // Debug flags
782                         || (annotation instanceof DisableDebugFlag)
783                         || (annotation instanceof DisableDebugFlags)
784                         || (annotation instanceof EnableDebugFlag)
785                         || (annotation instanceof EnableDebugFlags)
786                         || (annotation instanceof SetLongDebugFlag)
787                         || (annotation instanceof SetLongDebugFlags)
788                         // Logcat flags
789                         || (annotation instanceof SetLogcatTag)
790                         || (annotation instanceof SetLogcatTags);
791         return processedHere || isAnnotationSupported(annotation);
792     }
793 
794     /**
795      * By default returns {@code false}, but subclasses can override to support custom annotations.
796      *
797      * <p>Note: when overridden, {@link #processAnnotation(Description, Annotation)} should be
798      * overridden as well.
799      */
isAnnotationSupported(Annotation annotation)800     protected boolean isAnnotationSupported(Annotation annotation) {
801         return false;
802     }
803 
804     /**
805      * Called to process custom annotations present in the test (when {@link
806      * #isAnnotationSupported(Annotation)} returns {@code true} for that annotation type).
807      */
processAnnotation(Description description, Annotation annotation)808     protected void processAnnotation(Description description, Annotation annotation) {
809         throw new IllegalStateException(
810                 "Rule subclass ("
811                         + this.getClass().getName()
812                         + ") supports annotation "
813                         + annotation.annotationType().getName()
814                         + ", but doesn't override processAnnotation(), which was called with "
815                         + annotation);
816     }
817 
818     // TODO(b/377592216, 373477535): use TestHelper.getAnnotations() instead (after it has unit or
819     // integration tests)
getAllFlagAnnotations(Description description)820     private List<Annotation> getAllFlagAnnotations(Description description) {
821         List<Annotation> result = new ArrayList<>();
822         for (Annotation testMethodAnnotation : description.getAnnotations()) {
823             if (isFlagAnnotationPresent(testMethodAnnotation)) {
824                 result.add(testMethodAnnotation);
825             } else {
826                 mLog.v("Ignoring annotation %s", testMethodAnnotation);
827             }
828         }
829 
830         // Get all the flag based annotations from test class and super classes
831         Class<?> clazz = description.getTestClass();
832         do {
833             addFlagAnnotations(result, clazz);
834             for (Class<?> classInterface : clazz.getInterfaces()) {
835                 // TODO(b/340882758): add unit test for this as well. Also, unit test need to make
836                 // sure class prevails - for example, if interface has SetFlag(x, true) and test
837                 // have SetFlag(x, false), the interface annotation should be applied before the
838                 // class one.
839                 addFlagAnnotations(result, classInterface);
840             }
841             clazz = clazz.getSuperclass();
842         } while (clazz != null);
843 
844         return result;
845     }
846 
addFlagAnnotations(List<Annotation> annotations, Class<?> clazz)847     private void addFlagAnnotations(List<Annotation> annotations, Class<?> clazz) {
848         Annotation[] classAnnotations = clazz.getAnnotations();
849         if (classAnnotations == null) {
850             return;
851         }
852         for (Annotation annotation : classAnnotations) {
853             if (isFlagAnnotationPresent(annotation)) {
854                 annotations.add(annotation);
855             }
856         }
857     }
858 
859     // Single SetFlagEnabled annotations present
setAnnotatedFlag(SetFlagEnabled annotation)860     private void setAnnotatedFlag(SetFlagEnabled annotation) {
861         setFlag(annotation.value(), true);
862     }
863 
864     // Multiple SetFlagEnabled annotations present
setAnnotatedFlag(SetFlagsEnabled repeatedAnnotation)865     private void setAnnotatedFlag(SetFlagsEnabled repeatedAnnotation) {
866         for (SetFlagEnabled annotation : repeatedAnnotation.value()) {
867             setAnnotatedFlag(annotation);
868         }
869     }
870 
871     // Single SetFlagDisabled annotations present
setAnnotatedFlag(SetFlagDisabled annotation)872     private void setAnnotatedFlag(SetFlagDisabled annotation) {
873         setFlag(annotation.value(), false);
874     }
875 
876     // Multiple SetFlagDisabled annotations present
setAnnotatedFlag(SetFlagsDisabled repeatedAnnotation)877     private void setAnnotatedFlag(SetFlagsDisabled repeatedAnnotation) {
878         for (SetFlagDisabled annotation : repeatedAnnotation.value()) {
879             setAnnotatedFlag(annotation);
880         }
881     }
882 
883     // Single SetFlagTrue annotations present
setAnnotatedFlag(SetFlagTrue annotation)884     private void setAnnotatedFlag(SetFlagTrue annotation) {
885         setFlag(annotation.value(), true);
886     }
887 
888     // Multiple SetFlagTrue annotations present
setAnnotatedFlag(SetFlagsTrue repeatedAnnotation)889     private void setAnnotatedFlag(SetFlagsTrue repeatedAnnotation) {
890         for (SetFlagTrue annotation : repeatedAnnotation.value()) {
891             setAnnotatedFlag(annotation);
892         }
893     }
894 
895     // Single SetFlagFalse annotations present
setAnnotatedFlag(SetFlagFalse annotation)896     private void setAnnotatedFlag(SetFlagFalse annotation) {
897         setFlag(annotation.value(), false);
898     }
899 
900     // Multiple SetFlagFalse annotations present
setAnnotatedFlag(SetFlagsFalse repeatedAnnotation)901     private void setAnnotatedFlag(SetFlagsFalse repeatedAnnotation) {
902         for (SetFlagFalse annotation : repeatedAnnotation.value()) {
903             setAnnotatedFlag(annotation);
904         }
905     }
906 
907     // Single SetIntegerFlag annotations present
setAnnotatedFlag(SetIntegerFlag annotation)908     private void setAnnotatedFlag(SetIntegerFlag annotation) {
909         setFlag(annotation.name(), annotation.value());
910     }
911 
912     // Multiple SetIntegerFlag annotations present
setAnnotatedFlag(SetIntegerFlags repeatedAnnotation)913     private void setAnnotatedFlag(SetIntegerFlags repeatedAnnotation) {
914         for (SetIntegerFlag annotation : repeatedAnnotation.value()) {
915             setAnnotatedFlag(annotation);
916         }
917     }
918 
919     // Single SetLongFlag annotations present
setAnnotatedFlag(SetLongFlag annotation)920     private void setAnnotatedFlag(SetLongFlag annotation) {
921         setFlag(annotation.name(), annotation.value());
922     }
923 
924     // Multiple SetLongFlag annotations present
setAnnotatedFlag(SetLongFlags repeatedAnnotation)925     private void setAnnotatedFlag(SetLongFlags repeatedAnnotation) {
926         for (SetLongFlag annotation : repeatedAnnotation.value()) {
927             setAnnotatedFlag(annotation);
928         }
929     }
930 
931     // Single SetLongFlag annotations present
setAnnotatedFlag(SetFloatFlag annotation)932     private void setAnnotatedFlag(SetFloatFlag annotation) {
933         setFlag(annotation.name(), annotation.value());
934     }
935 
936     // Multiple SetLongFlag annotations present
setAnnotatedFlag(SetFloatFlags repeatedAnnotation)937     private void setAnnotatedFlag(SetFloatFlags repeatedAnnotation) {
938         for (SetFloatFlag annotation : repeatedAnnotation.value()) {
939             setAnnotatedFlag(annotation);
940         }
941     }
942 
943     // Single SetDoubleFlag annotations present
setAnnotatedFlag(SetDoubleFlag annotation)944     private void setAnnotatedFlag(SetDoubleFlag annotation) {
945         setFlag(annotation.name(), annotation.value());
946     }
947 
948     // Multiple SetDoubleFlag annotations present
setAnnotatedFlag(SetDoubleFlags repeatedAnnotation)949     private void setAnnotatedFlag(SetDoubleFlags repeatedAnnotation) {
950         for (SetDoubleFlag annotation : repeatedAnnotation.value()) {
951             setAnnotatedFlag(annotation);
952         }
953     }
954 
955     // Single SetStringFlag annotations present
setAnnotatedFlag(SetStringFlag annotation)956     private void setAnnotatedFlag(SetStringFlag annotation) {
957         setFlag(annotation.name(), annotation.value());
958     }
959 
960     // Multiple SetStringFlag annotations present
setAnnotatedFlag(SetStringFlags repeatedAnnotation)961     private void setAnnotatedFlag(SetStringFlags repeatedAnnotation) {
962         for (SetStringFlag annotation : repeatedAnnotation.value()) {
963             setAnnotatedFlag(annotation);
964         }
965     }
966 
967     // Single SetStringArrayFlag annotations present
setAnnotatedFlag(SetStringArrayFlag annotation)968     private void setAnnotatedFlag(SetStringArrayFlag annotation) {
969         setArrayFlagWithExplicitSeparator(
970                 annotation.name(), annotation.separator(), annotation.value());
971     }
972 
973     // Multiple SetStringArrayFlag annotations present
setAnnotatedFlag(SetStringArrayFlags repeatedAnnotation)974     private void setAnnotatedFlag(SetStringArrayFlags repeatedAnnotation) {
975         for (SetStringArrayFlag annotation : repeatedAnnotation.value()) {
976             setAnnotatedFlag(annotation);
977         }
978     }
979 
980     // Single EnableDebugFlag annotations present
setAnnotatedFlag(EnableDebugFlag annotation)981     private void setAnnotatedFlag(EnableDebugFlag annotation) {
982         setDebugFlag(annotation.value(), true);
983     }
984 
985     // Multiple EnableDebugFlag annotations present
setAnnotatedFlag(EnableDebugFlags repeatedAnnotation)986     private void setAnnotatedFlag(EnableDebugFlags repeatedAnnotation) {
987         for (EnableDebugFlag annotation : repeatedAnnotation.value()) {
988             setAnnotatedFlag(annotation);
989         }
990     }
991 
992     // Single DisableDebugFlag annotations present
setAnnotatedFlag(DisableDebugFlag annotation)993     private void setAnnotatedFlag(DisableDebugFlag annotation) {
994         setDebugFlag(annotation.value(), false);
995     }
996 
997     // Multiple DisableDebugFlag annotations present
setAnnotatedFlag(DisableDebugFlags repeatedAnnotation)998     private void setAnnotatedFlag(DisableDebugFlags repeatedAnnotation) {
999         for (DisableDebugFlag annotation : repeatedAnnotation.value()) {
1000             setAnnotatedFlag(annotation);
1001         }
1002     }
1003 
1004     // Single SetLongDebugFlag annotations present
setAnnotatedFlag(SetLongDebugFlag annotation)1005     private void setAnnotatedFlag(SetLongDebugFlag annotation) {
1006         setDebugFlag(annotation.name(), Long.toString(annotation.value()));
1007     }
1008 
1009     // Multiple SetLongDebugFlag annotations present
setAnnotatedFlag(SetLongDebugFlags repeatedAnnotation)1010     private void setAnnotatedFlag(SetLongDebugFlags repeatedAnnotation) {
1011         for (SetLongDebugFlag annotation : repeatedAnnotation.value()) {
1012             setAnnotatedFlag(annotation);
1013         }
1014     }
1015 
1016     // Single SetLogcatTag annotations present
setAnnotatedFlag(SetLogcatTag annotation)1017     private void setAnnotatedFlag(SetLogcatTag annotation) {
1018         setLogcatTag(annotation.tag(), annotation.level());
1019     }
1020 
1021     // Multiple SetLogcatTag annotations present
setAnnotatedFlag(SetLogcatTags repeatedAnnotation)1022     private void setAnnotatedFlag(SetLogcatTags repeatedAnnotation) {
1023         for (SetLogcatTag annotation : repeatedAnnotation.value()) {
1024             setAnnotatedFlag(annotation);
1025         }
1026     }
1027 
validateArgs( Map<String, String> configArgs, String prefix, String separator)1028     private void validateArgs(
1029         Map<String, String> configArgs, String prefix, String separator) {
1030         Objects.requireNonNull(configArgs, "configArgs cannot be null");
1031         Objects.requireNonNull(prefix, "prefix cannot be null");
1032         Objects.requireNonNull(separator, "separator cannot be null");
1033         if (prefix.isEmpty() || separator.isEmpty()) {
1034             throw new IllegalArgumentException(
1035                 "prefix or separator cannot be empty");
1036         }
1037         if (separator.equals("=") || separator.equals("_")) {
1038             throw new IllegalArgumentException(
1039                 "separator cannot be one of (=,_)");
1040         }
1041     }
1042 
1043     /**
1044      * Sets flags supplied as arguments to the test config.
1045      *
1046      * @param configArgs Map of arguments provided to the test config.
1047      * @param prefix Prefix of arguments which contain a flag.
1048      * @param separator separator between the prefix and name of the flag.
1049      *
1050      * <p> Separators cannot be one of _ or = because _ used in flag names and =
1051      * is used to assign values to flags.
1052      */
setFlagsFromConfig( Map<String, String> configArgs, String prefix, String separator)1053     public T setFlagsFromConfig(
1054             Map<String, String> configArgs, String prefix, String separator) {
1055         validateArgs(configArgs, prefix, separator);
1056         return runOrCache(
1057                 "setFlagsFromConfig",
1058                 () -> {
1059                     for (String key : configArgs.keySet()) {
1060                         mLog.d("Parsing argument from config: %s", key);
1061                         if (!key.contains(separator)) {
1062                             continue;
1063                         }
1064                         String[] keyParts = key.split(separator);
1065                         if (keyParts.length == 2 &&
1066                                 keyParts[0].equals(prefix)) {
1067                             mLog.d("Setting flag from config: %s=%s",
1068                                     keyParts[1], configArgs.get(key));
1069                                 setFlag(keyParts[1], configArgs.get(key));
1070                         }
1071                     }
1072                 });
1073     }
1074 
1075     @SuppressWarnings("ClassCanBeStatic") // Subclasses reference enclosing class
1076     private abstract class Command {
1077         protected final String mDescription;
1078 
1079         Command(String description) {
1080             mDescription = description;
1081         }
1082 
1083         abstract void execute();
1084 
1085         @Override
1086         public final String toString() {
1087             return mDescription;
1088         }
1089     }
1090 
1091     private final class RunnableCommand extends Command {
1092         private final Runnable mRunnable;
1093 
1094         RunnableCommand(String description, Runnable runnable) {
1095             super(description);
1096             mRunnable = runnable;
1097         }
1098 
1099         @Override
1100         void execute() {
1101             runCommand(mDescription, mRunnable);
1102         }
1103     }
1104 
1105     private abstract class SetFlagOrSystemPropertyCommand extends Command {
1106         protected final NameValuePair mFlagOrSystemProperty;
1107 
1108         SetFlagOrSystemPropertyCommand(String description, NameValuePair flagOrSystemProperty) {
1109             super(description + "(" + flagOrSystemProperty + ")");
1110             mFlagOrSystemProperty = flagOrSystemProperty;
1111         }
1112     }
1113 
1114     private final class SetFlagCommand extends SetFlagOrSystemPropertyCommand {
1115         SetFlagCommand(NameValuePair flag) {
1116             super("SetFlag", flag);
1117         }
1118 
1119         @Override
1120         void execute() {
1121             setFlag(mFlagOrSystemProperty);
1122         }
1123     }
1124 
1125     private final class SetSystemPropertyCommand extends SetFlagOrSystemPropertyCommand {
1126         SetSystemPropertyCommand(NameValuePair flag) {
1127             super("SetSystemProperty", flag);
1128         }
1129 
1130         @Override
1131         void execute() {
1132             setSystemProperty(mFlagOrSystemProperty);
1133         }
1134     }
1135 }
1136