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