Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3fef1bb7 authored by Doris Liu's avatar Doris Liu Committed by Android (Google) Code Review
Browse files

Merge "Test ValueAnimator with custom timing pulse provider"

parents c8161225 2822a426
Loading
Loading
Loading
Loading
+347 −1
Original line number Diff line number Diff line
@@ -15,13 +15,21 @@
*/
package android.animation;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.Choreographer;
import android.view.animation.LinearInterpolator;

import java.util.ArrayList;

import static android.test.MoreAsserts.assertNotEqual;

public class ValueAnimatorTests extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> {
    private static final long WAIT_TIME_OUT = 5000;
    private ValueAnimator a1;
    private ValueAnimator a2;

@@ -34,6 +42,9 @@ public class ValueAnimatorTests extends ActivityInstrumentationTestCase2<BasicAn
    private final static int A2_START_VALUE = 100;
    private final static int A2_END_VALUE = 200;

    private final static long DEFAULT_FRAME_INTERVAL = 5; //ms
    private final static long COMMIT_DELAY = 3; //ms

    public ValueAnimatorTests() {
        super(BasicAnimatorActivity.class);
    }
@@ -47,9 +58,9 @@ public class ValueAnimatorTests extends ActivityInstrumentationTestCase2<BasicAn

    @Override
    public void tearDown() throws Exception {
        super.tearDown();
        a1 = null;
        a2 = null;
        super.tearDown();
    }

    @SmallTest
@@ -492,10 +503,265 @@ public class ValueAnimatorTests extends ActivityInstrumentationTestCase2<BasicAn
        });
    }

    @SmallTest
    public void testUpdateListener() throws InterruptedException {

        final MyFrameCallbackProvider provider = new MyFrameCallbackProvider();
        long sleep = 0;
        while (provider.mHandler == null) {
            Thread.sleep(POLL_INTERVAL);
            sleep += POLL_INTERVAL;
            if (sleep > WAIT_TIME_OUT) {
                break;
            }
        }
        // Either the looper has started, or timed out
        assertNotNull(provider.mHandler);

        final MyListener listener = new MyListener();
        final MyUpdateListener l1 = new MyUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                long currentTime = SystemClock.uptimeMillis();
                long frameDelay = provider.getFrameDelay();
                if (lastUpdateTime > 0) {
                    // Error tolerance here is one frame.
                    assertTrue((currentTime - lastUpdateTime) < frameDelay * 2);
                } else {
                    // First frame:
                    assertTrue(listener.startCalled);
                    assertTrue(listener.startTime > 0);
                    assertTrue(currentTime - listener.startTime < frameDelay * 2);
                }
                super.onAnimationUpdate(animation);
            }
        };
        a1.addUpdateListener(l1);
        a1.addListener(listener);
        a1.setStartDelay(100);

        provider.mHandler.post(new Runnable() {
            @Override
            public void run() {
                AnimationHandler.getInstance().setProvider(provider);
                a1.start();
            }
        });
        Thread.sleep(POLL_INTERVAL);
        assertTrue(a1.isStarted());
        Thread.sleep(a1.getTotalDuration() + TOLERANCE);
        // Finished by now.
        assertFalse(a1.isStarted());
        assertTrue(listener.endTime > 0);

        // Check the time difference between last frame and end time.
        assertTrue(listener.endTime >= l1.lastUpdateTime);
        assertTrue(listener.endTime - l1.lastUpdateTime < 2 * provider.getFrameDelay());
    }


    @SmallTest
    public void testConcurrentModification() throws Throwable {
        // Attempt to modify list of animations as the list is being iterated
        final ValueAnimator a0 = ValueAnimator.ofInt(100, 200).setDuration(500);
        final ValueAnimator a3 = ValueAnimator.ofFloat(0, 1).setDuration(500);
        final ValueAnimator a4 = ValueAnimator.ofInt(200, 300).setDuration(500);
        final MyListener listener = new MyListener() {
            @Override
            public void onAnimationEnd(Animator anim) {
                super.onAnimationEnd(anim);
                // AnimationHandler should be iterating the list at the moment, end/cancel all
                // the other animations. No ConcurrentModificationException should happen.
                a0.cancel();
                a1.end();
                a3.end();
                a4.cancel();
            }
        };
        a2.addListener(listener);

        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                a0.start();
                a1.start();
                a2.start();
                a3.start();
                a4.start();
            }
        });
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                assertTrue(a0.isStarted());
                assertTrue(a1.isStarted());
                assertTrue(a2.isStarted());
                assertTrue(a3.isStarted());
                assertTrue(a4.isStarted());
            }
        });
        Thread.sleep(POLL_INTERVAL);
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                // End the animator that should be in the middle of the list.
                a2.end();
            }
        });
        Thread.sleep(POLL_INTERVAL);
        assertTrue(listener.endCalled);
        assertFalse(a0.isStarted());
        assertFalse(a1.isStarted());
        assertFalse(a2.isStarted());
        assertFalse(a3.isStarted());
        assertFalse(a4.isStarted());
    }

    @SmallTest
    public void testASeek() throws Throwable {
        final MyListener l1 = new MyListener();
        final MyListener l2 = new MyListener();
        final MyUpdateListener updateListener1 = new MyUpdateListener();
        final MyUpdateListener updateListener2 = new MyUpdateListener();
        final float a1StartFraction = 0.2f;
        final float a2StartFraction = 0.3f;

        // Extend duration so we have plenty of latitude to manipulate the animations when they
        // are running.
        a1.setDuration(1000);
        a2.setDuration(1000);
        a1.addListener(l1);
        a2.addListener(l2);
        a1.addUpdateListener(updateListener1);
        a2.addUpdateListener(updateListener2);
        TimeInterpolator interpolator = new LinearInterpolator();
        a1.setInterpolator(interpolator);
        a2.setInterpolator(interpolator);

        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                assertFalse(a1.isStarted());
                assertFalse(a1.isRunning());
                assertFalse(a2.isStarted());
                assertFalse(a2.isRunning());

                // Test isRunning() and isStarted() before and after seek
                a1.setCurrentFraction(a1StartFraction);
                a2.setCurrentFraction(a2StartFraction);

                assertFalse(a1.isStarted());
                assertFalse(a1.isRunning());
                assertFalse(a2.isStarted());
                assertFalse(a2.isRunning());
            }
        });
        Thread.sleep(POLL_INTERVAL);

        // Start animation and seek during the animation.
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                assertFalse(a1.isStarted());
                assertFalse(a1.isRunning());
                assertFalse(a2.isStarted());
                assertFalse(a2.isRunning());
                assertEquals(a1StartFraction, a1.getAnimatedFraction());
                assertEquals(a2StartFraction, a2.getAnimatedFraction());

                a1.start();
                a2.start();
            }
        });

        Thread.sleep(POLL_INTERVAL);
        final float halfwayFraction = 0.5f;
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                assertTrue(l1.startCalled);
                assertTrue(l2.startCalled);
                assertFalse(l1.endCalled);
                assertFalse(l2.endCalled);

                // Check whether the animations start from the seeking fraction
                assertTrue(updateListener1.startFraction >= a1StartFraction);
                assertTrue(updateListener2.startFraction >= a2StartFraction);

                assertTrue(a1.isStarted());
                assertTrue(a1.isRunning());
                assertTrue(a2.isStarted());
                assertTrue(a2.isRunning());

                a1.setCurrentFraction(halfwayFraction);
                a2.setCurrentFraction(halfwayFraction);
            }
        });

        Thread.sleep(POLL_INTERVAL);

        // Check that seeking during running doesn't change animation's internal state
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                assertTrue(l1.startCalled);
                assertTrue(l2.startCalled);
                assertFalse(l1.endCalled);
                assertFalse(l2.endCalled);

                assertTrue(a1.isStarted());
                assertTrue(a1.isRunning());
                assertTrue(a2.isStarted());
                assertTrue(a2.isRunning());
            }
        });

        // Wait until the animators finish successfully.
        long wait = Math.max(a1.getTotalDuration(), a2.getTotalDuration());
        Thread.sleep(wait);

        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                // Verify that the animators have finished.
                assertTrue(l1.endCalled);
                assertTrue(l2.endCalled);

                assertFalse(a1.isStarted());
                assertFalse(a2.isStarted());
                assertFalse(a1.isRunning());
                assertFalse(a2.isRunning());
            }
        });

        // Re-start animator a1 after it ends normally, and check that seek value from last run
        // does not affect the new run.
        updateListener1.reset();
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                a1.start();
            }
        });

        Thread.sleep(POLL_INTERVAL);
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                assertTrue(updateListener1.wasRunning);
                assertTrue(updateListener1.startFraction >= 0);
                assertTrue(updateListener1.startFraction < halfwayFraction);
                a1.end();
            }
        });

    }

    class MyUpdateListener implements ValueAnimator.AnimatorUpdateListener {
        boolean wasRunning = false;
        long firstRunningFrameTime = -1;
        long lastUpdateTime = -1;
        float startFraction = 0;

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
@@ -503,24 +769,36 @@ public class ValueAnimatorTests extends ActivityInstrumentationTestCase2<BasicAn
            if (animation.isRunning() && !wasRunning) {
                // Delay has passed
                firstRunningFrameTime = lastUpdateTime;
                startFraction = animation.getAnimatedFraction();
                wasRunning = animation.isRunning();
            }
        }

        void reset() {
            wasRunning = false;
            firstRunningFrameTime = -1;
            lastUpdateTime = -1;
            startFraction = 0;
        }
    }

    class MyListener implements Animator.AnimatorListener {
        boolean startCalled = false;
        boolean cancelCalled = false;
        boolean endCalled = false;
        long startTime = -1;
        long endTime = -1;

        @Override
        public void onAnimationStart(Animator animation) {
            startCalled = true;
            startTime = SystemClock.uptimeMillis();
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            endCalled = true;
            endTime = SystemClock.uptimeMillis();
        }

        @Override
@@ -548,4 +826,72 @@ public class ValueAnimatorTests extends ActivityInstrumentationTestCase2<BasicAn
            resumeCalled = true;
        }
    }

    class MyFrameCallbackProvider implements AnimationHandler.AnimationFrameCallbackProvider {

        Handler mHandler = null;
        private final static int MSG_FRAME = 0;
        private long mFrameDelay = DEFAULT_FRAME_INTERVAL;
        private ArrayList<Choreographer.FrameCallback> mFrameCallbacks = new ArrayList<>();

        final LooperThread mThread = new LooperThread();

        public MyFrameCallbackProvider() {
            mThread.start();
        }

        @Override
        public void postFrameCallback(Choreographer.FrameCallback callback) {
            mHandler.sendEmptyMessageDelayed(MSG_FRAME, mFrameDelay);
            if (!mFrameCallbacks.contains(callback)) {
                mFrameCallbacks.add(callback);
            }
        }

        @Override
        public void postCommitCallback(Runnable runnable) {
            // Run the runnable after a commit delay
            mHandler.postDelayed(runnable, COMMIT_DELAY);
        }

        @Override
        public long getFrameTime() {
            return SystemClock.uptimeMillis();
        }

        @Override
        public long getFrameDelay() {
            return mFrameDelay;
        }

        @Override
        public void setFrameDelay(long delay) {
            mFrameDelay = delay;
            if (mFrameCallbacks.size() != 0) {
                mHandler.removeMessages(MSG_FRAME);
                mHandler.sendEmptyMessageDelayed(MSG_FRAME, mFrameDelay);
            }
        }

        class LooperThread extends Thread {
            public void run() {
                Looper.prepare();
                mHandler = new Handler() {
                    public void handleMessage(Message msg) {
                        // Handle message here.
                        switch (msg.what) {
                            case MSG_FRAME:
                                for (int i = 0; i < mFrameCallbacks.size(); i++) {
                                    mFrameCallbacks.get(i).doFrame(SystemClock.uptimeMillis());
                                }
                                break;
                            default:
                                break;
                        }
                    }
                };
                Looper.loop();
            }
        }
    }
}