Bug 711959 - Adapt JavaPanZoomController to use the RenderTask mechanism. r=Cwiiis, kats
authorAugustin Trancart <augustin.trancart@gmail.com>
Wed, 04 Sep 2013 14:07:11 -0400
changeset 145498 f746195f55957d694a7aa45c5560cf0641acf843
parent 145497 05c2d7b68a229d4221c25e1052689e6473ecd51f
child 145499 0fb65049cb956db14f3bf604b4c3b78010ea6b46
push id25214
push userkwierso@gmail.com
push dateThu, 05 Sep 2013 00:02:20 +0000
treeherdermozilla-central@99bd249e5a20 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersCwiiis, kats
bugs711959
milestone26.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 711959 - Adapt JavaPanZoomController to use the RenderTask mechanism. r=Cwiiis, kats
mobile/android/base/gfx/GeckoLayerClient.java
mobile/android/base/gfx/JavaPanZoomController.java
mobile/android/base/gfx/PanZoomTarget.java
--- a/mobile/android/base/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/gfx/GeckoLayerClient.java
@@ -916,16 +916,29 @@ public class GeckoLayerClient implements
     /** Implementation of PanZoomTarget */
     @Override
     public boolean post(Runnable action) {
         return mView.post(action);
     }
 
     /** Implementation of PanZoomTarget */
     @Override
+    public void postRenderTask(RenderTask task) {
+        mView.postRenderTask(task);
+    }
+
+    /** Implementation of PanZoomTarget */
+    @Override
+    public void removeRenderTask(RenderTask task) {
+        mView.removeRenderTask(task);
+    }
+
+
+    /** Implementation of PanZoomTarget */
+    @Override
     public boolean postDelayed(Runnable action, long delayMillis) {
         return mView.postDelayed(action, delayMillis);
     }
 
     /** Implementation of PanZoomTarget */
     @Override
     public Object getLock() {
         return this;
--- a/mobile/android/base/gfx/JavaPanZoomController.java
+++ b/mobile/android/base/gfx/JavaPanZoomController.java
@@ -25,19 +25,16 @@ import android.os.Build;
 import android.util.FloatMath;
 import android.util.Log;
 import android.view.GestureDetector;
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 
-import java.util.Timer;
-import java.util.TimerTask;
-
 /*
  * Handles the kinetic scrolling and zooming physics for a layer controller.
  *
  * Many ideas are from Joe Hewitt's Scrollability:
  *   https://github.com/joehewitt/scrollability/
  */
 class JavaPanZoomController
     extends GestureDetector.SimpleOnGestureListener
@@ -72,18 +69,18 @@ class JavaPanZoomController
     private static final float MAX_ZOOM = 4.0f;
 
     // The maximum amount we would like to scroll with the mouse
     private static final float MAX_SCROLL = 0.075f * GeckoAppShell.getDpi();
 
     // The maximum zoom factor adjustment per frame of the AUTONAV animation
     private static final float MAX_ZOOM_DELTA = 0.125f;
 
-    // Length of the bounce animation in ms
-    private static final int BOUNCE_ANIMATION_DURATION = 250;
+    // The number of time a bounce animation run at most.
+    private static final int BOUNCE_FRAME_NUMBER = 15;
 
     private enum PanZoomState {
         NOTHING,                /* no touch-start events received */
         FLING,                  /* all touches removed, but we're still scrolling page */
         TOUCHING,               /* one touch-start event received */
         PANNING_LOCKED_X,       /* touch-start followed by move (i.e. panning with axis lock) X axis */
         PANNING_LOCKED_Y,       /* as above for Y axis */
         PANNING,                /* panning without axis lock */
@@ -110,20 +107,18 @@ class JavaPanZoomController
 
     private final PanZoomTarget mTarget;
     private final SubdocumentScrollHelper mSubscroller;
     private final Axis mX;
     private final Axis mY;
     private final TouchEventHandler mTouchEventHandler;
     private final EventDispatcher mEventDispatcher;
 
-    /* The timer that handles flings or bounces. */
-    private Timer mAnimationTimer;
-    /* The runnable being scheduled by the animation timer. */
-    private AnimationRunnable mAnimationRunnable;
+    /* The task that handles flings, autonav or bounces. */
+    private PanZoomRenderTask mAnimationRenderTask;
     /* The zoom focus at the first zoom event (in page coordinates). */
     private PointF mLastZoomFocus;
     /* The time the last motion event took place. */
     private long mLastEventTime;
     /* Current state the pan/zoom UI is in. */
     private PanZoomState mState;
     /* The per-frame zoom delta for the currently-running AUTONAV animation. */
     private float mAutonavZoomDelta;
@@ -414,17 +409,17 @@ class JavaPanZoomController
 
     /*
      * Panning/scrolling
      */
 
     private boolean handleTouchStart(MotionEvent event) {
         // user is taking control of movement, so stop
         // any auto-movement we have going
-        stopAnimationTimer();
+        stopAnimationTask();
 
         switch (mState) {
         case ANIMATED_ZOOM:
             // We just interrupted a double-tap animation, so force a redraw in
             // case this touchstart is just a tap that doesn't end up triggering
             // a redraw
             mTarget.forceRedraw(null);
             // fall through
@@ -601,17 +596,17 @@ class JavaPanZoomController
                 bounce(); // if not needed, this will automatically go to state NOTHING
                 return true;
             }
             return false;
         }
 
         if (mState == PanZoomState.NOTHING) {
             setState(PanZoomState.AUTONAV);
-            startAnimationTimer(new AutonavRunnable());
+            startAnimationRenderTask(new AutonavRenderTask());
         }
         if (mState == PanZoomState.AUTONAV) {
             mX.setAutoscrollVelocity(velocityX);
             mY.setAutoscrollVelocity(velocityY);
             mAutonavZoomDelta = zoomDelta;
             return true;
         }
         return false;
@@ -727,74 +722,67 @@ class JavaPanZoomController
 
     private void scrollBy(float dx, float dy) {
         mTarget.scrollBy(dx, dy);
     }
 
     private void fling() {
         updatePosition();
 
-        stopAnimationTimer();
+        stopAnimationTask();
 
         boolean stopped = stopped();
         mX.startFling(stopped);
         mY.startFling(stopped);
 
-        startAnimationTimer(new FlingRunnable());
+        startAnimationRenderTask(new FlingRenderTask());
     }
 
     /* Performs a bounce-back animation to the given viewport metrics. */
     private void bounce(ImmutableViewportMetrics metrics, PanZoomState state) {
-        stopAnimationTimer();
+        stopAnimationTask();
 
         ImmutableViewportMetrics bounceStartMetrics = getMetrics();
         if (bounceStartMetrics.fuzzyEquals(metrics)) {
             setState(PanZoomState.NOTHING);
             return;
         }
 
         setState(state);
 
         // At this point we have already set mState to BOUNCE or ANIMATED_ZOOM, so
         // getRedrawHint() is returning false. This means we can safely call
         // setAnimationTarget to set the new final display port and not have it get
         // clobbered by display ports from intermediate animation frames.
         mTarget.setAnimationTarget(metrics);
-        startAnimationTimer(new BounceRunnable(bounceStartMetrics, metrics));
+        startAnimationRenderTask(new BounceRenderTask(bounceStartMetrics, metrics));
     }
 
     /* Performs a bounce-back animation to the nearest valid viewport metrics. */
     private void bounce() {
         bounce(getValidViewportMetrics(), PanZoomState.BOUNCE);
     }
 
     /* Starts the fling or bounce animation. */
-    private void startAnimationTimer(final AnimationRunnable runnable) {
-        if (mAnimationTimer != null) {
-            Log.e(LOGTAG, "Attempted to start a new timer without canceling the old one!");
-            stopAnimationTimer();
+    private void startAnimationRenderTask(final PanZoomRenderTask task) {
+        if (mAnimationRenderTask != null) {
+            Log.e(LOGTAG, "Attempted to start a new task without canceling the old one!");
+            stopAnimationTask();
         }
 
-        mAnimationTimer = new Timer("Animation Timer");
-        mAnimationRunnable = runnable;
-        mAnimationTimer.scheduleAtFixedRate(new TimerTask() {
-            @Override
-            public void run() { mTarget.post(runnable); }
-        }, 0, (int)Axis.MS_PER_FRAME);
+        mAnimationRenderTask = task;
+        mTarget.postRenderTask(mAnimationRenderTask);
     }
 
     /* Stops the fling or bounce animation. */
-    private void stopAnimationTimer() {
-        if (mAnimationTimer != null) {
-            mAnimationTimer.cancel();
-            mAnimationTimer = null;
-        }
-        if (mAnimationRunnable != null) {
-            mAnimationRunnable.terminate();
-            mAnimationRunnable = null;
+    private void stopAnimationTask() {
+        if (mAnimationRenderTask != null) {
+            mAnimationRenderTask.terminate();
+            mTarget.removeRenderTask(mAnimationRenderTask);
+            mAnimationRenderTask = null;
         }
     }
 
     private float getVelocity() {
         float xvel = mX.getRealVelocity();
         float yvel = mY.getRealVelocity();
         return FloatMath.sqrt(xvel * xvel + yvel * yvel);
     }
@@ -825,69 +813,89 @@ class JavaPanZoomController
             }
         } else {
             synchronized (mTarget.getLock()) {
                 scrollBy(displacement.x, displacement.y);
             }
         }
     }
 
-    private abstract class AnimationRunnable implements Runnable {
-        private boolean mAnimationTerminated;
+    /**
+     * This class is an implementation of RenderTask which enforces its implementor to run in the UI thread.
+     *
+     */
+    private abstract class PanZoomRenderTask extends RenderTask {
 
-        /* This should always run on the UI thread */
-        @Override
-        public final void run() {
-            /*
-             * Since the animation timer queues this runnable on the UI thread, it
-             * is possible that even when the animation timer is cancelled, there
-             * are multiple instances of this queued, so we need to have another
-             * mechanism to abort. This is done by using the mAnimationTerminated flag.
-             */
-            if (mAnimationTerminated) {
-                return;
+        private final Runnable mRunnable = new Runnable() {
+            @Override
+            public final void run() {
+                if (mContinueAnimation) {
+                    animateFrame();
+                }
             }
-            animateFrame();
+        };
+
+        private boolean mContinueAnimation = true;
+
+        public PanZoomRenderTask() {
+            super(false);
         }
 
+        @Override
+        protected final boolean internalRun(long timeDelta, long currentFrameStartTime) {
+
+            mTarget.post(mRunnable);
+            return mContinueAnimation;
+        }
+
+        /**
+         * The method subclasses must override. This method is run on the UI thread thanks to internalRun
+         */
         protected abstract void animateFrame();
 
-        /* This should always run on the UI thread */
-        protected final void terminate() {
-            mAnimationTerminated = true;
+        /**
+         * Terminate the animation.
+         */
+        public void terminate() {
+            mContinueAnimation = false;
         }
     }
 
-    private class AutonavRunnable extends AnimationRunnable {
+    private class AutonavRenderTask extends PanZoomRenderTask {
+        public AutonavRenderTask() {
+            super();
+        }
+
         @Override
         protected void animateFrame() {
             if (mState != PanZoomState.AUTONAV) {
                 finishAnimation();
                 return;
             }
 
             updatePosition();
             synchronized (mTarget.getLock()) {
                 mTarget.setViewportMetrics(applyZoomDelta(getMetrics(), mAutonavZoomDelta));
             }
         }
     }
 
-    /* The callback that performs the bounce animation. */
-    private class BounceRunnable extends AnimationRunnable {
-        /* The current frame of the bounce-back animation */
-        private int mBounceFrame;
+    /* The task that performs the bounce animation. */
+    private class BounceRenderTask extends PanZoomRenderTask {
+
         /*
          * The viewport metrics that represent the start and end of the bounce-back animation,
          * respectively.
          */
         private ImmutableViewportMetrics mBounceStartMetrics;
         private ImmutableViewportMetrics mBounceEndMetrics;
+        private int mBounceFrame;
 
-        BounceRunnable(ImmutableViewportMetrics startMetrics, ImmutableViewportMetrics endMetrics) {
+        BounceRenderTask(ImmutableViewportMetrics startMetrics, ImmutableViewportMetrics endMetrics) {
+            super();
             mBounceStartMetrics = startMetrics;
             mBounceEndMetrics = endMetrics;
         }
 
         @Override
         protected void animateFrame() {
             /*
              * The pan/zoom controller might have signaled to us that it wants to abort the
@@ -895,48 +903,53 @@ class JavaPanZoomController
              * out.
              */
             if (!(mState == PanZoomState.BOUNCE || mState == PanZoomState.ANIMATED_ZOOM)) {
                 finishAnimation();
                 return;
             }
 
             /* Perform the next frame of the bounce-back animation. */
-            if (mBounceFrame < (int)(BOUNCE_ANIMATION_DURATION / Axis.MS_PER_FRAME)) {
+            if (mBounceFrame < BOUNCE_FRAME_NUMBER) {
                 advanceBounce();
                 return;
             }
 
             /* Finally, if there's nothing else to do, complete the animation and go to sleep. */
             finishBounce();
             finishAnimation();
             setState(PanZoomState.NOTHING);
         }
 
         /* Performs one frame of a bounce animation. */
         private void advanceBounce() {
             synchronized (mTarget.getLock()) {
-                float t = easeOut(mBounceFrame * Axis.MS_PER_FRAME / BOUNCE_ANIMATION_DURATION);
+                float t = easeOut(((float)mBounceFrame) / BOUNCE_FRAME_NUMBER);
                 ImmutableViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t);
                 mTarget.setViewportMetrics(newMetrics);
                 mBounceFrame++;
             }
         }
 
         /* Concludes a bounce animation and snaps the viewport into place. */
         private void finishBounce() {
             synchronized (mTarget.getLock()) {
                 mTarget.setViewportMetrics(mBounceEndMetrics);
                 mBounceFrame = -1;
             }
         }
     }
 
     // The callback that performs the fling animation.
-    private class FlingRunnable extends AnimationRunnable {
+    private class FlingRenderTask extends PanZoomRenderTask {
+
+        public FlingRenderTask() {
+            super();
+        }
+
         @Override
         protected void animateFrame() {
             /*
              * The pan/zoom controller might have signaled to us that it wants to abort the
              * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail
              * out.
              */
             if (mState != PanZoomState.FLING) {
@@ -978,17 +991,17 @@ class JavaPanZoomController
                 setState(PanZoomState.NOTHING);
             }
         }
     }
 
     private void finishAnimation() {
         checkMainThread();
 
-        stopAnimationTimer();
+        stopAnimationTask();
 
         // Force a viewport synchronisation
         mTarget.forceRedraw(null);
     }
 
     /* Returns the nearest viewport metrics with no overscroll visible. */
     private ImmutableViewportMetrics getValidViewportMetrics() {
         return getValidViewportMetrics(getMetrics());
--- a/mobile/android/base/gfx/PanZoomTarget.java
+++ b/mobile/android/base/gfx/PanZoomTarget.java
@@ -21,11 +21,13 @@ public interface PanZoomTarget {
     public void scrollBy(float dx, float dy);
     public void onSubdocumentScrollBy(float dx, float dy);
     public void panZoomStopped();
     /** This triggers an (asynchronous) viewport update/redraw. */
     public void forceRedraw(DisplayPortMetrics displayPort);
 
     public boolean post(Runnable action);
     public boolean postDelayed(Runnable action, long delayMillis);
+    public void postRenderTask(RenderTask task);
+    public void removeRenderTask(RenderTask task);
     public Object getLock();
     public PointF convertViewPointToLayerPoint(PointF viewPoint);
 }