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