/* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.drawee.drawable; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import com.facebook.common.testing.FakeClock; import com.facebook.testing.robolectric.v2.WithTestDefaultsRunner; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InOrder; import static org.mockito.Mockito.*; @RunWith(WithTestDefaultsRunner.class) public class FadeDrawableTest { private Drawable[] mLayers = new Drawable[] { DrawableTestUtils.mockDrawable(), DrawableTestUtils.mockDrawable(), DrawableTestUtils.mockDrawable(), }; private FakeClock mFakeClock; private FadeDrawable mFadeDrawable; private Canvas mCanvas = mock(Canvas.class); private Drawable.Callback mCallback = mock(Drawable.Callback.class); @Before public void setUp() { mFakeClock = new FakeClock(); mFadeDrawable = new FakeFadeDrawable(mFakeClock, mLayers); mFadeDrawable.setCallback(mCallback); } private void resetInteractions() { reset(mCallback, mLayers[0], mLayers[1], mLayers[2]); when(mLayers[0].mutate()).thenReturn(mLayers[0]); when(mLayers[1].mutate()).thenReturn(mLayers[1]); when(mLayers[2].mutate()).thenReturn(mLayers[2]); } @Test public void testIntrinsicDimensions() { when(mLayers[0].getIntrinsicWidth()).thenReturn(100); when(mLayers[1].getIntrinsicWidth()).thenReturn(200); when(mLayers[2].getIntrinsicWidth()).thenReturn(150); when(mLayers[0].getIntrinsicHeight()).thenReturn(400); when(mLayers[1].getIntrinsicHeight()).thenReturn(350); when(mLayers[2].getIntrinsicHeight()).thenReturn(300); Assert.assertEquals(200, mFadeDrawable.getIntrinsicWidth()); Assert.assertEquals(400, mFadeDrawable.getIntrinsicHeight()); } @Test public void testInitialState() { // initially only the fist layer is displayed and there is no transition Assert.assertEquals(FadeDrawable.TRANSITION_NONE, mFadeDrawable.mTransitionState); Assert.assertEquals(255, mFadeDrawable.mAlphas[0]); Assert.assertEquals(0, mFadeDrawable.mAlphas[1]); Assert.assertEquals(0, mFadeDrawable.mAlphas[2]); Assert.assertEquals(true, mFadeDrawable.mIsLayerOn[0]); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[1]); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[2]); } @Test public void testFadeToLayer() { // start fade mFadeDrawable.setTransitionDuration(100); mFadeDrawable.fadeToLayer(1); Assert.assertEquals(100, mFadeDrawable.mDurationMs); Assert.assertEquals(FadeDrawable.TRANSITION_STARTING, mFadeDrawable.mTransitionState); verify(mCallback).invalidateDrawable(mFadeDrawable); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[0]); Assert.assertEquals(true, mFadeDrawable.mIsLayerOn[1]); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[2]); // alphas will change only when the next draw happens Assert.assertEquals(255, mFadeDrawable.mAlphas[0]); Assert.assertEquals(0, mFadeDrawable.mAlphas[1]); Assert.assertEquals(0, mFadeDrawable.mAlphas[2]); } @Test public void testFadeUpToLayer() { // start fade mFadeDrawable.setTransitionDuration(100); mFadeDrawable.fadeUpToLayer(1); Assert.assertEquals(true, mFadeDrawable.mIsLayerOn[0]); Assert.assertEquals(true, mFadeDrawable.mIsLayerOn[1]); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[2]); Assert.assertEquals(100, mFadeDrawable.mDurationMs); Assert.assertEquals(FadeDrawable.TRANSITION_STARTING, mFadeDrawable.mTransitionState); verify(mCallback).invalidateDrawable(mFadeDrawable); // alphas will change only when the next draw happens Assert.assertEquals(255, mFadeDrawable.mAlphas[0]); Assert.assertEquals(0, mFadeDrawable.mAlphas[1]); Assert.assertEquals(0, mFadeDrawable.mAlphas[2]); } @Test public void testFadeInLayer() { //start fade in mFadeDrawable.setTransitionDuration(100); mFadeDrawable.fadeInLayer(2); Assert.assertEquals(true, mFadeDrawable.mIsLayerOn[0]); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[1]); Assert.assertEquals(true, mFadeDrawable.mIsLayerOn[2]); Assert.assertEquals(100, mFadeDrawable.mDurationMs); Assert.assertEquals(FadeDrawable.TRANSITION_STARTING, mFadeDrawable.mTransitionState); verify(mCallback).invalidateDrawable(mFadeDrawable); // alphas will change only when the next draw happens Assert.assertEquals(255, mFadeDrawable.mAlphas[0]); Assert.assertEquals(0, mFadeDrawable.mAlphas[1]); Assert.assertEquals(0, mFadeDrawable.mAlphas[2]); } @Test public void testFadeOutLayer() { //start fade out mFadeDrawable.setTransitionDuration(100); mFadeDrawable.fadeOutLayer(0); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[0]); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[1]); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[2]); Assert.assertEquals(100, mFadeDrawable.mDurationMs); Assert.assertEquals(FadeDrawable.TRANSITION_STARTING, mFadeDrawable.mTransitionState); verify(mCallback).invalidateDrawable(mFadeDrawable); // alphas will change only when the next draw happens Assert.assertEquals(255, mFadeDrawable.mAlphas[0]); Assert.assertEquals(0, mFadeDrawable.mAlphas[1]); Assert.assertEquals(0, mFadeDrawable.mAlphas[2]); } @Test public void testFadeOutAllLayers() { //start fade out mFadeDrawable.setTransitionDuration(100); mFadeDrawable.mIsLayerOn[1] = true; mFadeDrawable.mIsLayerOn[2] = true; mFadeDrawable.fadeOutAllLayers(); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[0]); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[1]); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[2]); Assert.assertEquals(100, mFadeDrawable.mDurationMs); Assert.assertEquals(FadeDrawable.TRANSITION_STARTING, mFadeDrawable.mTransitionState); verify(mCallback).invalidateDrawable(mFadeDrawable); // alphas will change only when the next draw happens Assert.assertEquals(255, mFadeDrawable.mAlphas[0]); Assert.assertEquals(0, mFadeDrawable.mAlphas[1]); Assert.assertEquals(0, mFadeDrawable.mAlphas[2]); } @Test public void testImmediateTransition() { testImmediateTransition(true); testImmediateTransition(false); } private void testImmediateTransition(boolean fadeUpToLayer) { resetInteractions(); if (fadeUpToLayer) { mFadeDrawable.fadeUpToLayer(1); } else { mFadeDrawable.fadeToLayer(1); } Assert.assertEquals(fadeUpToLayer, mFadeDrawable.mIsLayerOn[0]); Assert.assertEquals(true, mFadeDrawable.mIsLayerOn[1]); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[2]); verify(mCallback).invalidateDrawable(mFadeDrawable); mFadeDrawable.finishTransitionImmediately(); verify(mCallback, times(2)).invalidateDrawable(mFadeDrawable); Assert.assertEquals(fadeUpToLayer ? 255 : 0, mFadeDrawable.mAlphas[0]); Assert.assertEquals(255, mFadeDrawable.mAlphas[1]); Assert.assertEquals(0, mFadeDrawable.mAlphas[2]); Assert.assertEquals(FadeDrawable.TRANSITION_NONE, mFadeDrawable.mTransitionState); } @Test public void testZeroTransition() { testZeroTransition(true); testZeroTransition(false); } private void testZeroTransition(boolean fadeUpToLayer) { resetInteractions(); mFadeDrawable.setTransitionDuration(0); if (fadeUpToLayer) { mFadeDrawable.fadeUpToLayer(1); } else { mFadeDrawable.fadeToLayer(1); } Assert.assertEquals(fadeUpToLayer, mFadeDrawable.mIsLayerOn[0]); Assert.assertEquals(true, mFadeDrawable.mIsLayerOn[1]); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[2]); verify(mCallback).invalidateDrawable(mFadeDrawable); mFadeDrawable.draw(mCanvas); Assert.assertEquals(fadeUpToLayer ? 255 : 0, mFadeDrawable.mAlphas[0]); Assert.assertEquals(255, mFadeDrawable.mAlphas[1]); Assert.assertEquals(0, mFadeDrawable.mAlphas[2]); Assert.assertEquals(FadeDrawable.TRANSITION_NONE, mFadeDrawable.mTransitionState); if (fadeUpToLayer) { verify(mLayers[0]).draw(mCanvas); } verify(mLayers[1]).draw(mCanvas); } @Test public void testTransition() { testTransition(true); testTransition(false); } private void testTransition(boolean fadeUpToLayer) { // duration is set to 85 ms // 85 = 5 * 17; 5 frames of 17ms // 255 / 5 = 51; each frame alpha should increase by 51 // reset drawable mFadeDrawable.reset(); // start animation resetInteractions(); mFadeDrawable.setTransitionDuration(85); if (fadeUpToLayer) { mFadeDrawable.fadeUpToLayer(1); } else { mFadeDrawable.fadeToLayer(1); } Assert.assertEquals(fadeUpToLayer, mFadeDrawable.mIsLayerOn[0]); Assert.assertEquals(true, mFadeDrawable.mIsLayerOn[1]); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[2]); verify(mCallback).invalidateDrawable(mFadeDrawable); verifyNoMoreInteractions(mCallback, mLayers[0], mLayers[1], mLayers[2]); // first frame resetInteractions(); mFadeDrawable.draw(mCanvas); Assert.assertEquals(255, mFadeDrawable.mAlphas[0]); Assert.assertEquals(0, mFadeDrawable.mAlphas[1]); Assert.assertEquals(0, mFadeDrawable.mAlphas[2]); Assert.assertEquals(FadeDrawable.TRANSITION_RUNNING, mFadeDrawable.mTransitionState); verify(mLayers[0]).mutate(); verify(mLayers[0]).setAlpha(255); verify(mLayers[0]).draw(mCanvas); verify(mCallback).invalidateDrawable(mFadeDrawable); verifyNoMoreInteractions(mCallback, mLayers[0], mLayers[1], mLayers[2]); // intermediate frames for (int i = 1; i < 5; i++) { resetInteractions(); mFakeClock.incrementBy(17); mFadeDrawable.draw(mCanvas); Assert.assertEquals(fadeUpToLayer ? 255 : 255 - 51 * i, mFadeDrawable.mAlphas[0]); Assert.assertEquals(51 * i, mFadeDrawable.mAlphas[1]); Assert.assertEquals(0, mFadeDrawable.mAlphas[2]); Assert.assertEquals(FadeDrawable.TRANSITION_RUNNING, mFadeDrawable.mTransitionState); if (fadeUpToLayer) { verify(mLayers[0]).mutate(); verify(mLayers[0]).setAlpha(255); verify(mLayers[0]).draw(mCanvas); } else { verify(mLayers[0]).mutate(); verify(mLayers[0]).setAlpha(255 - 51 * i); verify(mLayers[0]).draw(mCanvas); } verify(mLayers[1]).mutate(); verify(mLayers[1]).setAlpha(51 * i); verify(mLayers[1]).draw(mCanvas); verify(mCallback).invalidateDrawable(mFadeDrawable); verifyNoMoreInteractions(mCallback, mLayers[0], mLayers[1], mLayers[2]); } // last frame resetInteractions(); mFakeClock.incrementBy(17); mFadeDrawable.draw(mCanvas); Assert.assertEquals(fadeUpToLayer ? 255 : 0, mFadeDrawable.mAlphas[0]); Assert.assertEquals(255, mFadeDrawable.mAlphas[1]); Assert.assertEquals(0, mFadeDrawable.mAlphas[2]); Assert.assertEquals(FadeDrawable.TRANSITION_NONE, mFadeDrawable.mTransitionState); if (fadeUpToLayer) { verify(mLayers[0]).mutate(); verify(mLayers[0]).setAlpha(255); verify(mLayers[0]).draw(mCanvas); } verify(mLayers[1]).mutate(); verify(mLayers[1]).setAlpha(255); verify(mLayers[1]).draw(mCanvas); verifyNoMoreInteractions(mCallback, mLayers[0], mLayers[1], mLayers[2]); } @Test public void testSetAlpha() { InOrder inOrder = inOrder(mLayers[0], mLayers[1], mLayers[2], mCallback); // reset drawable mFadeDrawable.reset(); inOrder.verify(mCallback).invalidateDrawable(mFadeDrawable); // start animation mFadeDrawable.setTransitionDuration(85); mFadeDrawable.fadeUpToLayer(1); inOrder.verify(mCallback).invalidateDrawable(mFadeDrawable); // first frame mFadeDrawable.draw(mCanvas); inOrder.verify(mCallback, atLeastOnce()).invalidateDrawable(mFadeDrawable); // setAlpha mFadeDrawable.setAlpha(128); Assert.assertEquals(128, mFadeDrawable.getAlpha()); inOrder.verify(mCallback).invalidateDrawable(mFadeDrawable); // next frame mFakeClock.incrementBy(17); mFadeDrawable.draw(mCanvas); Assert.assertEquals(128, mFadeDrawable.getAlpha()); Assert.assertEquals(255, mFadeDrawable.mAlphas[0]); Assert.assertEquals(51, mFadeDrawable.mAlphas[1]); Assert.assertEquals(0, mFadeDrawable.mAlphas[2]); Assert.assertEquals(FadeDrawable.TRANSITION_RUNNING, mFadeDrawable.mTransitionState); inOrder.verify(mLayers[0]).mutate(); inOrder.verify(mLayers[0]).setAlpha(128); inOrder.verify(mLayers[0]).draw(mCanvas); inOrder.verify(mLayers[1]).mutate(); inOrder.verify(mLayers[1]).setAlpha(25); inOrder.verify(mLayers[1]).draw(mCanvas); inOrder.verify(mCallback, atLeastOnce()).invalidateDrawable(mFadeDrawable); inOrder.verifyNoMoreInteractions(); // make sure the fade has finished, and verify that after that we don't invalidate mFakeClock.incrementBy(1000); mFadeDrawable.draw(mCanvas); inOrder.verify(mCallback, never()).invalidateDrawable(mFadeDrawable); } @Test public void testReset() { // go to some non-initial state mFadeDrawable.fadeToLayer(2); mFadeDrawable.finishTransitionImmediately(); resetInteractions(); mFadeDrawable.reset(); Assert.assertEquals(FadeDrawable.TRANSITION_NONE, mFadeDrawable.mTransitionState); Assert.assertEquals(255, mFadeDrawable.mAlphas[0]); Assert.assertEquals(0, mFadeDrawable.mAlphas[1]); Assert.assertEquals(0, mFadeDrawable.mAlphas[2]); Assert.assertEquals(true, mFadeDrawable.mIsLayerOn[0]); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[1]); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[2]); verify(mCallback).invalidateDrawable(mFadeDrawable); } @Test public void testBatchMode() { mFadeDrawable.beginBatchMode(); mFadeDrawable.reset(); mFadeDrawable.fadeInLayer(1); mFadeDrawable.fadeOutLayer(0); mFadeDrawable.fadeOutAllLayers(); mFadeDrawable.fadeToLayer(2); mFadeDrawable.fadeUpToLayer(1); mFadeDrawable.finishTransitionImmediately(); mFadeDrawable.endBatchMode(); verify(mCallback, times(1)).invalidateDrawable(mFadeDrawable); Assert.assertEquals(255, mFadeDrawable.mAlphas[0]); Assert.assertEquals(255, mFadeDrawable.mAlphas[1]); Assert.assertEquals(0, mFadeDrawable.mAlphas[2]); Assert.assertEquals(true, mFadeDrawable.mIsLayerOn[0]); Assert.assertEquals(true, mFadeDrawable.mIsLayerOn[1]); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[2]); } @Test public void testNoBatchMode() { mFadeDrawable.reset(); mFadeDrawable.fadeInLayer(1); mFadeDrawable.fadeOutLayer(0); mFadeDrawable.fadeOutAllLayers(); mFadeDrawable.fadeToLayer(2); mFadeDrawable.fadeUpToLayer(1); mFadeDrawable.finishTransitionImmediately(); verify(mCallback, times(7)).invalidateDrawable(mFadeDrawable); Assert.assertEquals(255, mFadeDrawable.mAlphas[0]); Assert.assertEquals(255, mFadeDrawable.mAlphas[1]); Assert.assertEquals(0, mFadeDrawable.mAlphas[2]); Assert.assertEquals(true, mFadeDrawable.mIsLayerOn[0]); Assert.assertEquals(true, mFadeDrawable.mIsLayerOn[1]); Assert.assertEquals(false, mFadeDrawable.mIsLayerOn[2]); } private class FakeFadeDrawable extends FadeDrawable { private final FakeClock mFakeClock; public FakeFadeDrawable(FakeClock fakeClock, Drawable[] layers) { super(layers); mFakeClock = fakeClock; } @Override protected long getCurrentTimeMs() { return mFakeClock.now(); } } }