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