1 /*
2  * Copyright 2025 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 androidx.test.uiautomator.internal
18 
19 import android.graphics.Bitmap
20 import android.util.Log
21 import androidx.test.uiautomator.StableResult
22 import androidx.test.uiautomator.UiAutomatorTestScope.Companion.TAG
23 import kotlin.time.DurationUnit
24 import kotlin.time.toDuration
25 
waitForStableInternalnull26 internal fun waitForStableInternal(
27     stableTimeoutMs: Long,
28     stableIntervalMs: Long,
29     stablePollIntervalMs: Long,
30     bitmapProvider: () -> (Bitmap?),
31     rootViewNodeProvider: () -> (ViewNode)
32 ): StableResult {
33 
34     // Setting up states pre-iterations
35     var stableBitmap = bitmapProvider()
36     var stableRootNode = rootViewNodeProvider()
37 
38     // This keeps updating the stable node either until the timeout
39     // (in which case timeout = true) or until the stability has been achieved.
40     var remainingMs = stableTimeoutMs
41     var timeoutClock = TimeoutClock(timeoutMs = remainingMs, sleepIntervalMs = stablePollIntervalMs)
42     fun newTimeoutClockForRemainingTimeout(): TimeoutClock {
43         remainingMs -= timeoutClock.elapsedMs
44         timeoutClock = TimeoutClock(timeoutMs = remainingMs, sleepIntervalMs = stablePollIntervalMs)
45         return timeoutClock
46     }
47 
48     while (true) {
49 
50         if (timeoutClock.elapsedMs >= stableIntervalMs) {
51             val durationStr = timeoutClock.elapsedMs.toDuration(DurationUnit.MILLISECONDS)
52             Log.d(TAG, "Ui was stable for $durationStr.")
53             break
54         }
55 
56         // Acquire another state and see if it's changed.
57         val currentScreenshot = bitmapProvider()
58         val currentRootNode = rootViewNodeProvider()
59 
60         val viewHierarchyEquals = stableRootNode == currentRootNode
61         val bitmapEquals = stableBitmap?.sameAs(currentScreenshot) != false
62 
63         if (!viewHierarchyEquals || !bitmapEquals) {
64             // If it changed, restart with another root node but reduce the remaining timeout.
65             timeoutClock = newTimeoutClockForRemainingTimeout()
66             stableRootNode = currentRootNode
67             stableBitmap = currentScreenshot
68         }
69 
70         // Wait a bit
71         if (timeoutClock.isTimeoutOrSleep()) {
72             Log.d(TAG, "Timeout while waiting for stable ui.")
73             break
74         }
75     }
76 
77     return StableResult(
78         node = stableRootNode.accessibilityNodeInfo,
79         screenshot = stableBitmap,
80         isTimeout = timeoutClock.timeout
81     )
82 }
83