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