Bug 715164 - Guard against another race condition in PZC. r=pcwalton
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 05 Jan 2012 18:39:45 -0800
changeset 86311 c7e27452a143c834a4d1d7acf8c202261504210c
parent 86310 8b0437df0da3682206e3a37a43b2d49c92618442
child 86312 8ae16e346bd0c2c93711884b2a2e5db10060512d
push idunknown
push userunknown
push dateunknown
reviewerspcwalton
bugs715164
milestone12.0a1
Bug 715164 - Guard against another race condition in PZC. r=pcwalton
mobile/android/base/ui/PanZoomController.java
--- a/mobile/android/base/ui/PanZoomController.java
+++ b/mobile/android/base/ui/PanZoomController.java
@@ -120,16 +120,18 @@ public class PanZoomController
         0.90651f,   /* 12 */
         0.94471f,   /* 13 */
         0.97401f,   /* 14 */
         0.99309f,   /* 15 */
     };
 
     /* The timer that handles flings or bounces. */
     private Timer mAnimationTimer;
+    /* The runnable being scheduled by the animation timer. */
+    private AnimationRunnable mAnimationRunnable;
     /* Information about the X axis. */
     private AxisX mX;
     /* Information about the Y axis. */
     private AxisY mY;
     /* 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;
@@ -526,35 +528,40 @@ public class PanZoomController
     }
 
     /* Performs a bounce-back animation to the nearest valid viewport metrics. */
     private void bounce() {
         bounce(getValidViewportMetrics());
     }
 
     /* Starts the fling or bounce animation. */
-    private void startAnimationTimer(final Runnable runnable) {
+    private void startAnimationTimer(final AnimationRunnable runnable) {
         if (mAnimationTimer != null) {
             Log.e(LOGTAG, "Attempted to start a new fling without canceling the old one!");
             stopAnimationTimer();
         }
 
         mAnimationTimer = new Timer("Animation Timer");
+        mAnimationRunnable = runnable;
         mAnimationTimer.scheduleAtFixedRate(new TimerTask() {
             @Override
             public void run() { mController.post(runnable); }
         }, 0, 1000L/60L);
     }
 
     /* Stops the fling or bounce animation. */
     private void stopAnimationTimer() {
         if (mAnimationTimer != null) {
             mAnimationTimer.cancel();
             mAnimationTimer = null;
         }
+        if (mAnimationRunnable != null) {
+            mAnimationRunnable.terminate();
+            mAnimationRunnable = null;
+        }
     }
 
     private boolean stopped() {
         float absVelocity = (float)Math.sqrt(mX.velocity * mX.velocity +
                                              mY.velocity * mY.velocity);
         return absVelocity < STOPPED_THRESHOLD;
     }
 
@@ -582,33 +589,58 @@ public class PanZoomController
             synchronized (mController) {
                 mController.scrollBy(new PointF(mX.displacement, mY.displacement));
             }
         }
 
         mX.displacement = mY.displacement = 0;
     }
 
+    private abstract class AnimationRunnable implements Runnable {
+        private boolean mAnimationTerminated;
+
+        /* This should always run on the UI thread */
+        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;
+            }
+            animateFrame();
+        }
+
+        protected abstract void animateFrame();
+
+        /* This should always run on the UI thread */
+        protected final void terminate() {
+            mAnimationTerminated = true;
+        }
+    }
+
     /* The callback that performs the bounce animation. */
-    private class BounceRunnable implements Runnable {
+    private class BounceRunnable extends AnimationRunnable {
         /* The current frame of the bounce-back animation */
         private int mBounceFrame;
         /*
          * The viewport metrics that represent the start and end of the bounce-back animation,
          * respectively.
          */
         private ViewportMetrics mBounceStartMetrics;
         private ViewportMetrics mBounceEndMetrics;
 
         BounceRunnable(ViewportMetrics startMetrics, ViewportMetrics endMetrics) {
             mBounceStartMetrics = startMetrics;
             mBounceEndMetrics = endMetrics;
         }
 
-        public void run() {
+        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) {
                 finishAnimation();
                 return;
@@ -643,18 +675,18 @@ public class PanZoomController
                 mController.setViewportMetrics(mBounceEndMetrics);
                 mController.notifyLayerClientOfGeometryChange();
                 mBounceFrame = -1;
             }
         }
     }
 
     // The callback that performs the fling animation.
-    private class FlingRunnable implements Runnable {
-        public void run() {
+    private class FlingRunnable extends AnimationRunnable {
+        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) {
                 finishAnimation();
                 return;