1 /*
2  * Copyright (C) 2024 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.launcher3.celllayout
18 
19 import android.content.Context
20 import android.util.ArrayMap
21 import android.view.View
22 import androidx.test.core.app.ApplicationProvider
23 import androidx.test.ext.junit.runners.AndroidJUnit4
24 import androidx.test.filters.SmallTest
25 import com.android.launcher3.CellLayout
26 import com.android.launcher3.Reorderable
27 import com.android.launcher3.celllayout.ReorderPreviewAnimation.Companion.HINT_DURATION
28 import com.android.launcher3.celllayout.ReorderPreviewAnimation.Companion.PREVIEW_DURATION
29 import com.android.launcher3.util.ActivityContextWrapper
30 import com.android.launcher3.util.MultiTranslateDelegate
31 import com.android.launcher3.util.MultiTranslateDelegate.INDEX_REORDER_BOUNCE_OFFSET
32 import org.junit.Rule
33 import org.junit.Test
34 import org.junit.runner.RunWith
35 
36 class Mock(context: Context) : Reorderable, View(context) {
37 
38     init {
39         mLeft = 0
40         mRight = 100
41     }
42 
43     private val translateDelegate = MultiTranslateDelegate(this)
44 
45     private var scaleForReorderBounce = 1f
getTranslateDelegatenull46     override fun getTranslateDelegate(): MultiTranslateDelegate {
47         return translateDelegate
48     }
49 
setReorderBounceScalenull50     override fun setReorderBounceScale(scale: Float) {
51         scaleForReorderBounce = scale
52     }
53 
getReorderBounceScalenull54     override fun getReorderBounceScale(): Float {
55         return scaleForReorderBounce
56     }
57 
toAnimationValuesnull58     fun toAnimationValues(): AnimationValues {
59         return AnimationValues(
60             (translateDelegate.getTranslationX(INDEX_REORDER_BOUNCE_OFFSET).value * 100).toInt(),
61             (translateDelegate.getTranslationY(INDEX_REORDER_BOUNCE_OFFSET).value * 100).toInt(),
62             (scaleForReorderBounce * 100).toInt()
63         )
64     }
65 }
66 
67 data class AnimationValues(val dx: Int, val dy: Int, val scale: Int)
68 
69 @SmallTest
70 @RunWith(AndroidJUnit4::class)
71 class ReorderPreviewAnimationTest {
72 
73     @JvmField @Rule var cellLayoutBuilder = UnitTestCellLayoutBuilderRule()
74 
75     private val applicationContext =
76         ActivityContextWrapper(ApplicationProvider.getApplicationContext())
77 
78     /**
79      * @param animationTime the time of the animation we will check the state against.
80      * @param mode the mode either PREVIEW_DURATION or HINT_DURATION.
81      * @param valueToMatch the state of the animation we expect to see at animationTime.
82      * @param isAfterReverse if the animation is finish and we are returning to the beginning.
83      */
testAnimationAtGivenProgressnull84     private fun testAnimationAtGivenProgress(
85         animationTime: Int,
86         mode: Int,
87         valueToMatch: AnimationValues
88     ) {
89         val view = Mock(applicationContext)
90         val cellLayout = cellLayoutBuilder.createCellLayout(100, 100, false)
91         val map = ArrayMap<Reorderable, ReorderPreviewAnimation<Mock>>()
92         val animation =
93             ReorderPreviewAnimation(
94                 view,
95                 mode,
96                 3,
97                 3,
98                 1,
99                 7,
100                 1,
101                 1,
102                 CellLayout.REORDER_PREVIEW_MAGNITUDE,
103                 cellLayout,
104                 map
105             )
106         // Remove delay because it's randomly generated and it can slightly change the results.
107         animation.animator.startDelay = 0
108         animation.animator.currentPlayTime = animationTime.toLong()
109         val currentValue = view.toAnimationValues()
110         assert(currentValue == valueToMatch) {
111             "The value of the animation $currentValue at $animationTime time (milliseconds) doesn't match the given value $valueToMatch"
112         }
113     }
114 
115     @Test
testAnimationModePreviewnull116     fun testAnimationModePreview() {
117         testAnimationAtGivenProgress(
118             PREVIEW_DURATION * 0,
119             ReorderPreviewAnimation.MODE_PREVIEW,
120             AnimationValues(dx = 0, dy = 0, scale = 100)
121         )
122         testAnimationAtGivenProgress(
123             PREVIEW_DURATION / 2,
124             ReorderPreviewAnimation.MODE_PREVIEW,
125             AnimationValues(dx = 2, dy = -5, scale = 98)
126         )
127         testAnimationAtGivenProgress(
128             PREVIEW_DURATION / 3,
129             ReorderPreviewAnimation.MODE_PREVIEW,
130             AnimationValues(dx = 1, dy = -2, scale = 99)
131         )
132         testAnimationAtGivenProgress(
133             PREVIEW_DURATION,
134             ReorderPreviewAnimation.MODE_PREVIEW,
135             AnimationValues(dx = 5, dy = -10, scale = 96)
136         )
137 
138         // MODE_PREVIEW oscillates and goes back to 0,0
139         testAnimationAtGivenProgress(
140             PREVIEW_DURATION * 2,
141             ReorderPreviewAnimation.MODE_PREVIEW,
142             AnimationValues(dx = 0, dy = 0, scale = 100)
143         )
144         // (b/339313407) Temporarily disable this test as the behavior is
145         // inconsistent between Soong & Gradle builds.
146         //
147         // testAnimationAtGivenProgress(
148         //     PREVIEW_DURATION * 99,
149         //     ReorderPreviewAnimation.MODE_PREVIEW,
150         //     AnimationValues(dx = 5, dy = -10, scale = 96)
151         // )
152         testAnimationAtGivenProgress(
153             PREVIEW_DURATION * 98,
154             ReorderPreviewAnimation.MODE_PREVIEW,
155             AnimationValues(dx = 0, dy = 0, scale = 100)
156         )
157         testAnimationAtGivenProgress(
158             (PREVIEW_DURATION * 1.5).toInt(),
159             ReorderPreviewAnimation.MODE_PREVIEW,
160             AnimationValues(dx = 2, dy = -5, scale = 98)
161         )
162     }
163 
164     @Test
testAnimationModeHintnull165     fun testAnimationModeHint() {
166         testAnimationAtGivenProgress(
167             HINT_DURATION * 0,
168             ReorderPreviewAnimation.MODE_HINT,
169             AnimationValues(dx = 0, dy = 0, scale = 100)
170         )
171         testAnimationAtGivenProgress(
172             HINT_DURATION,
173             ReorderPreviewAnimation.MODE_HINT,
174             AnimationValues(dx = -5, dy = 10, scale = 96)
175         )
176         testAnimationAtGivenProgress(
177             HINT_DURATION / 2,
178             ReorderPreviewAnimation.MODE_HINT,
179             AnimationValues(dx = -2, dy = 5, scale = 98)
180         )
181         testAnimationAtGivenProgress(
182             HINT_DURATION / 3,
183             ReorderPreviewAnimation.MODE_HINT,
184             AnimationValues(dx = -1, dy = 2, scale = 99)
185         )
186         testAnimationAtGivenProgress(
187             HINT_DURATION,
188             ReorderPreviewAnimation.MODE_HINT,
189             AnimationValues(dx = -5, dy = 10, scale = 96)
190         )
191 
192         // After one cycle the animationValues should always be the top values and don't cycle.
193         testAnimationAtGivenProgress(
194             HINT_DURATION * 2,
195             ReorderPreviewAnimation.MODE_HINT,
196             AnimationValues(dx = -5, dy = 10, scale = 96)
197         )
198         testAnimationAtGivenProgress(
199             HINT_DURATION * 99,
200             ReorderPreviewAnimation.MODE_HINT,
201             AnimationValues(dx = -5, dy = 10, scale = 96)
202         )
203     }
204 }
205