1 /* 2 * Copyright (C) 2016 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 android.uirendering.cts.testclasses; 17 18 import static android.uirendering.cts.util.MockVsyncHelper.nextFrame; 19 20 import static org.junit.Assert.assertEquals; 21 import static org.junit.Assert.assertFalse; 22 import static org.junit.Assert.assertTrue; 23 24 import android.animation.ValueAnimator; 25 import android.content.Context; 26 import android.graphics.Bitmap; 27 import android.graphics.BlendMode; 28 import android.graphics.Canvas; 29 import android.graphics.Color; 30 import android.graphics.ColorFilter; 31 import android.graphics.Paint; 32 import android.graphics.PixelFormat; 33 import android.graphics.RecordingCanvas; 34 import android.graphics.Rect; 35 import android.graphics.RenderNode; 36 import android.graphics.drawable.Drawable; 37 import android.uirendering.cts.bitmapverifiers.ColorVerifier; 38 import android.uirendering.cts.bitmapverifiers.PerPixelBitmapVerifier; 39 import android.uirendering.cts.bitmapverifiers.RegionVerifier; 40 import android.uirendering.cts.testinfrastructure.ActivityTestBase; 41 import android.uirendering.cts.testinfrastructure.Tracer; 42 import android.uirendering.cts.util.MockVsyncHelper; 43 import android.view.ContextThemeWrapper; 44 import android.view.animation.AnimationUtils; 45 import android.widget.EdgeEffect; 46 47 import androidx.test.InstrumentationRegistry; 48 import androidx.test.filters.LargeTest; 49 import androidx.test.filters.SmallTest; 50 import androidx.test.runner.AndroidJUnit4; 51 52 import org.junit.After; 53 import org.junit.Before; 54 import org.junit.Rule; 55 import org.junit.Test; 56 import org.junit.runner.RunWith; 57 58 @SmallTest 59 @RunWith(AndroidJUnit4.class) 60 public class EdgeEffectTests extends ActivityTestBase { 61 private static final int WIDTH = 90; 62 private static final int HEIGHT = 90; 63 64 @Rule 65 public Tracer name = new Tracer(); 66 67 private Context mThemeContext; 68 private float mPreviousDurationScale; 69 70 interface EdgeEffectInitializer { initialize(EdgeEffect edgeEffect)71 void initialize(EdgeEffect edgeEffect); 72 } 73 getContext()74 private Context getContext() { 75 return mThemeContext; 76 } 77 78 @Before setUp()79 public void setUp() { 80 final Context targetContext = InstrumentationRegistry.getTargetContext(); 81 mThemeContext = new ContextThemeWrapper(targetContext, 82 android.R.style.Theme_Material_Light); 83 mPreviousDurationScale = ValueAnimator.getDurationScale(); 84 ValueAnimator.setDurationScale(1.0f); 85 } 86 87 @After tearDown()88 public void tearDown() { 89 ValueAnimator.setDurationScale(mPreviousDurationScale); 90 } 91 92 private static class EdgeEffectValidator extends PerPixelBitmapVerifier { 93 public float stretch; // in pixels, vertically 94 EdgeEffectValidator()95 EdgeEffectValidator() { 96 } 97 98 @Override verifyPixel(int x, int y, int observedColor)99 protected boolean verifyPixel(int x, int y, int observedColor) { 100 if (y < HEIGHT / 2) { 101 // Top half should always be the top color 102 return observedColor == Color.WHITE; 103 } 104 105 // This may be either bottom or top color, depending on the stretch 106 stretch += Color.red(observedColor) / 255f / WIDTH; 107 return true; 108 } 109 } 110 assertEdgeEffect(EdgeEffectInitializer initializer)111 private void assertEdgeEffect(EdgeEffectInitializer initializer) { 112 EdgeEffect edgeEffect = new EdgeEffect(getContext()); 113 edgeEffect.setSize(WIDTH, HEIGHT); 114 initializer.initialize(edgeEffect); 115 116 RenderNode renderNode = drawEdgeEffect(edgeEffect, 0, 0f); 117 118 float stretchPixelCount = getStretchDownPixelCount(renderNode); 119 120 // at least 1 pixel stretch 121 assertTrue(stretchPixelCount > 1); 122 } 123 getStretchDownPixelCount( RenderNode renderNode )124 private float getStretchDownPixelCount( 125 RenderNode renderNode 126 ) { 127 EdgeEffectValidator verifier = new EdgeEffectValidator(); 128 createTest() 129 .addCanvasClientWithoutUsingPicture((canvas, width, height) -> { 130 canvas.drawRenderNode(renderNode); 131 }, true) 132 .runWithVerifier(verifier); 133 134 return verifier.stretch; 135 } 136 137 @Test testOnPull()138 public void testOnPull() { 139 assertEdgeEffect(edgeEffect -> { 140 edgeEffect.onPull(1); 141 }); 142 } 143 144 @Test testSetSize()145 public void testSetSize() { 146 EdgeEffect edgeEffect = new EdgeEffect(getContext()); 147 edgeEffect.setSize(WIDTH, HEIGHT / 2); 148 149 RenderNode renderNode = drawEdgeEffect(edgeEffect, HEIGHT / 2f, 0f); 150 151 float stretchPixelCount = getStretchDownPixelCount(renderNode); 152 153 // The top half is not in the stretched area, so the only thing being stretched is the 154 // bottom half 155 assertEquals(0f, stretchPixelCount, 0.01f); 156 } 157 158 @Test testIsFinished()159 public void testIsFinished() { 160 EdgeEffect effect = new EdgeEffect(getContext()); 161 assertTrue(effect.isFinished()); 162 effect.onPull(0.5f); 163 assertFalse(effect.isFinished()); 164 } 165 166 @Test testFinish()167 public void testFinish() { 168 EdgeEffect effect = new EdgeEffect(getContext()); 169 effect.onPull(1); 170 effect.finish(); 171 assertTrue(effect.isFinished()); 172 173 effect.onAbsorb(1000); 174 effect.finish(); 175 assertFalse(effect.draw(new Canvas())); 176 } 177 178 @Test testGetMaxHeight()179 public void testGetMaxHeight() { 180 EdgeEffect edgeEffect = new EdgeEffect(getContext()); 181 edgeEffect.setSize(200, 200); 182 assertEquals(200, edgeEffect.getMaxHeight()); 183 edgeEffect.setSize(200, 0); 184 assertEquals(0, edgeEffect.getMaxHeight()); 185 } 186 187 @Test testDistance()188 public void testDistance() { 189 EdgeEffect effect = new EdgeEffect(getContext()); 190 191 assertEquals(0f, effect.getDistance(), 0.001f); 192 193 assertEquals(0.1f, effect.onPullDistance(0.1f, 0.5f), 0.001f); 194 195 assertEquals(0.1f, effect.getDistance(), 0.001f); 196 197 assertEquals(-0.05f, effect.onPullDistance(-0.05f, 0.5f), 0.001f); 198 199 assertEquals(0.05f, effect.getDistance(), 0.001f); 200 201 assertEquals(-0.05f, effect.onPullDistance(-0.2f, 0.5f), 0.001f); 202 203 assertEquals(0f, effect.getDistance(), 0.001f); 204 } 205 206 @Test testPullToZeroReleases()207 public void testPullToZeroReleases() { 208 EdgeEffect edgeEffect = new EdgeEffect(getContext()); 209 edgeEffect.setSize(200, 200); 210 assertEquals(0.5f, edgeEffect.onPullDistance(0.5f, 0.5f), 0f); 211 assertFalse(edgeEffect.isFinished()); 212 assertEquals(-0.5f, edgeEffect.onPullDistance(-0.5f, 0.5f), 0f); 213 assertTrue(edgeEffect.isFinished()); 214 } 215 216 @Test testFlingNegativeReleases()217 public void testFlingNegativeReleases() throws Throwable { 218 MockVsyncHelper.runOnVsyncThread(() -> { 219 EdgeEffect edgeEffect = new EdgeEffect(getContext()); 220 edgeEffect.setSize(WIDTH, HEIGHT); 221 assertEquals(0.01f, edgeEffect.onPullDistance(0.01f, 0.5f), 0f); 222 assertFalse(edgeEffect.isFinished()); 223 edgeEffect.onAbsorb(-10000); 224 nextFrame(); 225 drawEdgeEffect(edgeEffect, 0f, 0f); 226 // It should have flung past 0 in one frame 227 assertTrue(edgeEffect.isFinished()); 228 }); 229 } 230 231 /** 232 * Can't stretch past 1.0 233 */ 234 @Test testMaxStretch()235 public void testMaxStretch() throws Throwable { 236 EdgeEffect edgeEffect = new EdgeEffect(getContext()); 237 edgeEffect.setSize(WIDTH, HEIGHT); 238 float pulled = edgeEffect.onPullDistance(1f, 0.5f); 239 assertEquals(1f, pulled, 0.001f); 240 assertEquals(1f, edgeEffect.getDistance(), 0.001f); 241 pulled = edgeEffect.onPullDistance(0.1f, 0.5f); 242 assertEquals(0.1f, pulled, 0.001f); 243 assertEquals(1f, edgeEffect.getDistance(), 0.001f); 244 } 245 246 /** 247 * A fling past 1.0 results in stopping at 1 and then retracting. 248 */ 249 @Test testMaxFling()250 public void testMaxFling() throws Throwable { 251 MockVsyncHelper.runOnVsyncThread(() -> { 252 EdgeEffect edgeEffect = new EdgeEffect(getContext()); 253 edgeEffect.setSize(WIDTH, HEIGHT); 254 assertEquals(0.99f, edgeEffect.onPullDistance(0.99f, 0.5f), 0.001f); 255 edgeEffect.onAbsorb(10000); 256 nextFrame(); 257 drawEdgeEffect(edgeEffect, 0f, 0f); 258 assertEquals(1f, edgeEffect.getDistance(), 0.001f); 259 nextFrame(); 260 drawEdgeEffect(edgeEffect, 0f, 0f); 261 assertTrue(edgeEffect.getDistance() < 1f); 262 }); 263 } 264 265 private RenderNode drawStretchEffect(float rotation) { 266 EdgeEffect edgeEffect = new EdgeEffect(getContext()); 267 edgeEffect.setSize(WIDTH, HEIGHT); 268 edgeEffect.onPullDistance(1f, 0.5f); 269 270 return drawEdgeEffect(edgeEffect, 0, rotation); 271 } 272 273 private RenderNode drawEdgeEffect(EdgeEffect edgeEffect, float verticalOffset, float rotation) { 274 RenderNode renderNode = new RenderNode(""); 275 renderNode.setPosition(0, 0, WIDTH, HEIGHT); 276 RecordingCanvas recordingCanvas = renderNode.beginRecording(); 277 Paint paint = new Paint(); 278 paint.setColor(Color.WHITE); 279 recordingCanvas.drawRect(0f, 0f, WIDTH, HEIGHT / 2f, paint); 280 paint.setColor(Color.BLACK); 281 recordingCanvas.drawRect(0, HEIGHT / 2f, WIDTH, HEIGHT, paint); 282 renderNode.endRecording(); 283 284 RenderNode outer = new RenderNode("outer"); 285 outer.setPosition(0, 0, WIDTH, HEIGHT); 286 RecordingCanvas outerRecordingCanvas = outer.beginRecording(); 287 outerRecordingCanvas.drawRenderNode(renderNode); 288 recordingCanvas.translate(0f, verticalOffset); 289 recordingCanvas.rotate(rotation, WIDTH / 2f, HEIGHT / 2f); 290 edgeEffect.draw(outerRecordingCanvas); 291 outer.endRecording(); 292 return outer; 293 } 294 295 @Test 296 public void testStretchTop() { 297 RenderNode renderNode = drawStretchEffect(0f); 298 Rect innerRect = new Rect(0, 0, WIDTH, HEIGHT / 2 + 1); 299 Rect outerRect = new Rect(0, HEIGHT / 2 + 10, WIDTH, HEIGHT); 300 createTest() 301 .addCanvasClientWithoutUsingPicture((canvas, width, height) -> { 302 canvas.drawRenderNode(renderNode); 303 }, true) 304 .runWithVerifier( 305 new RegionVerifier().addVerifier( 306 innerRect, 307 new ColorVerifier(Color.WHITE) 308 ).addVerifier( 309 outerRect, 310 new ColorVerifier(Color.BLACK) 311 )); 312 } 313 314 @Test testStretchBottom()315 public void testStretchBottom() { 316 RenderNode renderNode = drawStretchEffect(180f); 317 Rect innerRect = new Rect(0, 0, WIDTH, 1); 318 Rect outerRect = new Rect(0, (HEIGHT / 2) - 1, WIDTH, HEIGHT / 2); 319 createTest() 320 .addCanvasClientWithoutUsingPicture((canvas, width, height) -> { 321 canvas.drawRenderNode(renderNode); 322 }, true) 323 .runWithVerifier( 324 new RegionVerifier().addVerifier( 325 innerRect, 326 new ColorVerifier(Color.WHITE) 327 ).addVerifier( 328 outerRect, 329 new ColorVerifier(Color.BLACK) 330 )); 331 } 332 333 @Test testNoSetSizeCallDoesNotCrash()334 public void testNoSetSizeCallDoesNotCrash() { 335 EdgeEffect edgeEffect = new EdgeEffect(getContext()); 336 edgeEffect.onPullDistance(1f, 1f); 337 edgeEffect.onAbsorb(100); 338 edgeEffect.onRelease(); 339 340 RenderNode node = new RenderNode(""); 341 RecordingCanvas canvas = node.beginRecording(); 342 edgeEffect.draw(canvas); 343 node.endRecording(); 344 } 345 346 @Test testInvalidPullDistanceDoesNotCrash()347 public void testInvalidPullDistanceDoesNotCrash() { 348 EdgeEffect edgeEffect = new EdgeEffect(getContext()); 349 // Verify that bad inputs to onPull do not crash 350 edgeEffect.onPull(Float.NaN, Float.NaN); 351 352 edgeEffect.setSize(TEST_WIDTH, TEST_HEIGHT); 353 RenderNode node = new RenderNode(""); 354 node.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT); 355 RecordingCanvas canvas = node.beginRecording(); 356 357 edgeEffect.draw(canvas); 358 node.endRecording(); 359 } 360 361 @Test testAbsorbThenDrawDoesNotCrash()362 public void testAbsorbThenDrawDoesNotCrash() { 363 MockVsyncHelper.runOnVsyncThread(() -> { 364 EdgeEffect edgeEffect = new EdgeEffect(getContext()); 365 edgeEffect.onPullDistance(1f, 1f); 366 edgeEffect.onAbsorb(100); 367 edgeEffect.onRelease(); 368 369 nextFrame(); 370 371 edgeEffect.setSize(10, 10); 372 RenderNode node = new RenderNode(""); 373 node.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT); 374 RecordingCanvas canvas = node.beginRecording(); 375 Paint paint = new Paint(); 376 paint.setColor(Color.RED); 377 canvas.drawRect(0f, 0f, TEST_WIDTH, TEST_HEIGHT, paint); 378 379 canvas.rotate(90, TEST_WIDTH / 2f, TEST_HEIGHT / 2f); 380 edgeEffect.draw(canvas); 381 node.endRecording(); 382 }); 383 } 384 385 @Test testStretchEffectUsesLayer()386 public void testStretchEffectUsesLayer() { 387 // Verify that any overscroll effect leverages a layer for rendering to ensure 388 // consistent graphics behavior. 389 EdgeEffect effect = new EdgeEffect(getContext()); 390 OverscrollDrawable drawable = new OverscrollDrawable(effect, Color.CYAN); 391 drawable.setBounds(0, 0, TEST_WIDTH, TEST_HEIGHT); 392 393 // Verify that the black background is visible when rendering without overscroll 394 createTest() 395 .addCanvasClientWithoutUsingPicture((canvas, width, height) -> { 396 canvas.drawColor(Color.BLACK); 397 drawable.setOverscroll(false); 398 drawable.draw(canvas); 399 }, true) 400 .runWithVerifier( 401 new RegionVerifier().addVerifier( 402 new Rect(0, 0, TEST_WIDTH, TEST_HEIGHT), 403 new ColorVerifier(Color.BLACK) 404 )); 405 406 // Verify cyan color is visible when rendering with overscroll enabled 407 createTest() 408 .addCanvasClientWithoutUsingPicture((canvas, width, height) -> { 409 canvas.drawColor(Color.BLACK); 410 drawable.setOverscroll(true); 411 drawable.draw(canvas); 412 }, true) 413 .runWithVerifier( 414 new RegionVerifier().addVerifier( 415 new Rect(0, 0, TEST_WIDTH, TEST_HEIGHT), 416 new ColorVerifier(Color.CYAN) 417 )); 418 } 419 420 /** 421 * Test drawable class that renders content using {@link BlendMode#DST_OVER} which 422 * requires a compositing layer in order to draw pixels, otherwise nothing is drawn. 423 */ 424 private static class OverscrollDrawable extends Drawable { 425 426 private final int mChildColor; 427 428 private final EdgeEffect mEdgeEffect; 429 430 private final RenderNode mParentNode = new RenderNode(""); 431 private final RenderNode mChildNode = new RenderNode(""); 432 433 private boolean mOverscroll = false; 434 OverscrollDrawable(EdgeEffect edgeEffect, int color)435 public OverscrollDrawable(EdgeEffect edgeEffect, int color) { 436 mChildColor = color; 437 mEdgeEffect = edgeEffect; 438 } 439 setOverscroll(boolean overscroll)440 public void setOverscroll(boolean overscroll) { 441 mOverscroll = overscroll; 442 invalidateSelf(); 443 } 444 onBoundsChange(Rect bounds)445 protected void onBoundsChange(Rect bounds) { 446 super.onBoundsChange(bounds); 447 mEdgeEffect.setSize(bounds.width(), bounds.height()); 448 mParentNode.setPosition(0, 0, bounds.width(), bounds.height()); 449 mChildNode.setPosition(0, 0, bounds.width(), bounds.height()); 450 } 451 452 @Override draw(Canvas canvas)453 public void draw(Canvas canvas) { 454 if (mOverscroll) { 455 mEdgeEffect.onPullDistance(1.0f, 0.5f); 456 } else { 457 mEdgeEffect.finish(); 458 } 459 460 RecordingCanvas parentCanvas = mParentNode.beginRecording(); 461 { 462 RecordingCanvas childCanvas = mChildNode.beginRecording(); 463 { 464 childCanvas.drawColor(mChildColor, BlendMode.DST_OVER); 465 } 466 mChildNode.endRecording(); 467 468 parentCanvas.drawRenderNode(mChildNode); 469 mEdgeEffect.draw(parentCanvas); 470 } 471 mParentNode.endRecording(); 472 473 canvas.drawRenderNode(mParentNode); 474 } 475 476 @Override setAlpha(int alpha)477 public void setAlpha(int alpha) { 478 // NO-OP 479 } 480 481 @Override setColorFilter(ColorFilter colorFilter)482 public void setColorFilter(ColorFilter colorFilter) { 483 // NO-OP 484 } 485 486 @Override getOpacity()487 public int getOpacity() { 488 return PixelFormat.TRANSLUCENT; 489 } 490 } 491 492 /** 493 * A held pull should not retract. 494 */ 495 @Test 496 @LargeTest testStretchPullAndHold()497 public void testStretchPullAndHold() throws Exception { 498 MockVsyncHelper.runOnVsyncThread(() -> { 499 EdgeEffect edgeEffect = createEdgeEffectWithPull(); 500 assertEquals(0.25f, edgeEffect.getDistance(), 0.001f); 501 502 // We must wait until the EdgeEffect would normally start receding (167 ms) 503 sleepAnimationTime(200); 504 505 // Drawing will cause updates of the distance if it is animating 506 RenderNode renderNode = new RenderNode(null); 507 Canvas canvas = renderNode.beginRecording(); 508 edgeEffect.draw(canvas); 509 510 // A glow effect would start receding now, so let's be sure it doesn't: 511 sleepAnimationTime(200); 512 edgeEffect.draw(canvas); 513 514 // It should not be updating now 515 assertEquals(0.25f, edgeEffect.getDistance(), 0.001f); 516 517 // Now let's release it and it should start animating 518 edgeEffect.onRelease(); 519 520 sleepAnimationTime(20); 521 522 // Now that it should be animating, the draw should update the distance 523 edgeEffect.draw(canvas); 524 525 assertTrue(edgeEffect.getDistance() < 0.25f); 526 }); 527 } 528 529 /** 530 * It should be possible to catch the stretch effect during an animation. 531 */ 532 @Test 533 @LargeTest 534 public void testCatchStretchDuringAnimation() throws Exception { 535 MockVsyncHelper.runOnVsyncThread(() -> { 536 EdgeEffect edgeEffect = createEdgeEffectWithPull(); 537 assertEquals(0.25f, edgeEffect.getDistance(), 0.001f); 538 edgeEffect.onRelease(); 539 540 // Wait some time to be sure it is animating away. 541 sleepAnimationTime(20); 542 543 // Drawing will cause updates of the distance if it is animating 544 RenderNode renderNode = new RenderNode(null); 545 Canvas canvas = renderNode.beginRecording(); 546 edgeEffect.draw(canvas); 547 548 // It should have started retracting. Now catch it. 549 float consumed = edgeEffect.onPullDistance(0f, 0.5f); 550 assertEquals(0f, consumed, 0f); 551 552 float distanceAfterAnimation = edgeEffect.getDistance(); 553 assertTrue(distanceAfterAnimation < 0.25f); 554 555 sleepAnimationTime(50); 556 557 // There should be no change once it has been caught. 558 edgeEffect.draw(canvas); 559 assertEquals(distanceAfterAnimation, edgeEffect.getDistance(), 0f); 560 }); 561 } 562 563 /** 564 * When an EdgeEffect is drawn on a non-RecordingCanvas, the animation 565 * should immediately end. 566 */ 567 @Test 568 public void testStretchOnBitmapCanvas() throws Throwable { 569 EdgeEffect edgeEffect = createEdgeEffectWithPull(); 570 Bitmap bitmap = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888); 571 Canvas canvas = new Canvas(bitmap); 572 edgeEffect.draw(canvas); 573 assertTrue(edgeEffect.isFinished()); 574 assertEquals(0f, edgeEffect.getDistance(), 0f); 575 } 576 577 @Test 578 public void testEdgeEffectWithAnimationsDisabled() { 579 float previousDuration = ValueAnimator.getDurationScale(); 580 ValueAnimator.setDurationScale(0); 581 try { 582 EdgeEffect edgeEffect = createEdgeEffectWithPull(); 583 RenderNode renderNode = new RenderNode(""); 584 renderNode.setPosition(0, 0, WIDTH, HEIGHT); 585 586 RecordingCanvas recordingCanvas = renderNode.beginRecording(); 587 Paint paint = new Paint(); 588 paint.setColor(Color.WHITE); 589 recordingCanvas.drawRect(0f, 0f, WIDTH, HEIGHT / 2f, paint); 590 paint.setColor(Color.BLACK); 591 recordingCanvas.drawRect(0, HEIGHT / 2f, WIDTH, HEIGHT, paint); 592 edgeEffect.draw(recordingCanvas); 593 594 renderNode.endRecording(); 595 596 assertTrue(edgeEffect.isFinished()); 597 assertEquals(0f, edgeEffect.getDistance(), 0f); 598 599 Rect innerRect = new Rect(0, 0, WIDTH, HEIGHT / 2); 600 Rect outerRect = new Rect(0, HEIGHT / 2, WIDTH, HEIGHT); 601 createTest() 602 .addCanvasClientWithoutUsingPicture((canvas, width, height) -> { 603 canvas.drawRenderNode(renderNode); 604 }, true) 605 .runWithVerifier( 606 new RegionVerifier().addVerifier( 607 innerRect, 608 new ColorVerifier(Color.WHITE) 609 ).addVerifier( 610 outerRect, 611 new ColorVerifier(Color.BLACK) 612 )); 613 } finally { 614 ValueAnimator.setDurationScale(previousDuration); 615 } 616 } 617 618 @Test testEdgeEffectAnimationDisabledMidAnimation()619 public void testEdgeEffectAnimationDisabledMidAnimation() { 620 float previousDuration = ValueAnimator.getDurationScale(); 621 try { 622 EdgeEffect edgeEffect = new EdgeEffect(getContext()); 623 edgeEffect.setSize(WIDTH, HEIGHT); 624 edgeEffect.onPullDistance(0.5f, 0.5f); 625 edgeEffect.onAbsorb(100); 626 627 // Explicitly disable animations post stretch 628 ValueAnimator.setDurationScale(0); 629 630 RenderNode renderNode = new RenderNode(""); 631 renderNode.setPosition(0, 0, WIDTH, HEIGHT); 632 633 RecordingCanvas recordingCanvas = renderNode.beginRecording(); 634 Paint paint = new Paint(); 635 paint.setColor(Color.WHITE); 636 recordingCanvas.drawRect(0f, 0f, WIDTH, HEIGHT / 2f, paint); 637 paint.setColor(Color.BLACK); 638 recordingCanvas.drawRect(0, HEIGHT / 2f, WIDTH, HEIGHT, paint); 639 edgeEffect.draw(recordingCanvas); 640 641 renderNode.endRecording(); 642 643 assertTrue(edgeEffect.isFinished()); 644 assertEquals(0f, edgeEffect.getDistance(), 0f); 645 646 Rect innerRect = new Rect(0, 0, WIDTH, HEIGHT / 2); 647 Rect outerRect = new Rect(0, HEIGHT / 2, WIDTH, HEIGHT); 648 createTest() 649 .addCanvasClientWithoutUsingPicture((canvas, width, height) -> { 650 canvas.drawRenderNode(renderNode); 651 }, true) 652 .runWithVerifier( 653 new RegionVerifier().addVerifier( 654 innerRect, 655 new ColorVerifier(Color.WHITE) 656 ).addVerifier( 657 outerRect, 658 new ColorVerifier(Color.BLACK) 659 )); 660 } finally { 661 ValueAnimator.setDurationScale(previousDuration); 662 } 663 } 664 665 @Test testOnAborbAfterStretch()666 public void testOnAborbAfterStretch() throws Throwable { 667 EdgeEffect edgeEffect = new EdgeEffect(getContext()); 668 edgeEffect.setSize(100, 100); 669 float distance = edgeEffect.onPullDistance(0.5f, 0.5f); 670 edgeEffect.onAbsorb(100); 671 assertEquals(distance, edgeEffect.getDistance(), 0.01f); 672 } 673 createEdgeEffectWithPull()674 private EdgeEffect createEdgeEffectWithPull() { 675 EdgeEffect edgeEffect = new EdgeEffect(getContext()); 676 edgeEffect.setSize(100, 100); 677 edgeEffect.onPullDistance(0.25f, 0.5f); 678 return edgeEffect; 679 } 680 681 /** 682 * This sleeps until the {@link AnimationUtils#currentAnimationTimeMillis()} changes 683 * by at least <code>durationMillis</code> milliseconds. This is useful for EdgeEffect because 684 * it uses that mechanism to determine the animation duration. 685 * 686 * Must be in a {@link MockVsyncHelper#runOnVsyncThread(MockVsyncHelper.CallableVoid)} 687 * 688 * @param durationMillis The time to sleep in milliseconds. 689 */ sleepAnimationTime(long durationMillis)690 private void sleepAnimationTime(long durationMillis) { 691 AnimationUtils.lockAnimationClock( 692 AnimationUtils.currentAnimationTimeMillis() + durationMillis); 693 } 694 695 private interface StretchVerifier { 696 void verify(float oldStretch, float newStretch); 697 } 698 699 // validates changes to the stretch over the course of an animation verifyStretch( long timeBetweenFrames, EdgeEffectInitializer initializer, StretchVerifier stretchVerifier )700 private void verifyStretch( 701 long timeBetweenFrames, 702 EdgeEffectInitializer initializer, 703 StretchVerifier stretchVerifier 704 ) { 705 MockVsyncHelper.runOnVsyncThread(() -> { 706 EdgeEffect edgeEffect = new EdgeEffect(getContext()); 707 edgeEffect.setSize(WIDTH, HEIGHT); 708 initializer.initialize(edgeEffect); 709 RenderNode renderNode1 = drawEdgeEffect(edgeEffect, 0, 0); 710 float oldStretch = getStretchDownPixelCount(renderNode1); 711 for (int i = 0; i < 3; i++) { 712 sleepAnimationTime(timeBetweenFrames); 713 RenderNode renderNode2 = drawEdgeEffect(edgeEffect, 0, 0); 714 float newStretch = getStretchDownPixelCount(renderNode2); 715 stretchVerifier.verify(oldStretch, newStretch); 716 oldStretch = newStretch; 717 } 718 }); 719 } 720 721 @Test testOnAbsorb()722 public void testOnAbsorb() { 723 verifyStretch( 724 8, // The spring will bounce back very quickly 725 edgeEffect -> edgeEffect.onAbsorb(300), 726 (oldStretch, newStretch) -> { 727 assertTrue("Stretch should grow", 728 oldStretch < newStretch); 729 } 730 ); 731 } 732 733 @Test testOnRelease()734 public void testOnRelease() { 735 verifyStretch( 736 16, 737 edgeEffect -> { 738 edgeEffect.onPull(1); 739 edgeEffect.onRelease(); 740 }, (oldStretch, newStretch) -> 741 assertTrue("Stretch should decrease", oldStretch > newStretch) 742 ); 743 } 744 } 745