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