1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.server.wm; 18 19 import static android.server.wm.StateLogger.logAlways; 20 import static android.server.wm.StateLogger.logE; 21 22 import android.os.SystemClock; 23 24 import java.util.concurrent.TimeUnit; 25 import java.util.function.BooleanSupplier; 26 import java.util.function.Consumer; 27 import java.util.function.Predicate; 28 import java.util.function.Supplier; 29 30 /** 31 * The utility class to wait a condition with customized options. 32 * The default retry policy is 5 times with interval 1 second. 33 * 34 * @param <T> The type of the object to validate. 35 * 36 * <p>Sample:</p> 37 * <pre> 38 * // Simple case. 39 * if (Condition.waitFor("true value", () -> true)) { 40 * println("Success"); 41 * } 42 * // Wait for customized result with customized validation. 43 * String result = Condition.waitForResult(new Condition<String>("string comparison") 44 * .setResultSupplier(() -> "Result string") 45 * .setResultValidator(str -> str.equals("Expected string")) 46 * .setRetryIntervalMs(500) 47 * .setRetryLimit(3) 48 * .setOnFailure(str -> println("Failed on " + str))); 49 * </pre> 50 */ 51 public class Condition<T> { 52 private final String mMessage; 53 54 // TODO(b/112837428): Implement a incremental retry policy to reduce the unnecessary constant 55 // time, currently keep the default as 5*1s because most of the original code uses it, and some 56 // tests might be sensitive to the waiting interval. 57 private long mRetryIntervalMs = TimeUnit.SECONDS.toMillis(1); 58 private int mRetryLimit = 5; 59 private boolean mReturnLastResult; 60 61 /** It decides whether this condition is satisfied. */ 62 private BooleanSupplier mSatisfier; 63 /** 64 * It is used when the condition is not a simple boolean expression, such as the caller may want 65 * to get the validated product as the return value. 66 */ 67 private Supplier<T> mResultSupplier; 68 /** It validates the result from {@link #mResultSupplier}. */ 69 private Predicate<T> mResultValidator; 70 private Consumer<T> mOnFailure; 71 private Runnable mOnRetry; 72 private T mLastResult; 73 private T mValidatedResult; 74 75 /** 76 * When using this constructor, it is expected that the condition will be configured with 77 * {@link #setResultSupplier} and {@link #setResultValidator}. 78 */ Condition(String message)79 public Condition(String message) { 80 this(message, null /* satisfier */); 81 } 82 83 /** 84 * Constructs with a simple boolean condition. 85 * 86 * @param message The message to show what is waiting for. 87 * @param satisfier If it returns true, that means the condition is satisfied. 88 */ Condition(String message, BooleanSupplier satisfier)89 public Condition(String message, BooleanSupplier satisfier) { 90 mMessage = message; 91 mSatisfier = satisfier; 92 } 93 94 /** Set the supplier which provides the result object to validate. */ setResultSupplier(Supplier<T> supplier)95 public Condition<T> setResultSupplier(Supplier<T> supplier) { 96 mResultSupplier = supplier; 97 return this; 98 } 99 100 /** Set the validator which tests the object provided by the supplier. */ setResultValidator(Predicate<T> validator)101 public Condition<T> setResultValidator(Predicate<T> validator) { 102 mResultValidator = validator; 103 return this; 104 } 105 106 /** 107 * If true, when using {@link #waitForResult(Condition)}, the method will return the last result 108 * provided by {@link #mResultSupplier} even it is not valid (by {@link #mResultValidator}). 109 */ setReturnLastResult(boolean returnLastResult)110 public Condition<T> setReturnLastResult(boolean returnLastResult) { 111 mReturnLastResult = returnLastResult; 112 return this; 113 } 114 115 /** 116 * Executes the action when the condition does not satisfy within the time limit. The passed 117 * object to the consumer will be the last result from the supplier. 118 */ setOnFailure(Consumer<T> onFailure)119 public Condition<T> setOnFailure(Consumer<T> onFailure) { 120 mOnFailure = onFailure; 121 return this; 122 } 123 setOnRetry(Runnable onRetry)124 public Condition<T> setOnRetry(Runnable onRetry) { 125 mOnRetry = onRetry; 126 return this; 127 } 128 setRetryIntervalMs(long millis)129 public Condition<T> setRetryIntervalMs(long millis) { 130 mRetryIntervalMs = millis; 131 return this; 132 } 133 setRetryLimit(int limit)134 public Condition<T> setRetryLimit(int limit) { 135 mRetryLimit = limit; 136 return this; 137 } 138 139 /** Build the condition by {@link #mResultSupplier} and {@link #mResultValidator}. */ prepareSatisfier()140 private void prepareSatisfier() { 141 if (mResultSupplier == null || mResultValidator == null) { 142 throw new IllegalArgumentException("No specified condition"); 143 } 144 145 mSatisfier = () -> { 146 final T result = mResultSupplier.get(); 147 mLastResult = result; 148 if (mResultValidator.test(result)) { 149 mValidatedResult = result; 150 return true; 151 } 152 return false; 153 }; 154 } 155 156 /** 157 * @see #waitFor(Condition) 158 * @see #Condition(String, BooleanSupplier) 159 */ waitFor(String message, BooleanSupplier satisfier)160 public static boolean waitFor(String message, BooleanSupplier satisfier) { 161 return waitFor(new Condition<>(message, satisfier)); 162 } 163 164 /** @return {@code false} if the condition does not satisfy within the time limit. */ waitFor(Condition<T> condition)165 public static <T> boolean waitFor(Condition<T> condition) { 166 if (condition.mSatisfier == null) { 167 condition.prepareSatisfier(); 168 } 169 170 final long startTime = SystemClock.elapsedRealtime(); 171 for (int i = 1; i <= condition.mRetryLimit; i++) { 172 if (condition.mSatisfier.getAsBoolean()) { 173 return true; 174 } else { 175 SystemClock.sleep(condition.mRetryIntervalMs); 176 logAlways("***Waiting for " + condition.mMessage + " ... retry=" + i 177 + " elapsed=" + (SystemClock.elapsedRealtime() - startTime) + "ms"); 178 if (condition.mOnRetry != null && i < condition.mRetryLimit) { 179 condition.mOnRetry.run(); 180 } 181 } 182 } 183 if (condition.mSatisfier.getAsBoolean()) { 184 return true; 185 } 186 187 if (condition.mOnFailure == null) { 188 logE("Condition is not satisfied: " + condition.mMessage); 189 } else { 190 condition.mOnFailure.accept(condition.mLastResult); 191 } 192 return false; 193 } 194 195 /** @see #waitForResult(Condition) */ waitForResult(String message, Consumer<Condition<T>> setup)196 public static <T> T waitForResult(String message, Consumer<Condition<T>> setup) { 197 final Condition<T> condition = new Condition<>(message); 198 setup.accept(condition); 199 return waitForResult(condition); 200 } 201 202 /** 203 * @return {@code null} if the condition does not satisfy within the time limit or the result 204 * supplier returns {@code null}. 205 */ waitForResult(Condition<T> condition)206 public static <T> T waitForResult(Condition<T> condition) { 207 condition.mLastResult = condition.mValidatedResult = null; 208 condition.prepareSatisfier(); 209 waitFor(condition); 210 if (condition.mValidatedResult != null) { 211 return condition.mValidatedResult; 212 } 213 return condition.mReturnLastResult ? condition.mLastResult : null; 214 } 215 } 216