1 /* 2 * Copyright (C) 2021 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 com.android.server.wm.traces.common 18 19 /** 20 * The utility class to wait a condition with customized options. 21 * The default retry policy is 5 times with interval 1 second. 22 * 23 * @param <T> The type of the object to validate. 24 * 25 * <p>Sample:</p> 26 * <pre> 27 * // Simple case. 28 * if (Condition.waitFor("true value", () -> true)) { 29 * println("Success"); 30 * } 31 * // Wait for customized result with customized validation. 32 * String result = WaitForCondition.Builder(supplier = () -> "Result string") 33 * .withCondition(str -> str.equals("Expected string")) 34 * .withRetryIntervalMs(500) 35 * .withRetryLimit(3) 36 * .onFailure(str -> println("Failed on " + str))) 37 * .build() 38 * .waitFor() 39 * </pre> 40 41 * @param condition If it returns true, that means the condition is satisfied. 42 */ 43 class WaitCondition<T> private constructor( 44 private val supplier: () -> T, 45 private val condition: Condition<T>, 46 private val retryLimit: Int, 47 private val onLog: ((String, Boolean) -> Unit)?, 48 private val onFailure: ((T) -> Any)?, 49 private val onRetry: ((T) -> Any)?, 50 private val onSuccess: ((T) -> Any)? 51 ) { 52 /** 53 * @return `false` if the condition does not satisfy within the time limit. 54 */ waitFornull55 fun waitFor(): Boolean { 56 onLog?.invoke("***Waiting for $condition", /* isError */ false) 57 var currState: T? = null 58 for (i in 0..retryLimit) { 59 currState = supplier.invoke() 60 if (condition.isSatisfied(currState)) { 61 onLog?.invoke("***Waiting for $condition ... Success!", /* isError */ false) 62 onSuccess?.invoke(currState) 63 return true 64 } else { 65 val detailedMessage = condition.getMessage(currState) 66 onLog?.invoke("***Waiting for $detailedMessage... retry=${i + 1}", 67 /* isError */ true) 68 if (i < retryLimit) { 69 onRetry?.invoke(currState) 70 } 71 } 72 } 73 74 val detailedMessage = if (currState != null) { 75 condition.getMessage(currState) 76 } else { 77 condition.toString() 78 } 79 onLog?.invoke("***Waiting for $detailedMessage ... Failed!", /* isError */ true) 80 if (onFailure != null) { 81 require(currState != null) { "Missing last result for failure notification" } 82 onFailure.invoke(currState) 83 } 84 return false 85 } 86 87 class Builder<T>( 88 private val supplier: () -> T, 89 private var retryLimit: Int 90 ) { 91 private val conditions = mutableListOf<Condition<T>>() 92 private var onFailure: ((T) -> Any)? = null 93 private var onRetry: ((T) -> Any)? = null 94 private var onSuccess: ((T) -> Any)? = null 95 private var onLog: ((String, Boolean) -> Unit)? = null 96 withConditionnull97 fun withCondition(condition: Condition<T>) = 98 apply { conditions.add(condition) } 99 withConditionnull100 fun withCondition(message: String, condition: (T) -> Boolean) = 101 apply { withCondition(Condition(message, condition)) } 102 spreadConditionListnull103 private fun spreadConditionList(): List<Condition<T>> = 104 conditions.flatMap { 105 if (it is ConditionList<T>) { 106 it.conditions 107 } else { 108 listOf(it) 109 } 110 } 111 112 /** 113 * Executes the action when the condition does not satisfy within the time limit. The passed 114 * object to the consumer will be the last result from the supplier. 115 */ onFailurenull116 fun onFailure(onFailure: (T) -> Any): Builder<T> = 117 apply { this.onFailure = onFailure } 118 onLognull119 fun onLog(onLog: (String, Boolean) -> Unit): Builder<T> = 120 apply { this.onLog = onLog } 121 onRetrynull122 fun onRetry(onRetry: ((T) -> Any)? = null): Builder<T> = 123 apply { this.onRetry = onRetry } 124 onSuccessnull125 fun onSuccess(onRetry: ((T) -> Any)? = null): Builder<T> = 126 apply { this.onSuccess = onRetry } 127 buildnull128 fun build(): WaitCondition<T> = 129 WaitCondition(supplier, ConditionList(spreadConditionList()), retryLimit, 130 onLog, onFailure, onRetry, onSuccess) 131 } 132 133 companion object { 134 // TODO(b/112837428): Implement a incremental retry policy to reduce the unnecessary 135 // constant time, currently keep the default as 5*1s because most of the original code 136 // uses it, and some tests might be sensitive to the waiting interval. 137 const val DEFAULT_RETRY_LIMIT = 10 138 const val DEFAULT_RETRY_INTERVAL_MS = 500L 139 } 140 }