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 package com.android.launcher3.icons; 17 18 import static com.android.launcher3.icons.FastBitmapDrawable.CLICK_FEEDBACK_DURATION; 19 import static com.android.launcher3.icons.FastBitmapDrawable.HOVERED_SCALE; 20 import static com.android.launcher3.icons.FastBitmapDrawable.HOVER_FEEDBACK_DURATION; 21 import static com.android.launcher3.icons.FastBitmapDrawable.PRESSED_SCALE; 22 import static com.android.launcher3.icons.FastBitmapDrawable.SCALE; 23 24 import static org.junit.Assert.assertEquals; 25 import static org.junit.Assert.assertFalse; 26 import static org.junit.Assert.assertNull; 27 import static org.junit.Assert.assertTrue; 28 import static org.mockito.Mockito.clearInvocations; 29 import static org.mockito.Mockito.spy; 30 import static org.mockito.Mockito.verify; 31 import static org.mockito.Mockito.when; 32 33 import android.graphics.Bitmap; 34 import android.graphics.drawable.Drawable; 35 import android.view.animation.AccelerateInterpolator; 36 import android.view.animation.DecelerateInterpolator; 37 import android.view.animation.PathInterpolator; 38 39 import androidx.test.annotation.UiThreadTest; 40 import androidx.test.filters.SmallTest; 41 42 import com.android.launcher3.util.LauncherMultivalentJUnit; 43 44 import org.junit.Before; 45 import org.junit.Test; 46 import org.junit.runner.RunWith; 47 import org.mockito.Mock; 48 import org.mockito.MockitoAnnotations; 49 import org.mockito.Spy; 50 51 /** 52 * Tests for FastBitmapDrawable. 53 */ 54 @SmallTest 55 @RunWith(LauncherMultivalentJUnit.class) 56 @UiThreadTest 57 public class FastBitmapDrawableTest { 58 private static final float EPSILON = 0.00001f; 59 60 @Spy 61 FastBitmapDrawable mFastBitmapDrawable = 62 spy(new FastBitmapDrawable(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888))); 63 @Mock Drawable mBadge; 64 65 @Before setUp()66 public void setUp() { 67 MockitoAnnotations.initMocks(this); 68 FastBitmapDrawable.setFlagHoverEnabled(true); 69 when(mFastBitmapDrawable.isVisible()).thenReturn(true); 70 mFastBitmapDrawable.mIsPressed = false; 71 mFastBitmapDrawable.mIsHovered = false; 72 mFastBitmapDrawable.resetScale(); 73 } 74 75 @Test testOnStateChange_noState()76 public void testOnStateChange_noState() { 77 int[] state = new int[]{}; 78 79 boolean isHandled = mFastBitmapDrawable.onStateChange(state); 80 81 // No scale changes without state change. 82 assertFalse("State change handled.", isHandled); 83 assertNull("Scale animation not null.", mFastBitmapDrawable.mScaleAnimation); 84 } 85 86 @Test testOnStateChange_statePressed()87 public void testOnStateChange_statePressed() { 88 int[] state = new int[]{android.R.attr.state_pressed}; 89 90 boolean isHandled = mFastBitmapDrawable.onStateChange(state); 91 92 // Animate to state pressed. 93 assertTrue("State change not handled.", isHandled); 94 assertEquals("Duration not correct.", mFastBitmapDrawable.mScaleAnimation.getDuration(), 95 CLICK_FEEDBACK_DURATION); 96 mFastBitmapDrawable.mScaleAnimation.end(); 97 assertEquals("End value not correct.", 98 (float) SCALE.get(mFastBitmapDrawable), PRESSED_SCALE, EPSILON); 99 assertTrue("Wrong interpolator used.", 100 mFastBitmapDrawable.mScaleAnimation.getInterpolator() 101 instanceof AccelerateInterpolator); 102 } 103 104 @Test testOnStateChange_stateHovered()105 public void testOnStateChange_stateHovered() { 106 int[] state = new int[]{android.R.attr.state_hovered}; 107 108 boolean isHandled = mFastBitmapDrawable.onStateChange(state); 109 110 // Animate to state hovered. 111 assertTrue("State change not handled.", isHandled); 112 assertEquals("Duration not correct.", mFastBitmapDrawable.mScaleAnimation.getDuration(), 113 HOVER_FEEDBACK_DURATION); 114 mFastBitmapDrawable.mScaleAnimation.end(); 115 assertEquals("End value not correct.", 116 (float) SCALE.get(mFastBitmapDrawable), HOVERED_SCALE, EPSILON); 117 assertTrue("Wrong interpolator used.", 118 mFastBitmapDrawable.mScaleAnimation.getInterpolator() instanceof PathInterpolator); 119 } 120 121 @Test testOnStateChange_stateHoveredFlagDisabled()122 public void testOnStateChange_stateHoveredFlagDisabled() { 123 FastBitmapDrawable.setFlagHoverEnabled(false); 124 int[] state = new int[]{android.R.attr.state_hovered}; 125 126 boolean isHandled = mFastBitmapDrawable.onStateChange(state); 127 128 // No state change with flag disabled. 129 assertFalse("Hover state change handled with flag disabled.", isHandled); 130 assertNull("Animation should not run with hover flag disabled.", 131 mFastBitmapDrawable.mScaleAnimation); 132 } 133 134 @Test testOnStateChange_statePressedAndHovered()135 public void testOnStateChange_statePressedAndHovered() { 136 int[] state = new int[]{android.R.attr.state_pressed, android.R.attr.state_hovered}; 137 138 boolean isHandled = mFastBitmapDrawable.onStateChange(state); 139 140 // Animate to pressed state only. 141 assertTrue("State change not handled.", isHandled); 142 assertEquals("Duration not correct.", mFastBitmapDrawable.mScaleAnimation.getDuration(), 143 CLICK_FEEDBACK_DURATION); 144 mFastBitmapDrawable.mScaleAnimation.end(); 145 assertEquals("End value not correct.", 146 (float) SCALE.get(mFastBitmapDrawable), PRESSED_SCALE, EPSILON); 147 assertTrue("Wrong interpolator used.", 148 mFastBitmapDrawable.mScaleAnimation.getInterpolator() 149 instanceof AccelerateInterpolator); 150 } 151 152 @Test testOnStateChange_stateHoveredAndPressed()153 public void testOnStateChange_stateHoveredAndPressed() { 154 int[] state = new int[]{android.R.attr.state_hovered, android.R.attr.state_pressed}; 155 156 boolean isHandled = mFastBitmapDrawable.onStateChange(state); 157 158 // Animate to pressed state only. 159 assertTrue("State change not handled.", isHandled); 160 assertEquals("Duration not correct.", mFastBitmapDrawable.mScaleAnimation.getDuration(), 161 CLICK_FEEDBACK_DURATION); 162 mFastBitmapDrawable.mScaleAnimation.end(); 163 assertEquals("End value not correct.", 164 (float) SCALE.get(mFastBitmapDrawable), PRESSED_SCALE, EPSILON); 165 assertTrue("Wrong interpolator used.", 166 mFastBitmapDrawable.mScaleAnimation.getInterpolator() 167 instanceof AccelerateInterpolator); 168 } 169 170 @Test testOnStateChange_stateHoveredAndPressedToPressed()171 public void testOnStateChange_stateHoveredAndPressedToPressed() { 172 mFastBitmapDrawable.mIsPressed = true; 173 mFastBitmapDrawable.mIsHovered = true; 174 SCALE.setValue(mFastBitmapDrawable, PRESSED_SCALE); 175 int[] state = new int[]{android.R.attr.state_pressed}; 176 177 boolean isHandled = mFastBitmapDrawable.onStateChange(state); 178 179 // No scale change from pressed state to pressed state. 180 assertTrue("State not changed.", isHandled); 181 assertEquals("End value not correct.", 182 (float) SCALE.get(mFastBitmapDrawable), PRESSED_SCALE, EPSILON); 183 } 184 185 @Test testOnStateChange_stateHoveredAndPressedToHovered()186 public void testOnStateChange_stateHoveredAndPressedToHovered() { 187 mFastBitmapDrawable.mIsPressed = true; 188 mFastBitmapDrawable.mIsHovered = true; 189 SCALE.setValue(mFastBitmapDrawable, PRESSED_SCALE); 190 int[] state = new int[]{android.R.attr.state_hovered}; 191 192 boolean isHandled = mFastBitmapDrawable.onStateChange(state); 193 194 // No scale change from pressed state to hovered state. 195 assertTrue("State not changed.", isHandled); 196 assertEquals("End value not correct.", 197 (float) SCALE.get(mFastBitmapDrawable), HOVERED_SCALE, EPSILON); 198 } 199 200 @Test testOnStateChange_stateHoveredToPressed()201 public void testOnStateChange_stateHoveredToPressed() { 202 mFastBitmapDrawable.mIsHovered = true; 203 SCALE.setValue(mFastBitmapDrawable, HOVERED_SCALE); 204 int[] state = new int[]{android.R.attr.state_pressed}; 205 206 boolean isHandled = mFastBitmapDrawable.onStateChange(state); 207 208 // No scale change from pressed state to hovered state. 209 assertTrue("State not changed.", isHandled); 210 assertEquals("End value not correct.", 211 (float) SCALE.get(mFastBitmapDrawable), PRESSED_SCALE, EPSILON); 212 } 213 214 @Test testOnStateChange_statePressedToHovered()215 public void testOnStateChange_statePressedToHovered() { 216 mFastBitmapDrawable.mIsPressed = true; 217 SCALE.setValue(mFastBitmapDrawable, PRESSED_SCALE); 218 int[] state = new int[]{android.R.attr.state_hovered}; 219 220 boolean isHandled = mFastBitmapDrawable.onStateChange(state); 221 222 // No scale change from pressed state to hovered state. 223 assertTrue("State not changed.", isHandled); 224 assertEquals("End value not correct.", 225 (float) SCALE.get(mFastBitmapDrawable), HOVERED_SCALE, EPSILON); 226 } 227 228 @Test testOnStateChange_stateDefaultFromPressed()229 public void testOnStateChange_stateDefaultFromPressed() { 230 mFastBitmapDrawable.mIsPressed = true; 231 SCALE.setValue(mFastBitmapDrawable, PRESSED_SCALE); 232 int[] state = new int[]{}; 233 234 boolean isHandled = mFastBitmapDrawable.onStateChange(state); 235 236 // Animate to default state from pressed state. 237 assertTrue("State change not handled.", isHandled); 238 assertEquals("Duration not correct.", mFastBitmapDrawable.mScaleAnimation.getDuration(), 239 CLICK_FEEDBACK_DURATION); 240 mFastBitmapDrawable.mScaleAnimation.end(); 241 assertEquals("End value not correct.", (float) SCALE.get(mFastBitmapDrawable), 1f, EPSILON); 242 assertTrue("Wrong interpolator used.", 243 mFastBitmapDrawable.mScaleAnimation.getInterpolator() 244 instanceof DecelerateInterpolator); 245 } 246 247 @Test testOnStateChange_stateDefaultFromHovered()248 public void testOnStateChange_stateDefaultFromHovered() { 249 mFastBitmapDrawable.mIsHovered = true; 250 SCALE.setValue(mFastBitmapDrawable, HOVERED_SCALE); 251 int[] state = new int[]{}; 252 253 boolean isHandled = mFastBitmapDrawable.onStateChange(state); 254 255 // Animate to default state from hovered state. 256 assertTrue("State change not handled.", isHandled); 257 assertEquals("Duration not correct.", mFastBitmapDrawable.mScaleAnimation.getDuration(), 258 HOVER_FEEDBACK_DURATION); 259 mFastBitmapDrawable.mScaleAnimation.end(); 260 assertEquals("End value not correct.", (float) SCALE.get(mFastBitmapDrawable), 1f, EPSILON); 261 assertTrue("Wrong interpolator used.", 262 mFastBitmapDrawable.mScaleAnimation.getInterpolator() instanceof PathInterpolator); 263 } 264 265 @Test testOnStateChange_stateHoveredWhilePartiallyScaled()266 public void testOnStateChange_stateHoveredWhilePartiallyScaled() { 267 SCALE.setValue(mFastBitmapDrawable, 0.5f); 268 int[] state = new int[]{android.R.attr.state_hovered}; 269 270 boolean isHandled = mFastBitmapDrawable.onStateChange(state); 271 272 // Animate to hovered state from midway to pressed state. 273 assertTrue("State change not handled.", isHandled); 274 assertEquals("Duration not correct.", 275 mFastBitmapDrawable.mScaleAnimation.getDuration(), HOVER_FEEDBACK_DURATION); 276 mFastBitmapDrawable.mScaleAnimation.end(); 277 assertEquals("End value not correct.", 278 (float) SCALE.get(mFastBitmapDrawable), HOVERED_SCALE, EPSILON); 279 assertTrue("Wrong interpolator used.", 280 mFastBitmapDrawable.mScaleAnimation.getInterpolator() instanceof PathInterpolator); 281 } 282 283 @Test testOnStateChange_statePressedWhilePartiallyScaled()284 public void testOnStateChange_statePressedWhilePartiallyScaled() { 285 SCALE.setValue(mFastBitmapDrawable, 0.5f); 286 int[] state = new int[]{android.R.attr.state_pressed}; 287 288 boolean isHandled = mFastBitmapDrawable.onStateChange(state); 289 290 // Animate to pressed state from midway to hovered state. 291 assertTrue("State change not handled.", isHandled); 292 assertEquals("Duration not correct.", 293 mFastBitmapDrawable.mScaleAnimation.getDuration(), CLICK_FEEDBACK_DURATION); 294 mFastBitmapDrawable.mScaleAnimation.end(); 295 assertEquals("End value not correct.", 296 (float) SCALE.get(mFastBitmapDrawable), PRESSED_SCALE, EPSILON); 297 assertTrue("Wrong interpolator used.", 298 mFastBitmapDrawable.mScaleAnimation.getInterpolator() 299 instanceof AccelerateInterpolator); 300 } 301 302 @Test testOnStateChange_stateDefaultFromPressedNotVisible()303 public void testOnStateChange_stateDefaultFromPressedNotVisible() { 304 when(mFastBitmapDrawable.isVisible()).thenReturn(false); 305 mFastBitmapDrawable.mIsPressed = true; 306 SCALE.setValue(mFastBitmapDrawable, PRESSED_SCALE); 307 clearInvocations(mFastBitmapDrawable); 308 int[] state = new int[]{}; 309 310 boolean isHandled = mFastBitmapDrawable.onStateChange(state); 311 312 // No animations when state was pressed but drawable no longer visible. Set values directly. 313 assertTrue("State change not handled.", isHandled); 314 assertNull("Scale animation not null.", mFastBitmapDrawable.mScaleAnimation); 315 assertEquals("End value not correct.", (float) SCALE.get(mFastBitmapDrawable), 1f, EPSILON); 316 verify(mFastBitmapDrawable).invalidateSelf(); 317 } 318 319 @Test testOnStateChange_stateDefaultFromHoveredNotVisible()320 public void testOnStateChange_stateDefaultFromHoveredNotVisible() { 321 when(mFastBitmapDrawable.isVisible()).thenReturn(false); 322 mFastBitmapDrawable.mIsHovered = true; 323 SCALE.setValue(mFastBitmapDrawable, HOVERED_SCALE); 324 clearInvocations(mFastBitmapDrawable); 325 int[] state = new int[]{}; 326 327 boolean isHandled = mFastBitmapDrawable.onStateChange(state); 328 329 // No animations when state was hovered but drawable no longer visible. Set values directly. 330 assertTrue("State change not handled.", isHandled); 331 assertNull("Scale animation not null.", mFastBitmapDrawable.mScaleAnimation); 332 assertEquals("End value not correct.", (float) SCALE.get(mFastBitmapDrawable), 1f, EPSILON); 333 verify(mFastBitmapDrawable).invalidateSelf(); 334 } 335 336 @Test testUpdateBadgeAlpha()337 public void testUpdateBadgeAlpha() { 338 mFastBitmapDrawable.setBadge(mBadge); 339 340 mFastBitmapDrawable.setAlpha(1); 341 mFastBitmapDrawable.setAlpha(0); 342 343 verify(mBadge).setAlpha(1); 344 verify(mBadge).setAlpha(0); 345 } 346 } 347