1 /*
2  * Copyright (C) 2023 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.systemui.unfold.progress
18 
19 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
20 import com.android.systemui.util.leak.ReferenceTestUtils.waitForCondition
21 import com.google.common.truth.Truth.assertThat
22 import com.google.common.truth.Truth.assertWithMessage
23 
24 /** Listener usable by tests with some handy assertions. */
25 class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionProgressListener {
26 
27     private val recordings: MutableList<UnfoldTransitionRecording> = arrayListOf()
28     private var currentRecording: UnfoldTransitionRecording? = null
29     var lastCallbackThread: Thread? = null
30         private set
31 
onTransitionStartednull32     override fun onTransitionStarted() {
33         lastCallbackThread = Thread.currentThread()
34         assertWithMessage("Trying to start a transition when it is already in progress")
35             .that(currentRecording)
36             .isNull()
37 
38         currentRecording = UnfoldTransitionRecording()
39     }
40 
onTransitionProgressnull41     override fun onTransitionProgress(progress: Float) {
42         lastCallbackThread = Thread.currentThread()
43         assertWithMessage("Received transition progress event when it's not started")
44             .that(currentRecording)
45             .isNotNull()
46         currentRecording!!.addProgress(progress)
47     }
48 
onTransitionFinishingnull49     override fun onTransitionFinishing() {
50         lastCallbackThread = Thread.currentThread()
51         assertWithMessage("Received transition finishing event when it's not started")
52             .that(currentRecording)
53             .isNotNull()
54         currentRecording!!.onFinishing()
55     }
56 
onTransitionFinishednull57     override fun onTransitionFinished() {
58         lastCallbackThread = Thread.currentThread()
59         assertWithMessage("Received transition finish event when it's not started")
60             .that(currentRecording)
61             .isNotNull()
62         recordings += currentRecording!!
63         currentRecording = null
64     }
65 
ensureTransitionFinishednull66     fun ensureTransitionFinished(): UnfoldTransitionRecording {
67         waitForCondition { recordings.size == 1 }
68         return recordings.first()
69     }
70 
71     /**
72      * Number of progress event for the currently running transition
73      * Returns null if there is no currently running transition
74      */
75     val currentTransitionProgressEventCount: Int?
76         get() = currentRecording?.progressHistory?.size
77 
78     /**
79      * Runs [block] and ensures that there was at least once onTransitionProgress event after that
80      */
waitForProgressChangeAfternull81     fun waitForProgressChangeAfter(block: () -> Unit) {
82         val eventCount = currentTransitionProgressEventCount
83         block()
84         waitForCondition {
85             currentTransitionProgressEventCount != eventCount
86         }
87     }
88 
assertStartednull89     fun assertStarted() {
90         assertWithMessage("Transition didn't start").that(currentRecording).isNotNull()
91     }
92 
assertNotStartednull93     fun assertNotStarted() {
94         assertWithMessage("Transition started").that(currentRecording).isNull()
95     }
96 
assertLastProgressnull97     fun assertLastProgress(progress: Float) {
98         currentRecording?.assertLastProgress(progress) ?: error("unfold not in progress.")
99     }
100 
clearnull101     fun clear() {
102         currentRecording = null
103         recordings.clear()
104     }
105 
106     class UnfoldTransitionRecording {
107         val progressHistory: MutableList<Float> = arrayListOf()
108         private var finishingInvocations: Int = 0
109 
addProgressnull110         fun addProgress(progress: Float) {
111             assertThat(progress).isAtMost(1.0f)
112             assertThat(progress).isAtLeast(0.0f)
113 
114             progressHistory += progress
115         }
116 
onFinishingnull117         fun onFinishing() {
118             finishingInvocations++
119         }
120 
assertIncreasingProgressnull121         fun assertIncreasingProgress() {
122             assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
123             assertThat(progressHistory).isInOrder()
124         }
125 
assertDecreasingProgressnull126         fun assertDecreasingProgress() {
127             assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
128             assertThat(progressHistory).isInOrder(Comparator.reverseOrder<Float>())
129         }
130 
assertFinishedWithUnfoldnull131         fun assertFinishedWithUnfold() {
132             assertThat(progressHistory).isNotEmpty()
133             assertThat(progressHistory.last()).isEqualTo(1.0f)
134         }
135 
assertFinishedWithFoldnull136         fun assertFinishedWithFold() {
137             assertThat(progressHistory).isNotEmpty()
138             assertThat(progressHistory.last()).isEqualTo(0.0f)
139         }
140 
assertHasFoldAnimationAtTheEndnull141         fun assertHasFoldAnimationAtTheEnd() {
142             // Check that there are at least a few decreasing events at the end
143             assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
144             assertThat(progressHistory.takeLast(MIN_ANIMATION_EVENTS))
145                 .isInOrder(Comparator.reverseOrder<Float>())
146             assertThat(progressHistory.last()).isEqualTo(0.0f)
147         }
148 
assertHasSingleFinishingEventnull149         fun assertHasSingleFinishingEvent() {
150             assertWithMessage(
151                     "onTransitionFinishing callback should be invoked exactly " + "one time"
152                 )
153                 .that(finishingInvocations)
154                 .isEqualTo(1)
155         }
156 
assertLastProgressnull157         fun assertLastProgress(progress: Float) {
158             waitForCondition { progress == progressHistory.lastOrNull() }
159         }
160     }
161 
162     private companion object {
163         private const val MIN_ANIMATION_EVENTS = 3
164     }
165 }
166