[Libreoffice-commits] core.git: 14 commits - android/Bootstrap android/experimental desktop/source

Tomaž Vajngerl tomaz.vajngerl at collabora.com
Wed Sep 24 11:42:18 PDT 2014


 android/Bootstrap/src/org/libreoffice/kit/DirectBufferAllocator.java                      |   52 
 android/Bootstrap/src/org/libreoffice/kit/LibreOfficeKit.java                             |    7 
 android/experimental/LOAndroid3/src/java/org/libreoffice/DirectBufferAllocator.java       |   33 
 android/experimental/LOAndroid3/src/java/org/libreoffice/LOEvent.java                     |    6 
 android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java                 |   16 
 android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java     |   26 
 android/experimental/LOAndroid3/src/java/org/libreoffice/TileProviderFactory.java         |   31 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/BufferedCairoImage.java    |    2 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/CheckerboardImage.java     |    2 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DisplayPortCalculator.java |  755 ++++++++++
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DisplayPortMetrics.java    |   58 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DrawTimingQueue.java       |   95 +
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/FlexibleGLSurfaceView.java |  196 --
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GLController.java          |   77 -
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GLThread.java              |    3 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java      |  263 +--
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java       |  316 +---
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java         |  223 +-
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java             |  247 ++-
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java        |    9 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScreenshotLayer.java       |    2 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScrollbarLayer.java        |    2 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TextLayer.java             |    2 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TileLayer.java             |   11 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TouchEventHandler.java     |  325 ++++
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java      |  294 ++-
 desktop/source/lib/lokandroid.cxx                                                         |   28 
 27 files changed, 2153 insertions(+), 928 deletions(-)

New commits:
commit 0ab9ddaf4d67e968ad22ec296fbeda02ce1a468d
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Wed Sep 24 20:41:54 2014 +0200

    android: LayerRenderer - use highp and flip in vertex shader
    
    Change-Id: Ia517b0d94fdfb3f8fdd9b9c383c8fb337173932c

diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java
index 5146b22..34c5c61 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java
@@ -99,6 +99,11 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
 
     // The shaders run on the GPU directly, the vertex shader is only applying the
     // matrix transform detailed above
+
+    // Note we flip the y-coordinate in the vertex shader from a
+    // coordinate system with (0,0) in the top left to one with (0,0) in
+    // the bottom left.
+
     public static final String DEFAULT_VERTEX_SHADER =
         "uniform mat4 uTMatrix;\n" +
         "attribute vec4 vPosition;\n" +
@@ -106,18 +111,21 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
         "varying vec2 vTexCoord;\n" +
         "void main() {\n" +
         "    gl_Position = uTMatrix * vPosition;\n" +
-        "    vTexCoord = aTexCoord;\n" +
+        "    vTexCoord.x = aTexCoord.x;\n" +
+        "    vTexCoord.y = 1.0 - aTexCoord.y;\n" +
         "}\n";
 
-    // Note we flip the y-coordinate in the fragment shader from a
-    // coordinate system with (0,0) in the top left to one with (0,0) in
-    // the bottom left.
+    // We use highp because the screenshot textures
+    // we use are large and we stretch them alot
+    // so we need all the precision we can get.
+    // Unfortunately, highp is not required by ES 2.0
+    // so on GPU's like Mali we end up getting mediump
     public static final String DEFAULT_FRAGMENT_SHADER =
-        "precision mediump float;\n" +
+        "precision highp float;\n" +
         "varying vec2 vTexCoord;\n" +
         "uniform sampler2D sTexture;\n" +
         "void main() {\n" +
-        "    gl_FragColor = texture2D(sTexture, vec2(vTexCoord.x, 1.0 - vTexCoord.y));\n" +
+        "    gl_FragColor = texture2D(sTexture, vTexCoord);\n" +
         "}\n";
 
     public void setCheckerboardBitmap(Bitmap bitmap, float pageWidth, float pageHeight) {
commit f789cd425e7808cca01058b0843ed0cda045bcb6
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Wed Sep 24 20:38:13 2014 +0200

    android: define mRootLayer directly as MultiTileLayer
    
    Change-Id: I6a1f6971482ea1de28977d7905ba9fd85921551d

diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
index e43a308..4f94e17 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
@@ -67,7 +67,7 @@ public class GeckoLayerClient implements LayerView.Listener {
     private boolean mRecordDrawTimes;
     private DrawTimingQueue mDrawTimingQueue;
 
-    private Layer mRootLayer;
+    private MultiTileLayer mRootLayer;
 
     /* The viewport that Gecko is currently displaying. */
     private ViewportMetrics mGeckoViewport;
@@ -129,9 +129,7 @@ public class GeckoLayerClient implements LayerView.Listener {
     }
 
     protected void updateLayerAfterDraw() {
-        if (mRootLayer instanceof MultiTileLayer) {
-            ((MultiTileLayer) mRootLayer).invalidate();
-        }
+        mRootLayer.invalidate();
     }
 
     public void beginDrawing(ViewportMetrics viewportMetrics) {
@@ -250,7 +248,7 @@ public class GeckoLayerClient implements LayerView.Listener {
         synchronized (mLayerController) {
         // adjust the page dimensions to account for differences in zoom
         // between the rendered content (which is what the compositor tells us)
-    // and our zoom level (which may have diverged).
+        // and our zoom level (which may have diverged).
         float ourZoom = mLayerController.getZoomFactor();
         pageWidth = pageWidth * ourZoom / zoom;
         pageHeight = pageHeight * ourZoom /zoom;
@@ -273,16 +271,11 @@ public class GeckoLayerClient implements LayerView.Listener {
     }
 
     public List<SubTile> getTiles() {
-        if (mRootLayer instanceof MultiTileLayer) {
-            return ((MultiTileLayer) mRootLayer).getTiles();
-        }
-        return null;
+        return mRootLayer.getTiles();
     }
 
     public void addTile(SubTile tile) {
-        if (mRootLayer instanceof MultiTileLayer) {
-            ((MultiTileLayer) mRootLayer).addTile(tile);
-        }
+        mRootLayer.addTile(tile);
     }
 
     @Override
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java
index 469d7f5..e60e89d 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java
@@ -42,7 +42,6 @@ import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
-import android.util.Log;
 
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -95,8 +94,6 @@ public class MultiTileLayer extends Layer {
     }
 
     private void validateTiles() {
-        Log.i(LOGTAG, "validateTiles()");
-
         // Set tile origins and resolution
         Point origin = new Point();
         refreshTileMetrics(origin, getResolution(), false);
@@ -120,8 +117,9 @@ public class MultiTileLayer extends Layer {
             RectF layerBounds = layer.getBounds(context);
 
             if (!RectF.intersects(layerBounds, context.viewport)) {
-                if (firstDirtyTile == null)
+                if (firstDirtyTile == null) {
                     firstDirtyTile = layer;
+                }
                 dirtyTiles++;
             } else {
                 // This tile intersects with the screen and is dirty,
@@ -196,8 +194,9 @@ public class MultiTileLayer extends Layer {
         for (SubTile layer : mTiles) {
             // Avoid work, only draw tiles that intersect with the viewport
             RectF layerBounds = layer.getBounds(context);
-            if (RectF.intersects(layerBounds, context.viewport))
+            if (RectF.intersects(layerBounds, context.viewport)) {
                 layer.draw(context);
+            }
         }
     }
 
commit df433a70cd2fe564a4d046a0bbb1e90292978184
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Wed Sep 24 15:19:58 2014 +0200

    android: upgrade PanZoomController - add configurable zoom limits
    
    Change-Id: I19815f58af4d060cffe515829a2a5472d32bf83c

diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java
index 35a320d..810ff26 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java
@@ -118,6 +118,7 @@ public class LibreOfficeMainActivity extends Activity {
         }
 
         mLayerController = new LayerController(this);
+        mLayerController.setAllowZoom(true);
         mLayerClient = new GeckoLayerClient(this);
         mLayerController.setLayerClient(mLayerClient);
         mGeckoLayout.addView(mLayerController.getView(), 0);
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DisplayPortCalculator.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DisplayPortCalculator.java
index a255974..88507e5 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DisplayPortCalculator.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DisplayPortCalculator.java
@@ -36,7 +36,7 @@ final class DisplayPortCalculator {
     private static final String PREF_DISPLAYPORT_VB_DANGER_Y_INCR = "gfx.displayport.strategy_vb.danger_y_incr";
     private static final String PREF_DISPLAYPORT_PB_VELOCITY_THRESHOLD = "gfx.displayport.strategy_pb.threshold";
 
-    private static DisplayPortStrategy sStrategy = new NoMarginStrategy(null);
+    private static DisplayPortStrategy sStrategy = new DynamicResolutionStrategy(null);
 
     static DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
         return sStrategy.calculate(metrics, (velocity == null ? ZERO_VELOCITY : velocity));
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
index ce047ee..e43a308 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
@@ -172,7 +172,7 @@ public class GeckoLayerClient implements LayerView.Listener {
             // Don't adjust page size when zooming unless zoom levels are
             // approximately equal.
             if (FloatUtils.fuzzyEquals(mLayerController.getZoomFactor(), mGeckoViewport.getZoomFactor())) {
-                mLayerController.setPageSize(mGeckoViewport.getPageSize());
+                mLayerController.setPageSize(mGeckoViewport.getPageSize(), mGeckoViewport.getPageSize());
             }
         } else {
             mLayerController.setViewportMetrics(mGeckoViewport);
@@ -254,7 +254,7 @@ public class GeckoLayerClient implements LayerView.Listener {
         float ourZoom = mLayerController.getZoomFactor();
         pageWidth = pageWidth * ourZoom / zoom;
         pageHeight = pageHeight * ourZoom /zoom;
-        mLayerController.setPageSize(new FloatSize(pageWidth, pageHeight));
+        mLayerController.setPageSize(new FloatSize(pageWidth, pageHeight), new FloatSize(pageWidth, pageHeight));
         // Here the page size of the document has changed, but the document being displayed
         // is still the same. Therefore, we don't need to send anything to browser.js; any
         // changes we need to make to the display port will get sent the next time we call
@@ -296,13 +296,13 @@ public class GeckoLayerClient implements LayerView.Listener {
     }
 
     @Override
-    public void compositionResumeRequested() {
+    public void compositionResumeRequested(int width, int height) {
 
     }
 
     @Override
     public void surfaceChanged(int width, int height) {
-        compositionResumeRequested();
+        compositionResumeRequested(width, height);
         renderRequested();
     }
 
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java
index 01559a1..277ed42 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java
@@ -1,40 +1,7 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Android code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2009-2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Patrick Walton <pcwalton at mozilla.com>
- *   Chris Lord <chrislord.net at gmail.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
@@ -42,6 +9,7 @@ import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.Color;
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.view.GestureDetector;
@@ -49,8 +17,6 @@ import android.view.GestureDetector;
 import org.mozilla.gecko.ui.PanZoomController;
 import org.mozilla.gecko.ui.SimpleScaleGestureDetector;
 
-import java.util.regex.Pattern;
-
 /**
  * The layer controller manages a tile that represents the visible page. It does panning and
  * zooming natively by delegating to a panning/zooming controller. Touch events can be dispatched
@@ -87,12 +53,15 @@ public class LayerController {
     private GeckoLayerClient mLayerClient;          /* The layer client. */
 
     /* The new color for the checkerboard. */
-    private int mCheckerboardColor;
+    private int mCheckerboardColor = Color.WHITE;
     private boolean mCheckerboardShouldShowChecks;
 
-    private boolean mForceRedraw;
+    private boolean mAllowZoom;
+    private float mDefaultZoom;
+    private float mMinZoom;
+    private float mMaxZoom;
 
-    private static Pattern sColorPattern;
+    private boolean mForceRedraw;
 
     public LayerController(Context context) {
         mContext = context;
@@ -124,6 +93,10 @@ public class LayerController {
         return mViewportMetrics.getViewport();
     }
 
+    public RectF getCssViewport() {
+        return mViewportMetrics.getCssViewport();
+    }
+
     public FloatSize getViewportSize() {
         return mViewportMetrics.getSize();
     }
@@ -132,6 +105,10 @@ public class LayerController {
         return mViewportMetrics.getPageSize();
     }
 
+    public FloatSize getCssPageSize() {
+        return mViewportMetrics.getCssPageSize();
+    }
+
     public PointF getOrigin() {
         return mViewportMetrics.getOrigin();
     }
@@ -189,12 +166,12 @@ public class LayerController {
     }
 
     /** Sets the current page size. You must hold the monitor while calling this. */
-    public void setPageSize(FloatSize size) {
-        if (mViewportMetrics.getPageSize().fuzzyEquals(size))
+    public void setPageSize(FloatSize size, FloatSize cssSize) {
+        if (mViewportMetrics.getCssPageSize().equals(cssSize))
             return;
 
         ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
-        viewportMetrics.setPageSize(size, size);
+        viewportMetrics.setPageSize(size, cssSize);
         mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
 
         // Page size is owned by the layer client, so no need to notify it of
@@ -294,8 +271,9 @@ public class LayerController {
      * correct.
      */
     public PointF convertViewPointToLayerPoint(PointF viewPoint) {
-        if (mRootLayer == null)
+        if (mLayerClient == null) {
             return null;
+        }
 
         ImmutableViewportMetrics viewportMetrics = mViewportMetrics;
         PointF origin = viewportMetrics.getOrigin();
@@ -337,5 +315,41 @@ public class LayerController {
         mCheckerboardColor = newColor;
         mView.requestRender();
     }
-}
 
+    public void setAllowZoom(final boolean aValue) {
+        mAllowZoom = aValue;
+        mView.post(new Runnable() {
+            public void run() {
+                mView.getTouchEventHandler().setDoubleTapEnabled(aValue);
+            }
+        });
+    }
+
+    public boolean getAllowZoom() {
+        return mAllowZoom;
+    }
+
+    public void setDefaultZoom(float aValue) {
+        mDefaultZoom = aValue;
+    }
+
+    public float getDefaultZoom() {
+        return mDefaultZoom;
+    }
+
+    public void setMinZoom(float aValue) {
+        mMinZoom = aValue;
+    }
+
+    public float getMinZoom() {
+        return mMinZoom;
+    }
+
+    public void setMaxZoom(float aValue) {
+        mMaxZoom = aValue;
+    }
+
+    public float getMaxZoom() {
+        return mMaxZoom;
+    }
+}
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java
index f981667..9c6a616 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java
@@ -1,40 +1,7 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Android code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2009-2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Patrick Walton <pcwalton at mozilla.com>
- *   Arkady Blyakher <rkadyb at mit.edu>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
@@ -42,7 +9,6 @@ package org.mozilla.gecko.gfx;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.PixelFormat;
-import android.opengl.GLSurfaceView;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -227,7 +193,7 @@ public class LayerView extends SurfaceView implements SurfaceHolder.Callback {
     }
 
 
-    public GLSurfaceView.Renderer getRenderer() {
+    public LayerRenderer getRenderer() {
         return mRenderer;
     }
 
@@ -235,7 +201,7 @@ public class LayerView extends SurfaceView implements SurfaceHolder.Callback {
         mListener = listener;
     }
 
-    public synchronized GLController getGLController() {
+    public GLController getGLController() {
         return mGLController;
     }
 
@@ -288,7 +254,7 @@ public class LayerView extends SurfaceView implements SurfaceHolder.Callback {
     public interface Listener {
         void renderRequested();
         void compositionPauseRequested();
-        void compositionResumeRequested();
+        void compositionResumeRequested(int width, int height);
         void surfaceChanged(int width, int height);
     }
 
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TouchEventHandler.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TouchEventHandler.java
index d972ef3..78a141e 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TouchEventHandler.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/TouchEventHandler.java
@@ -10,8 +10,8 @@ import android.os.SystemClock;
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 import android.view.View.OnTouchListener;
-import android.view.ViewConfiguration;
 
+import org.mozilla.gecko.ui.PanZoomController;
 import org.mozilla.gecko.ui.SimpleScaleGestureDetector;
 
 import java.util.LinkedList;
@@ -41,18 +41,24 @@ import java.util.Queue;
  * at some point after the first or second event in the block is processed in Gecko.
  * This code assumes we get EXACTLY ONE default-prevented notification for each block
  * of events.
+ *
+ * Note that even if all events are default-prevented, we still send specific types
+ * of notifications to the pan/zoom controller. The notifications are needed
+ * to respond to user actions a timely manner regardless of default-prevention,
+ * and fix issues like bug 749384.
  */
 public final class TouchEventHandler {
     private static final String LOGTAG = "GeckoTouchEventHandler";
 
     // The time limit for listeners to respond with preventDefault on touchevents
     // before we begin panning the page
-    private final int EVENT_LISTENER_TIMEOUT = ViewConfiguration.getLongPressTimeout();
+    private final int EVENT_LISTENER_TIMEOUT = 200;
 
     private final LayerView mView;
-    private final LayerController mController;
     private final GestureDetector mGestureDetector;
     private final SimpleScaleGestureDetector mScaleGestureDetector;
+    private final PanZoomController mPanZoomController;
+    private final GestureDetector.OnDoubleTapListener mDoubleTapListener;
 
     // the queue of events that we are holding on to while waiting for a preventDefault
     // notification
@@ -119,15 +125,16 @@ public final class TouchEventHandler {
 
     TouchEventHandler(Context context, LayerView view, LayerController controller) {
         mView = view;
-        mController = controller;
 
         mEventQueue = new LinkedList<MotionEvent>();
         mGestureDetector = new GestureDetector(context, controller.getGestureListener());
         mScaleGestureDetector = new SimpleScaleGestureDetector(controller.getScaleGestureListener());
+        mPanZoomController = controller.getPanZoomController();
         mListenerTimeoutProcessor = new ListenerTimeoutProcessor();
         mDispatchEvents = true;
 
-        mGestureDetector.setOnDoubleTapListener(controller.getDoubleTapListener());
+        mDoubleTapListener = controller.getDoubleTapListener();
+        setDoubleTapEnabled(true);
     }
 
     /* This function MUST be called on the UI thread */
@@ -142,7 +149,18 @@ public final class TouchEventHandler {
         if (isDownEvent(event)) {
             // this is the start of a new block of events! whee!
             mHoldInQueue = mWaitForTouchListeners;
+
+            // Set mDispatchEvents to true so that we are guaranteed to either queue these
+            // events or dispatch them. The only time we should not do either is once we've
+            // heard back from content to preventDefault this block.
+            mDispatchEvents = true;
             if (mHoldInQueue) {
+                // if the new block we are starting is the current block (i.e. there are no
+                // other blocks waiting in the queue, then we should let the pan/zoom controller
+                // know we are waiting for the touch listeners to run
+                if (mEventQueue.isEmpty()) {
+                    mPanZoomController.waitingForTouchListeners(event);
+                }
                 // if we're holding the events in the queue, set the timeout so that
                 // we dispatch these events if we don't get a default-prevented notification
                 mView.postDelayed(mListenerTimeoutProcessor, EVENT_LISTENER_TIMEOUT);
@@ -164,6 +182,8 @@ public final class TouchEventHandler {
             mEventQueue.add(MotionEvent.obtain(event));
         } else if (mDispatchEvents) {
             dispatchEvent(event);
+        } else if (touchFinished(event)) {
+            mPanZoomController.preventedTouchFinished();
         }
 
         // notify gecko of the event
@@ -193,6 +213,11 @@ public final class TouchEventHandler {
     }
 
     /* This function MUST be called on the UI thread. */
+    public void setDoubleTapEnabled(boolean aValue) {
+        mGestureDetector.setOnDoubleTapListener(aValue ? mDoubleTapListener : null);
+    }
+
+    /* This function MUST be called on the UI thread. */
     public void setWaitForTouchListeners(boolean aValue) {
         mWaitForTouchListeners = aValue;
     }
@@ -207,18 +232,32 @@ public final class TouchEventHandler {
         return (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN);
     }
 
+    private boolean touchFinished(MotionEvent event) {
+        int action = (event.getAction() & MotionEvent.ACTION_MASK);
+        return (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL);
+    }
+
     /**
      * Dispatch the event to the gesture detectors and the pan/zoom controller.
      */
     private void dispatchEvent(MotionEvent event) {
         if (mGestureDetector.onTouchEvent(event)) {
-            return;
+            // An up/cancel event should get passed to both detectors, in
+            // case it comes from a pointer the scale detector is tracking.
+            switch (event.getAction() & MotionEvent.ACTION_MASK) {
+                case MotionEvent.ACTION_POINTER_UP:
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    break;
+                default:
+                    return;
+            }
         }
         mScaleGestureDetector.onTouchEvent(event);
         if (mScaleGestureDetector.isInProgress()) {
             return;
         }
-        mController.getPanZoomController().onTouchEvent(event);
+        mPanZoomController.onTouchEvent(event);
     }
 
     /**
@@ -244,6 +283,8 @@ public final class TouchEventHandler {
             // default-prevented.
             if (allowDefaultAction) {
                 dispatchEvent(event);
+            } else if (touchFinished(event)) {
+                mPanZoomController.preventedTouchFinished();
             }
             event = mEventQueue.peek();
             if (event == null) {
@@ -259,6 +300,7 @@ public final class TouchEventHandler {
             if (isDownEvent(event)) {
                 // we have finished processing the block we were interested in.
                 // now we wait for the next call to processEventBlock
+                mPanZoomController.waitingForTouchListeners(event);
                 break;
             }
             // pop the event we peeked above, as it is still part of the block and
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java
index acded9c..8f81b5d 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java
@@ -1,40 +1,7 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Android code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2009-2012
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Patrick Walton <pcwalton at mozilla.com>
- *   Kartikaya Gupta <kgupta at mozilla.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.ui;
 
@@ -52,6 +19,8 @@ import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.ViewportMetrics;
 import org.mozilla.gecko.util.FloatUtils;
 
+import java.util.Arrays;
+import java.util.StringTokenizer;
 import java.util.Timer;
 import java.util.TimerTask;
 
@@ -85,7 +54,7 @@ public class PanZoomController
     private static final float MAX_ZOOM = 4.0f;
 
     /* 16 precomputed frames of the _ease-out_ animation from the CSS Transitions specification. */
-    private static final float[] EASE_OUT_ANIMATION_FRAMES = {
+    private static float[] ZOOM_ANIMATION_FRAMES = new float[] {
         0.00000f,   /* 0 */
         0.10211f,   /* 1 */
         0.19864f,   /* 2 */
@@ -115,7 +84,11 @@ public class PanZoomController
         PANNING_HOLD_LOCKED, /* like PANNING_HOLD, but axis lock still in effect */
         PINCHING,       /* nth touch-start, where n > 1. this mode allows pan and zoom */
         ANIMATED_ZOOM,  /* animated zoom to a new rect */
-        BOUNCE          /* in a bounce animation */
+        BOUNCE,         /* in a bounce animation */
+
+        WAITING_LISTENERS, /* a state halfway between NOTHING and TOUCHING - the user has
+                        put a finger down, but we don't yet know if a touch listener has
+                        prevented the default actions yet. we still need to abort animations. */
     }
 
     private final LayerController mController;
@@ -156,6 +129,23 @@ public class PanZoomController
         }
     }
 
+    private void setZoomAnimationFrames(String frames) {
+        try {
+            if (frames.length() > 0) {
+                StringTokenizer st = new StringTokenizer(frames, ",");
+                float[] values = new float[st.countTokens()];
+                for (int i = 0; i < values.length; i++) {
+                    values[i] = Float.parseFloat(st.nextToken());
+                }
+                ZOOM_ANIMATION_FRAMES = values;
+            }
+        } catch (NumberFormatException e) {
+            Log.e(LOGTAG, "Error setting zoom animation frames", e);
+        } finally {
+            Log.i(LOGTAG, "Zoom animation frames: " + Arrays.toString(ZOOM_ANIMATION_FRAMES));
+        }
+    }
+
     public boolean onTouchEvent(MotionEvent event) {
         switch (event.getAction() & MotionEvent.ACTION_MASK) {
         case MotionEvent.ACTION_DOWN:   return onTouchStart(event);
@@ -195,6 +185,30 @@ public class PanZoomController
         }
     }
 
+    /** This function must be called on the UI thread. */
+    public void waitingForTouchListeners(MotionEvent event) {
+        checkMainThread();
+        if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
+            // this is the first touch point going down, so we enter the pending state
+            mSubscroller.cancel();
+            // seting the state will kill any animations in progress, possibly leaving
+            // the page in overscroll
+            mState = PanZoomState.WAITING_LISTENERS;
+        }
+    }
+
+    /** This function must be called on the UI thread. */
+    public void preventedTouchFinished() {
+        checkMainThread();
+        if (mState == PanZoomState.WAITING_LISTENERS) {
+            // if we enter here, we just finished a block of events whose default actions
+            // were prevented by touch listeners. Now there are no touch points left, so
+            // we need to reset our state and re-bounce because we might be in overscroll
+            mState = PanZoomState.NOTHING;
+            bounce();
+        }
+    }
+
     /** This must be called on the UI thread. */
     public void pageSizeUpdated() {
         if (mState == PanZoomState.NOTHING) {
@@ -222,10 +236,16 @@ public class PanZoomController
 
         switch (mState) {
         case ANIMATED_ZOOM:
-            return false;
+            // 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
+            mController.setForceRedraw();
+            mController.notifyLayerClientOfGeometryChange();
+            // fall through
         case FLING:
         case BOUNCE:
         case NOTHING:
+        case WAITING_LISTENERS:
             startTouch(event.getX(0), event.getY(0), event.getEventTime());
             return false;
         case TOUCHING:
@@ -244,11 +264,16 @@ public class PanZoomController
     private boolean onTouchMove(MotionEvent event) {
 
         switch (mState) {
-        case NOTHING:
         case FLING:
         case BOUNCE:
+        case WAITING_LISTENERS:
             // should never happen
             Log.e(LOGTAG, "Received impossible touch move while in " + mState);
+            // fall through
+        case ANIMATED_ZOOM:
+        case NOTHING:
+            // may happen if user double-taps and drags without lifting after the
+            // second tap. ignore the move if this happens.
             return false;
 
         case TOUCHING:
@@ -274,7 +299,6 @@ public class PanZoomController
             track(event);
             return true;
 
-        case ANIMATED_ZOOM:
         case PINCHING:
             // scale gesture listener will handle this
             return false;
@@ -286,12 +310,18 @@ public class PanZoomController
     private boolean onTouchEnd(MotionEvent event) {
 
         switch (mState) {
-        case NOTHING:
         case FLING:
         case BOUNCE:
+        case WAITING_LISTENERS:
             // should never happen
             Log.e(LOGTAG, "Received impossible touch end while in " + mState);
+            // fall through
+        case ANIMATED_ZOOM:
+        case NOTHING:
+            // may happen if user double-taps and drags without lifting after the
+            // second tap. ignore if this happens.
             return false;
+
         case TOUCHING:
             mState = PanZoomState.NOTHING;
             // the switch into TOUCHING might have happened while the page was
@@ -299,6 +329,7 @@ public class PanZoomController
             // was the case
             bounce();
             return false;
+
         case PANNING:
         case PANNING_LOCKED:
         case PANNING_HOLD:
@@ -306,19 +337,28 @@ public class PanZoomController
             mState = PanZoomState.FLING;
             fling();
             return true;
+
         case PINCHING:
             mState = PanZoomState.NOTHING;
             return true;
-        case ANIMATED_ZOOM:
-            return false;
         }
         Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchEnd");
         return false;
     }
 
     private boolean onTouchCancel(MotionEvent event) {
-        mState = PanZoomState.NOTHING;
+        if (mState == PanZoomState.WAITING_LISTENERS) {
+            // we might get a cancel event from the TouchEventHandler while in the
+            // WAITING_LISTENERS state if the touch listeners prevent-default the
+            // block of events. at this point being in WAITING_LISTENERS is equivalent
+            // to being in NOTHING with the exception of possibly being in overscroll.
+            // so here we don't want to do anything right now; the overscroll will be
+            // corrected in preventedTouchFinished().
+            return false;
+        }
+
         cancelTouch();
+        mState = PanZoomState.NOTHING;
         // ensure we snap back if we're overscrolled
         bounce();
         return false;
@@ -423,16 +463,17 @@ public class PanZoomController
             return;
         }
 
-        mState = PanZoomState.BOUNCE;
-        // set the animation target *after* setting state BOUNCE, so that
-        // the getRedrawHint() is returning false and we don't clobber the display
-        // port we set as a result of this animation target call.
+        // 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.
         mController.setAnimationTarget(metrics);
         startAnimationTimer(new BounceRunnable(bounceStartMetrics, metrics));
     }
 
     /* Performs a bounce-back animation to the nearest valid viewport metrics. */
     private void bounce() {
+        mState = PanZoomState.BOUNCE;
         bounce(getValidViewportMetrics());
     }
 
@@ -477,14 +518,14 @@ public class PanZoomController
         return getVelocity() < STOPPED_THRESHOLD;
     }
 
-    PointF getDisplacement() {
+    PointF resetDisplacement() {
         return new PointF(mX.resetDisplacement(), mY.resetDisplacement());
     }
 
     private void updatePosition() {
         mX.displace();
         mY.displace();
-        PointF displacement = getDisplacement();
+        PointF displacement = resetDisplacement();
         if (FloatUtils.fuzzyEquals(displacement.x, 0.0f) && FloatUtils.fuzzyEquals(displacement.y, 0.0f)) {
             return;
         }
@@ -542,13 +583,13 @@ public class PanZoomController
              * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail
              * out.
              */
-            if (mState != PanZoomState.BOUNCE) {
+            if (!(mState == PanZoomState.BOUNCE || mState == PanZoomState.ANIMATED_ZOOM)) {
                 finishAnimation();
                 return;
             }
 
             /* Perform the next frame of the bounce-back animation. */
-            if (mBounceFrame < EASE_OUT_ANIMATION_FRAMES.length) {
+            if (mBounceFrame < ZOOM_ANIMATION_FRAMES.length) {
                 advanceBounce();
                 return;
             }
@@ -562,7 +603,7 @@ public class PanZoomController
         /* Performs one frame of a bounce animation. */
         private void advanceBounce() {
             synchronized (mController) {
-                float t = EASE_OUT_ANIMATION_FRAMES[mBounceFrame];
+                float t = ZOOM_ANIMATION_FRAMES[mBounceFrame];
                 ViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t);
                 mController.setViewportMetrics(newMetrics);
                 mController.notifyLayerClientOfGeometryChange();
@@ -655,19 +696,37 @@ public class PanZoomController
 
         float focusX = viewport.width() / 2.0f;
         float focusY = viewport.height() / 2.0f;
+
         float minZoomFactor = 0.0f;
-        if (viewport.width() > pageSize.width && pageSize.width > 0) {
+        float maxZoomFactor = MAX_ZOOM;
+
+        if (mController.getMinZoom() > 0)
+            minZoomFactor = mController.getMinZoom();
+        if (mController.getMaxZoom() > 0)
+            maxZoomFactor = mController.getMaxZoom();
+
+        if (!mController.getAllowZoom()) {
+            // If allowZoom is false, clamp to the default zoom level.
+            maxZoomFactor = minZoomFactor = mController.getDefaultZoom();
+        }
+
+        // Ensure minZoomFactor keeps the page at least as big as the viewport.
+        if (pageSize.width > 0) {
             float scaleFactor = viewport.width() / pageSize.width;
             minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor);
-            focusX = 0.0f;
+            if (viewport.width() > pageSize.width)
+                focusX = 0.0f;
         }
-        if (viewport.height() > pageSize.height && pageSize.height > 0) {
+        if (pageSize.height > 0) {
             float scaleFactor = viewport.height() / pageSize.height;
             minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor);
-            focusY = 0.0f;
+            if (viewport.height() > pageSize.height)
+                focusY = 0.0f;
         }
 
-        if (!FloatUtils.fuzzyEquals(minZoomFactor, 0.0f)) {
+        maxZoomFactor = Math.max(maxZoomFactor, minZoomFactor);
+
+        if (zoomFactor < minZoomFactor) {
             // if one (or both) of the page dimensions is smaller than the viewport,
             // zoom using the top/left as the focus on that axis. this prevents the
             // scenario where, if both dimensions are smaller than the viewport, but
@@ -675,9 +734,9 @@ public class PanZoomController
             // after applying the scale
             PointF center = new PointF(focusX, focusY);
             viewportMetrics.scaleTo(minZoomFactor, center);
-        } else if (zoomFactor > MAX_ZOOM) {
+        } else if (zoomFactor > maxZoomFactor) {
             PointF center = new PointF(viewport.width() / 2.0f, viewport.height() / 2.0f);
-            viewportMetrics.scaleTo(MAX_ZOOM, center);
+            viewportMetrics.scaleTo(maxZoomFactor, center);
         }
 
         /* Now we pan to the right origin. */
@@ -717,9 +776,11 @@ public class PanZoomController
         if (mState == PanZoomState.ANIMATED_ZOOM)
             return false;
 
+        if (!mController.getAllowZoom())
+            return false;
+
         mState = PanZoomState.PINCHING;
         mLastZoomFocus = new PointF(detector.getFocusX(), detector.getFocusY());
-
         cancelTouch();
 
         return true;
@@ -729,7 +790,8 @@ public class PanZoomController
     public boolean onScale(SimpleScaleGestureDetector detector) {
         Log.d(LOGTAG, "onScale in state " + mState);
 
-        if (mState == PanZoomState.ANIMATED_ZOOM)
+
+        if (mState != PanZoomState.PINCHING)
             return false;
 
         float prevSpan = detector.getPreviousSpan();
@@ -752,13 +814,31 @@ public class PanZoomController
 
         synchronized (mController) {
             float newZoomFactor = mController.getZoomFactor() * spanRatio;
-            if (newZoomFactor >= MAX_ZOOM) {
-                // apply resistance when zooming past MAX_ZOOM,
-                // such that it asymptotically reaches MAX_ZOOM + 1.0
+            float minZoomFactor = 0.0f;
+            float maxZoomFactor = MAX_ZOOM;
+
+            if (mController.getMinZoom() > 0)
+                minZoomFactor = mController.getMinZoom();
+            if (mController.getMaxZoom() > 0)
+                maxZoomFactor = mController.getMaxZoom();
+
+            if (newZoomFactor < minZoomFactor) {
+                // apply resistance when zooming past minZoomFactor,
+                // such that it asymptotically reaches minZoomFactor / 2.0
                 // but never exceeds that
-                float excessZoom = newZoomFactor - MAX_ZOOM;
+                final float rate = 0.5f; // controls how quickly we approach the limit
+                float excessZoom = minZoomFactor - newZoomFactor;
+                excessZoom = 1.0f - (float)Math.exp(-excessZoom * rate);
+                newZoomFactor = minZoomFactor * (1.0f - excessZoom / 2.0f);
+            }
+
+            if (newZoomFactor > maxZoomFactor) {
+                // apply resistance when zooming past maxZoomFactor,
+                // such that it asymptotically reaches maxZoomFactor + 1.0
+                // but never exceeds that
+                float excessZoom = newZoomFactor - maxZoomFactor;
                 excessZoom = 1.0f - (float)Math.exp(-excessZoom);
-                newZoomFactor = MAX_ZOOM + excessZoom;
+                newZoomFactor = maxZoomFactor + excessZoom;
             }
 
             mController.scrollBy(new PointF(mLastZoomFocus.x - detector.getFocusX(),
@@ -807,23 +887,37 @@ public class PanZoomController
     }
 
     @Override
-    public boolean onDown(MotionEvent motionEvent) {
-        return false;
+    public boolean onSingleTapUp(MotionEvent motionEvent) {
+        // When zooming is enabled, wait to see if there's a double-tap.
+        if (mController.getAllowZoom())
+            return false;
+        return true;
     }
 
     @Override
     public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
+        // When zooming is disabled, we handle this in onSingleTapUp.
+        if (!mController.getAllowZoom())
+            return false;
         return true;
     }
 
     @Override
     public boolean onDoubleTap(MotionEvent motionEvent) {
+        if (!mController.getAllowZoom())
+            return false;
         return true;
     }
 
-    public void cancelTouch() {
+    private void cancelTouch() {
     }
 
+    /**
+     * Zoom to a specified rect IN CSS PIXELS.
+     *
+     * While we usually use device pixels, @zoomToRect must be specified in CSS
+     * pixels.
+     */
     private boolean animatedZoomTo(RectF zoomToRect) {
         mState = PanZoomState.ANIMATED_ZOOM;
         final float startZoom = mController.getZoomFactor();
@@ -849,10 +943,11 @@ public class PanZoomController
             zoomToRect.right = zoomToRect.left + newWidth;
         }
 
-        float finalZoom = viewport.width() * startZoom / zoomToRect.width();
+        float finalZoom = viewport.width() / zoomToRect.width();
 
         ViewportMetrics finalMetrics = new ViewportMetrics(mController.getViewportMetrics());
-        finalMetrics.setOrigin(new PointF(zoomToRect.left, zoomToRect.top));
+        finalMetrics.setOrigin(new PointF(zoomToRect.left * finalMetrics.getZoomFactor(),
+                                          zoomToRect.top * finalMetrics.getZoomFactor()));
         finalMetrics.scaleTo(finalZoom, new PointF(0.0f, 0.0f));
 
         // 2. now run getValidViewportMetrics on it, so that the target viewport is
commit 0455b3d4b874db06a205d1133f48bcd323665911
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Wed Sep 24 13:18:03 2014 +0200

    android: update LayerRenderer to newer Fennec code
    
    Change-Id: Idc7c49351c17dc9953d55ee7f1b42e497074c85a

diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
index 506c57e..bdcfb1d 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
@@ -152,7 +152,7 @@ public class LOKitThread extends Thread {
             Log.i(LOGTAG, "Done generate thumbnail!");
             if (bitmap != null) {
                 Log.i(LOGTAG, "Setting checkboard image!");
-                mApplication.getLayerController().getView().changeCheckerboardBitmap(bitmap);
+                mApplication.getLayerController().getView().changeCheckerboardBitmap(bitmap, mTileProvider.getPageWidth(), mTileProvider.getPageHeight());
                 Log.i(LOGTAG, "Done setting checkboard image!!");
                 mCheckboardImageSet = true;
             }
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java
index ade6a46..5146b22 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java
@@ -1,41 +1,7 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Android code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2009-2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Patrick Walton <pcwalton at mozilla.com>
- *   Chris Lord <chrislord.net at gmail.com>
- *   Arkady Blyakher <rkadyb at mit.edu>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
@@ -88,7 +54,8 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
     private final ScrollbarLayer mHorizScrollLayer;
     private final ScrollbarLayer mVertScrollLayer;
     private final FadeRunnable mFadeRunnable;
-    private final FloatBuffer mCoordBuffer;
+    private ByteBuffer mCoordByteBuffer;
+    private FloatBuffer mCoordBuffer;
     private RenderContext mLastPageContext;
     private int mMaxTextureSize;
     private int mBackgroundColor;
@@ -153,20 +120,26 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
         "    gl_FragColor = texture2D(sTexture, vec2(vTexCoord.x, 1.0 - vTexCoord.y));\n" +
         "}\n";
 
-    public void setCheckerboardBitmap(Bitmap bitmap) {
+    public void setCheckerboardBitmap(Bitmap bitmap, float pageWidth, float pageHeight) {
         mCheckerboardLayer.setBitmap(bitmap);
         mCheckerboardLayer.beginTransaction();
         try {
+            mCheckerboardLayer.setPosition(new Rect(0, 0, Math.round(pageWidth),
+                                                    Math.round(pageHeight)));
             mCheckerboardLayer.invalidate();
         } finally {
             mCheckerboardLayer.endTransaction();
         }
     }
 
-    public void updateCheckerboardBitmap(Bitmap bitmap, float x, float y, float width, float height) {
+    public void updateCheckerboardBitmap(Bitmap bitmap, float x, float y,
+                                         float width, float height,
+                                         float pageWidth, float pageHeight) {
         mCheckerboardLayer.updateBitmap(bitmap, x, y, width, height);
         mCheckerboardLayer.beginTransaction();
         try {
+            mCheckerboardLayer.setPosition(new Rect(0, 0, Math.round(pageWidth),
+                                                    Math.round(pageHeight)));
             mCheckerboardLayer.invalidate();
         } finally {
             mCheckerboardLayer.endTransaction();
@@ -175,12 +148,6 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
 
     public void resetCheckerboard() {
         mCheckerboardLayer.reset();
-        mCheckerboardLayer.beginTransaction();
-        try {
-            mCheckerboardLayer.invalidate();
-        } finally {
-            mCheckerboardLayer.endTransaction();
-        }
     }
 
     public LayerRenderer(LayerView view) {
@@ -205,9 +172,22 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
 
         // Initialize the FloatBuffer that will be used to store all vertices and texture
         // coordinates in draw() commands.
-        ByteBuffer byteBuffer = DirectBufferAllocator.allocate(COORD_BUFFER_SIZE * 4);
-        byteBuffer.order(ByteOrder.nativeOrder());
-        mCoordBuffer = byteBuffer.asFloatBuffer();
+        mCoordByteBuffer = DirectBufferAllocator.allocate(COORD_BUFFER_SIZE * 4);
+        mCoordByteBuffer.order(ByteOrder.nativeOrder());
+        mCoordBuffer = mCoordByteBuffer.asFloatBuffer();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mCoordByteBuffer != null) {
+                DirectBufferAllocator.free(mCoordByteBuffer);
+                mCoordByteBuffer = null;
+                mCoordBuffer = null;
+            }
+        } finally {
+            super.finalize();
+        }
     }
 
     public void onSurfaceCreated(GL10 gl, EGLConfig config) {
@@ -408,20 +388,6 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
         }).start();
     }
 
-    private void updateCheckerboardImage() {
-        int checkerboardColor = mView.getController().getCheckerboardColor();
-        boolean showChecks = mView.getController().checkerboardShouldShowChecks();
-
-        mCheckerboardLayer.beginTransaction();  // called on compositor thread
-        try {
-            if (mCheckerboardLayer.updateBackground(showChecks, checkerboardColor))
-                mCheckerboardLayer.invalidate();
-        } finally {
-            mCheckerboardLayer.endTransaction();
-        }
-
-    }
-
     /*
      * create a vertex shader type (GLES20.GL_VERTEX_SHADER)
      * or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java
index 62a26bf..f981667 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerView.java
@@ -348,8 +348,8 @@ public class LayerView extends SurfaceView implements SurfaceHolder.Callback {
         }
     }
 
-    public void changeCheckerboardBitmap(Bitmap bitmap) {
+    public void changeCheckerboardBitmap(Bitmap bitmap, float pageWidth, float pageHeight) {
         mRenderer.resetCheckerboard();
-        mRenderer.setCheckerboardBitmap(bitmap);
+        mRenderer.setCheckerboardBitmap(bitmap, pageWidth, pageHeight);
     }
 }
commit 2550cb0124f19269521cecfba39afa4ac4023431
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Wed Sep 24 12:49:08 2014 +0200

    android: introduce TileProviderFactory
    
    Change-Id: I98ba16b4d1537ddeb2f8a29d15a803d527ccafe3

diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
index d11c4c9..506c57e 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
@@ -26,6 +26,7 @@ public class LOKitThread extends Thread {
     private boolean mCheckboardImageSet = false;
 
     public LOKitThread() {
+        TileProviderFactory.initialize();
     }
 
     private RectF normlizeRect(ImmutableViewportMetrics metrics) {
@@ -35,9 +36,9 @@ public class LOKitThread extends Thread {
     }
 
     private Rect roundToTileSize(RectF input, int tileSize) {
-        int minX = (Math.round(input.left)    / tileSize) * tileSize;
-        int minY = (Math.round(input.top)     / tileSize) * tileSize;
-        int maxX = ((Math.round(input.right)  / tileSize) + 1) * tileSize;
+        int minX = (Math.round(input.left) / tileSize) * tileSize;
+        int minY = (Math.round(input.top) / tileSize) * tileSize;
+        int maxX = ((Math.round(input.right) / tileSize) + 1) * tileSize;
         int maxY = ((Math.round(input.bottom) / tileSize) + 1) * tileSize;
         return new Rect(minX, minY, maxX, maxY);
     }
@@ -136,7 +137,7 @@ public class LOKitThread extends Thread {
         if (mTileProvider != null) {
             mTileProvider.close();
         }
-        mTileProvider = new LOKitTileProvider(mApplication.getLayerController(), filename);
+        mTileProvider = TileProviderFactory.create(mApplication.getLayerController(), filename);
         boolean isReady = mTileProvider.isReady();
         if (isReady) {
             updateCheckbardImage();
@@ -191,4 +192,8 @@ public class LOKitThread extends Thread {
         Log.i(LOGTAG, "Event: " + event.getTypeString());
         mEventQueue.add(event);
     }
+
+    public void clearQueue() {
+        mEventQueue.clear();
+    }
 }
diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java
index 284afe9..35a320d 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java
@@ -13,7 +13,6 @@ import android.widget.AdapterView;
 import android.widget.ListView;
 import android.widget.RelativeLayout;
 
-import org.libreoffice.kit.LibreOfficeKit;
 import org.mozilla.gecko.gfx.GeckoLayerClient;
 import org.mozilla.gecko.gfx.LayerController;
 
@@ -111,19 +110,18 @@ public class LibreOfficeMainActivity extends Activity {
             mDrawerList.setOnItemClickListener(new DocumentPartClickListener());
         }
 
-        LibreOfficeKit.loadStatic();
+        if (sLOKitThread == null) {
+            sLOKitThread = new LOKitThread();
+            sLOKitThread.start();
+        } else {
+            sLOKitThread.clearQueue();
+        }
 
         mLayerController = new LayerController(this);
         mLayerClient = new GeckoLayerClient(this);
         mLayerController.setLayerClient(mLayerClient);
         mGeckoLayout.addView(mLayerController.getView(), 0);
 
-        if (sLOKitThread == null) {
-            sLOKitThread = new LOKitThread();
-            sLOKitThread.start();
-        }
-
-        sLOKitThread.mEventQueue.clear();
         LOKitShell.sendEvent(LOEvent.load(inputFile));
 
         Log.w(LOGTAG, "UI almost up");
diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/TileProviderFactory.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/TileProviderFactory.java
new file mode 100644
index 0000000..5cd3ed0
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/TileProviderFactory.java
@@ -0,0 +1,31 @@
+package org.libreoffice;
+
+
+import org.libreoffice.kit.LibreOfficeKit;
+import org.mozilla.gecko.gfx.LayerController;
+
+public class TileProviderFactory {
+    private static TileProviderID currentTileProvider = TileProviderID.LOKIT;
+
+    private TileProviderFactory() {
+
+    }
+
+    public static void initialize() {
+        if (currentTileProvider == TileProviderID.LOKIT) {
+            LibreOfficeKit.loadStatic();
+        }
+    }
+
+    public static TileProvider create(LayerController layerController, String filename) {
+        if (currentTileProvider == TileProviderID.LOKIT) {
+            return new LOKitTileProvider(layerController, filename);
+        } else {
+            return new MockTileProvider(layerController, filename);
+        }
+    }
+
+    private static enum TileProviderID {
+        MOCK, LOKIT
+    }
+}
\ No newline at end of file
commit ef2b36f8d3a023d5562e742fe4db57dafdd28115
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Wed Sep 24 10:12:20 2014 +0200

    android: GeckoLayerClient update
    
    Change-Id: Ie684a4d3ef012b004ede52265750da5497db434e

diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOEvent.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOEvent.java
index 4aa32cb..f16b2da 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOEvent.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOEvent.java
@@ -20,7 +20,7 @@ public class LOEvent {
     private int mPartIndex;
     private String mFilename;
 
-    public LOEvent(int type, int widthPixels, int heightPixels, int tileWidth, int tileHeight) {
+    public LOEvent(int type, int widthPixels, int heightPixels) {
         mType = type;
         mTypeString = "Size Changed: " + widthPixels + " " + heightPixels;
     }
@@ -56,8 +56,8 @@ public class LOEvent {
         return new LOEvent(DRAW, rect);
     }
 
-    public static LOEvent sizeChanged(int widthPixels, int heightPixels, int tileWidth, int tileHeight) {
-        return new LOEvent(SIZE_CHANGED, widthPixels, heightPixels, tileWidth, tileHeight);
+    public static LOEvent sizeChanged(int widthPixels, int heightPixels) {
+        return new LOEvent(SIZE_CHANGED, widthPixels, heightPixels);
     }
 
     public static LOEvent tileSize(IntSize tileSize) {
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DrawTimingQueue.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DrawTimingQueue.java
new file mode 100644
index 0000000..ce868f1
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DrawTimingQueue.java
@@ -0,0 +1,95 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.gfx;
+
+import android.os.SystemClock;
+
+/**
+ * A custom-built data structure to assist with measuring draw times.
+ *
+ * This class maintains a fixed-size circular buffer of DisplayPortMetrics
+ * objects and associated timestamps. It provides only three operations, which
+ * is all we require for our purposes of measuring draw times. Note
+ * in particular that the class is designed so that even though it is
+ * accessed from multiple threads, it does not require synchronization;
+ * any concurrency errors that result from this are handled gracefully.
+ *
+ * Assuming an unrolled buffer so that mTail is greater than mHead, the data
+ * stored in the buffer at entries [mHead, mTail) will never be modified, and
+ * so are "safe" to read. If this reading is done on the same thread that
+ * owns mHead, then reading the range [mHead, mTail) is guaranteed to be safe
+ * since the range itself will not shrink.
+ */
+final class DrawTimingQueue {
+    private static final String LOGTAG = "GeckoDrawTimingQueue";
+    private static final int BUFFER_SIZE = 16;
+
+    private final DisplayPortMetrics[] mMetrics;
+    private final long[] mTimestamps;
+
+    private int mHead;
+    private int mTail;
+
+    DrawTimingQueue() {
+        mMetrics = new DisplayPortMetrics[BUFFER_SIZE];
+        mTimestamps = new long[BUFFER_SIZE];
+        mHead = BUFFER_SIZE - 1;
+        mTail = 0;
+    }
+
+    /**
+     * Add a new entry to the tail of the queue. If the buffer is full,
+     * do nothing. This must only be called from the Java UI thread.
+     */
+    boolean add(DisplayPortMetrics metrics) {
+        if (mHead == mTail) {
+            return false;
+        }
+        mMetrics[mTail] = metrics;
+        mTimestamps[mTail] = SystemClock.uptimeMillis();
+        mTail = (mTail + 1) % BUFFER_SIZE;
+        return true;
+    }
+
+    /**
+     * Find the timestamp associated with the given metrics, AND remove
+     * all metrics objects from the start of the queue up to and including
+     * the one provided. Note that because of draw coalescing, the metrics
+     * object passed in here may not be the one at the head of the queue,
+     * and so we must iterate our way through the list to find it.
+     * This must only be called from the compositor thread.
+     */
+    long findTimeFor(DisplayPortMetrics metrics) {
+        // keep a copy of the tail pointer so that we ignore new items
+        // added to the queue while we are searching. this is fine because
+        // the one we are looking for will either have been added already
+        // or will not be in the queue at all.
+        int tail = mTail;
+        // walk through the "safe" range from mHead to tail; these entries
+        // will not be modified unless we change mHead.
+        int i = (mHead + 1) % BUFFER_SIZE;
+        while (i != tail) {
+            if (mMetrics[i].fuzzyEquals(metrics)) {
+                // found it, copy out the timestamp to a local var BEFORE
+                // changing mHead or add could clobber the timestamp.
+                long timestamp = mTimestamps[i];
+                mHead = i;
+                return timestamp;
+            }
+            i = (i + 1) % BUFFER_SIZE;
+        }
+        return -1;
+    }
+
+    /**
+     * Reset the buffer to empty.
+     * This must only be called from the compositor thread.
+     */
+    void reset() {
+        // we can only modify mHead on this thread.
+        mHead = (mTail + BUFFER_SIZE - 1) % BUFFER_SIZE;
+    }
+}
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
index 2d735a8..ce047ee 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
@@ -44,6 +44,7 @@ import android.graphics.RectF;
 import android.os.SystemClock;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.view.View;
 
 import org.libreoffice.LOEvent;
 import org.libreoffice.LOKitShell;
@@ -51,64 +52,71 @@ import org.libreoffice.LibreOfficeMainActivity;
 import org.mozilla.gecko.util.FloatUtils;
 
 import java.util.List;
+import java.util.regex.Pattern;
 
-public class GeckoLayerClient {
+public class GeckoLayerClient implements LayerView.Listener {
     private static final String LOGTAG = "GeckoLayerClient";
 
-    private static final long MIN_VIEWPORT_CHANGE_DELAY = 25L;
-    private static final IntSize TILE_SIZE = new IntSize(256, 256);
+    private LayerController mLayerController;
+    private LayerRenderer mLayerRenderer;
+    private boolean mLayerRendererInitialized;
 
-    protected IntSize mScreenSize;
+    private IntSize mScreenSize;
+    private IntSize mWindowSize;
     private DisplayPortMetrics mDisplayPort;
-    protected Layer mTileLayer;
+    private boolean mRecordDrawTimes;
+    private DrawTimingQueue mDrawTimingQueue;
+
+    private Layer mRootLayer;
+
     /* The viewport that Gecko is currently displaying. */
-    protected ViewportMetrics mGeckoViewport;
+    private ViewportMetrics mGeckoViewport;
+
     /* The viewport that Gecko will display when drawing is finished */
-    protected ViewportMetrics mNewGeckoViewport;
-    protected LayerController mLayerController;
+    private ViewportMetrics mNewGeckoViewport;
     private Context mContext;
+    private static final long MIN_VIEWPORT_CHANGE_DELAY = 25L;
     private long mLastViewportChangeTime;
     private boolean mPendingViewportAdjust;
     private boolean mViewportSizeChanged;
+    private boolean mIgnorePaintsPendingViewportSizeChange;
+    private boolean mFirstPaint = true;
+
     // mUpdateViewportOnEndDraw is used to indicate that we received a
     // viewport update notification while drawing. therefore, when the
     // draw finishes, we need to update the entire viewport rather than
     // just the page size. this boolean should always be accessed from
     // inside a transaction, so no synchronization is needed.
     private boolean mUpdateViewportOnEndDraw;
+
     private String mLastCheckerboardColor;
 
+    private static Pattern sColorPattern;
+
+    /* Used as a temporary ViewTransform by syncViewportInfo */
+    private ViewTransform mCurrentViewTransform;
+
     public GeckoLayerClient(Context context) {
         mContext = context;
         mScreenSize = new IntSize(0, 0);
+        mWindowSize = new IntSize(0, 0);
         mDisplayPort = new DisplayPortMetrics();
+        mRecordDrawTimes = true;
+        mDrawTimingQueue = new DrawTimingQueue();
+        mCurrentViewTransform = new ViewTransform(0, 0, 1);
     }
 
-    protected void setupLayer() {
-        if (mTileLayer == null) {
-            Log.i(LOGTAG, "Creating MultiTileLayer");
-            mTileLayer = new MultiTileLayer(TILE_SIZE);
-            mLayerController.setRoot(mTileLayer);
-        }
-    }
+    /** Attaches the root layer to the layer controller so that Gecko appears. */
+    public void setLayerController(LayerController layerController) {
+        LayerView view = layerController.getView();
 
-    protected void updateLayerAfterDraw() {
-        if (mTileLayer instanceof MultiTileLayer) {
-            ((MultiTileLayer) mTileLayer).invalidate();
-        }
-    }
+        mLayerController = layerController;
 
-    protected IntSize getTileSize() {
-        return TILE_SIZE;
-    }
+        mRootLayer = new MultiTileLayer(new IntSize(256, 256));
 
-    /**
-     * Attaches the root layer to the layer controller so that Gecko appears.
-     */
-    public void setLayerController(LayerController layerController) {
-        mLayerController = layerController;
+        view.setListener(this);
+        layerController.setRoot(mRootLayer);
 
-        layerController.setRoot(mTileLayer);
         if (mGeckoViewport != null) {
             layerController.setViewportMetrics(mGeckoViewport);
         }
@@ -116,10 +124,19 @@ public class GeckoLayerClient {
         sendResizeEventIfNecessary(true);
     }
 
+    DisplayPortMetrics getDisplayPort() {
+        return mDisplayPort;
+    }
+
+    protected void updateLayerAfterDraw() {
+        if (mRootLayer instanceof MultiTileLayer) {
+            ((MultiTileLayer) mRootLayer).invalidate();
+        }
+    }
+
     public void beginDrawing(ViewportMetrics viewportMetrics) {
-        setupLayer();
         mNewGeckoViewport = viewportMetrics;
-        mTileLayer.beginTransaction();
+        mRootLayer.beginTransaction();
     }
 
     public void endDrawing() {
@@ -129,16 +146,12 @@ public class GeckoLayerClient {
                 mUpdateViewportOnEndDraw = false;
                 updateLayerAfterDraw();
             } finally {
-                mTileLayer.endTransaction();
+                mRootLayer.endTransaction();
             }
         }
         Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - endDrawing");
     }
 
-    DisplayPortMetrics getDisplayPort() {
-        return mDisplayPort;
-    }
-
     protected void updateViewport(boolean onlyUpdatePageSize) {
         // save and restore the viewport size stored in java; never let the
         // JS-side viewport dimensions override the java-side ones because
@@ -150,8 +163,8 @@ public class GeckoLayerClient {
 
         PointF displayportOrigin = mGeckoViewport.getOrigin();
         RectF position = mGeckoViewport.getViewport();
-        mTileLayer.setPosition(RectUtils.round(position));
-        mTileLayer.setResolution(mGeckoViewport.getZoomFactor());
+        mRootLayer.setPosition(RectUtils.round(position));
+        mRootLayer.setResolution(mGeckoViewport.getZoomFactor());
 
         Log.e(LOGTAG, "### updateViewport onlyUpdatePageSize=" + onlyUpdatePageSize + " getTileViewport " + mGeckoViewport);
 
@@ -173,51 +186,39 @@ public class GeckoLayerClient {
 
         DisplayMetrics metrics = new DisplayMetrics();
         LibreOfficeMainActivity.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+        View view = mLayerController.getView();
 
         IntSize newScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
+        IntSize newWindowSize = new IntSize(view.getWidth(), view.getHeight());
 
         // Return immediately if the screen size hasn't changed or the viewport
         // size is zero (which indicates that the rendering surface hasn't been
         // allocated yet).
         boolean screenSizeChanged = !mScreenSize.equals(newScreenSize);
+        boolean windowSizeChanged = !mWindowSize.equals(newWindowSize);
 
-        if (!force && !screenSizeChanged) {
+        if (!force && !screenSizeChanged && !windowSizeChanged) {
             return;
         }
 
         mScreenSize = newScreenSize;
+        mWindowSize = newWindowSize;
 
         if (screenSizeChanged) {
             Log.d(LOGTAG, "Screen-size changed to " + mScreenSize);
         }
 
-        IntSize tileSize = getTileSize();
-        LOEvent event = LOEvent.sizeChanged(metrics.widthPixels, metrics.heightPixels, tileSize.width, tileSize.height);
-        LOKitShell.sendEvent(event);
-    }
-
-    public void render() {
-        adjustViewportWithThrottling();
-    }
-
-    private void adjustViewportWithThrottling() {
-        if (!mLayerController.getRedrawHint())
-            return;
-
-        if (mPendingViewportAdjust)
-            return;
-
-        long timeDelta = System.currentTimeMillis() - mLastViewportChangeTime;
-        if (timeDelta < MIN_VIEWPORT_CHANGE_DELAY) {
-            mLayerController.getView().postDelayed(new AdjustRunnable(), MIN_VIEWPORT_CHANGE_DELAY - timeDelta);
-            mPendingViewportAdjust = true;
-        } else {
-            adjustViewport(null);
+        if (windowSizeChanged) {
+            Log.d(LOGTAG, "Window-size changed to " + mWindowSize);
         }
+
+        LOEvent event = LOEvent.sizeChanged(metrics.widthPixels, metrics.heightPixels);
+        LOKitShell.sendEvent(event);
     }
 
     public void viewportSizeChanged() {
-        mViewportSizeChanged = true;
+        sendResizeEventIfNecessary(true);
+        LOKitShell.viewSizeChanged();
     }
 
     void adjustViewport(DisplayPortMetrics displayPort) {
@@ -234,13 +235,31 @@ public class GeckoLayerClient {
         mDisplayPort = displayPort;
         mGeckoViewport = clampedMetrics;
 
+        if (mRecordDrawTimes) {
+            mDrawTimingQueue.add(displayPort);
+        }
+
         LOKitShell.sendEvent(LOEvent.viewport(clampedMetrics));
         if (mViewportSizeChanged) {
             mViewportSizeChanged = false;
             LOKitShell.viewSizeChanged();
         }
+    }
 
-        mLastViewportChangeTime = System.currentTimeMillis();
+    public void setPageSize(float zoom, float pageWidth, float pageHeight, float cssPageWidth, float cssPageHeight) {
+        synchronized (mLayerController) {
+        // adjust the page dimensions to account for differences in zoom
+        // between the rendered content (which is what the compositor tells us)
+    // and our zoom level (which may have diverged).
+        float ourZoom = mLayerController.getZoomFactor();
+        pageWidth = pageWidth * ourZoom / zoom;
+        pageHeight = pageHeight * ourZoom /zoom;
+        mLayerController.setPageSize(new FloatSize(pageWidth, pageHeight));
+        // Here the page size of the document has changed, but the document being displayed
+        // is still the same. Therefore, we don't need to send anything to browser.js; any
+        // changes we need to make to the display port will get sent the next time we call
+        // adjustViewport().
+        }
     }
 
     public void geometryChanged() {
@@ -250,24 +269,41 @@ public class GeckoLayerClient {
     }
 
     public ViewportMetrics getGeckoViewportMetrics() {
-        // Return a copy, as we modify this inside the Gecko thread
-        if (mGeckoViewport != null)
-            return new ViewportMetrics(mGeckoViewport);
-        return null;
+        return mGeckoViewport;
     }
 
+    public List<SubTile> getTiles() {
+        if (mRootLayer instanceof MultiTileLayer) {
+            return ((MultiTileLayer) mRootLayer).getTiles();
+        }
+        return null;
+    }
 
     public void addTile(SubTile tile) {
-        if (mTileLayer instanceof MultiTileLayer) {
-            ((MultiTileLayer) mTileLayer).addTile(tile);
+        if (mRootLayer instanceof MultiTileLayer) {
+            ((MultiTileLayer) mRootLayer).addTile(tile);
         }
     }
 
-    public List<SubTile> getTiles() {
-        if (mTileLayer instanceof MultiTileLayer) {
-            return ((MultiTileLayer) mTileLayer).getTiles();
-        }
-        return null;
+    @Override
+    public void renderRequested() {
+
+    }
+
+    @Override
+    public void compositionPauseRequested() {
+
+    }
+
+    @Override
+    public void compositionResumeRequested() {
+
+    }
+
+    @Override
+    public void surfaceChanged(int width, int height) {
+        compositionResumeRequested();
+        renderRequested();
     }
 
     private class AdjustRunnable implements Runnable {
commit 1fa58e5c60ac136e92e91c4efa0cdcc5c28afa75
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Wed Sep 24 09:10:12 2014 +0200

    android: update DisplayPortCalculator
    
    Change-Id: Ib47822940e83e8fb78a0a8b1e18028646bfb35ae

diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DisplayPortCalculator.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DisplayPortCalculator.java
index a0cb229..a255974 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DisplayPortCalculator.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/DisplayPortCalculator.java
@@ -7,16 +7,36 @@ package org.mozilla.gecko.gfx;
 
 import android.graphics.PointF;
 import android.graphics.RectF;
+import android.util.FloatMath;
 import android.util.Log;
 
+import org.json.JSONArray;
 import org.libreoffice.LOKitShell;
 import org.mozilla.gecko.util.FloatUtils;
 
+import java.util.Map;
+
 final class DisplayPortCalculator {
     private static final String LOGTAG = "GeckoDisplayPortCalculator";
     private static final PointF ZERO_VELOCITY = new PointF(0, 0);
 
-    private static DisplayPortStrategy sStrategy = new FixedMarginStrategy();
+    // Keep this in sync with the TILEDLAYERBUFFER_TILE_SIZE defined in gfx/layers/TiledLayerBuffer.h
+    private static final int TILE_SIZE = 256;
+
+    private static final String PREF_DISPLAYPORT_STRATEGY = "gfx.displayport.strategy";
+    private static final String PREF_DISPLAYPORT_FM_MULTIPLIER = "gfx.displayport.strategy_fm.multiplier";
+    private static final String PREF_DISPLAYPORT_FM_DANGER_X = "gfx.displayport.strategy_fm.danger_x";
+    private static final String PREF_DISPLAYPORT_FM_DANGER_Y = "gfx.displayport.strategy_fm.danger_y";
+    private static final String PREF_DISPLAYPORT_VB_MULTIPLIER = "gfx.displayport.strategy_vb.multiplier";
+    private static final String PREF_DISPLAYPORT_VB_VELOCITY_THRESHOLD = "gfx.displayport.strategy_vb.threshold";
+    private static final String PREF_DISPLAYPORT_VB_REVERSE_BUFFER = "gfx.displayport.strategy_vb.reverse_buffer";
+    private static final String PREF_DISPLAYPORT_VB_DANGER_X_BASE = "gfx.displayport.strategy_vb.danger_x_base";
+    private static final String PREF_DISPLAYPORT_VB_DANGER_Y_BASE = "gfx.displayport.strategy_vb.danger_y_base";
+    private static final String PREF_DISPLAYPORT_VB_DANGER_X_INCR = "gfx.displayport.strategy_vb.danger_x_incr";
+    private static final String PREF_DISPLAYPORT_VB_DANGER_Y_INCR = "gfx.displayport.strategy_vb.danger_y_incr";
+    private static final String PREF_DISPLAYPORT_PB_VELOCITY_THRESHOLD = "gfx.displayport.strategy_pb.threshold";
+
+    private static DisplayPortStrategy sStrategy = new NoMarginStrategy(null);
 
     static DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
         return sStrategy.calculate(metrics, (velocity == null ? ZERO_VELOCITY : velocity));
@@ -29,35 +49,78 @@ final class DisplayPortCalculator {
         return sStrategy.aboutToCheckerboard(metrics, (velocity == null ? ZERO_VELOCITY : velocity), displayPort);
     }
 
+    static boolean drawTimeUpdate(long millis, int pixels) {
+        return sStrategy.drawTimeUpdate(millis, pixels);
+    }
+
+    static void resetPageState() {
+        sStrategy.resetPageState();
+    }
+
+    static void addPrefNames(JSONArray prefs) {
+        prefs.put(PREF_DISPLAYPORT_STRATEGY);
+        prefs.put(PREF_DISPLAYPORT_FM_MULTIPLIER);
+        prefs.put(PREF_DISPLAYPORT_FM_DANGER_X);
+        prefs.put(PREF_DISPLAYPORT_FM_DANGER_Y);
+        prefs.put(PREF_DISPLAYPORT_VB_MULTIPLIER);
+        prefs.put(PREF_DISPLAYPORT_VB_VELOCITY_THRESHOLD);
+        prefs.put(PREF_DISPLAYPORT_VB_REVERSE_BUFFER);
+        prefs.put(PREF_DISPLAYPORT_VB_DANGER_X_BASE);
+        prefs.put(PREF_DISPLAYPORT_VB_DANGER_Y_BASE);
+        prefs.put(PREF_DISPLAYPORT_VB_DANGER_X_INCR);
+        prefs.put(PREF_DISPLAYPORT_VB_DANGER_Y_INCR);
+        prefs.put(PREF_DISPLAYPORT_PB_VELOCITY_THRESHOLD);
+    }
+
     /**
      * Set the active strategy to use.
      * See the gfx.displayport.strategy pref in mobile/android/app/mobile.js to see the
      * mapping between ints and strategies.
      */
-    static void setStrategy(int strategy) {
+    static boolean setStrategy(Map<String, Integer> prefs) {
+        Integer strategy = prefs.get(PREF_DISPLAYPORT_STRATEGY);
+        if (strategy == null) {
+            return false;
+        }
+
         switch (strategy) {
             case 0:
-            default:
-                sStrategy = new FixedMarginStrategy();
+                sStrategy = new FixedMarginStrategy(prefs);
                 break;
             case 1:
-                sStrategy = new VelocityBiasStrategy();
+                sStrategy = new VelocityBiasStrategy(prefs);
                 break;
             case 2:
-                sStrategy = new DynamicResolutionStrategy();
+                sStrategy = new DynamicResolutionStrategy(prefs);
                 break;
             case 3:
-                sStrategy = new NoMarginStrategy();
+                sStrategy = new NoMarginStrategy(prefs);
+                break;
+            case 4:
+                sStrategy = new PredictionBiasStrategy(prefs);
                 break;
+            default:
+                Log.e(LOGTAG, "Invalid strategy index specified");
+                return false;
         }
-        Log.i(LOGTAG, "Set strategy " + sStrategy.getClass().getName());
+        Log.i(LOGTAG, "Set strategy " + sStrategy.toString());
+        return true;
+    }
+
+    private static float getFloatPref(Map<String, Integer> prefs, String prefName, int defaultValue) {
+        Integer value = (prefs == null ? null : prefs.get(prefName));
+        return (float)(value == null || value < 0 ? defaultValue : value) / 1000f;
     }
 
-    private interface DisplayPortStrategy {
+    private static abstract class DisplayPortStrategy {
         /** Calculates a displayport given a viewport and panning velocity. */
-        public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity);
+        public abstract DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity);
         /** Returns true if a checkerboard is about to be visible and we should not throttle drawing. */
-        public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort);
+        public abstract boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort);
+        /** Notify the strategy of a new recorded draw time. Return false to turn off draw time recording. */
+        public boolean drawTimeUpdate(long millis, int pixels) { return false; }
+        /** Reset any page-specific state stored, as the page being displayed has changed. */
+        public void resetPageState() {}
     }
 
     /**
@@ -100,6 +163,24 @@ final class DisplayPortCalculator {
     }
 
     /**
+     * Expand the given margins such that when they are applied on the viewport, the resulting rect
+     * does not have any partial tiles, except when it is clipped by the page bounds. This assumes
+     * the tiles are TILE_SIZE by TILE_SIZE and start at the origin, such that there will always be
+     * a tile at (0,0)-(TILE_SIZE,TILE_SIZE)).
+     */
+    private static DisplayPortMetrics getTileAlignedDisplayPortMetrics(RectF margins, float zoom, ImmutableViewportMetrics metrics) {
+        float left = metrics.viewportRectLeft - margins.left;
+        float top = metrics.viewportRectTop - margins.top;
+        float right = metrics.viewportRectRight + margins.right;
+        float bottom = metrics.viewportRectBottom + margins.bottom;
+        left = Math.max(0.0f, TILE_SIZE * FloatMath.floor(left / TILE_SIZE));
+        top = Math.max(0.0f, TILE_SIZE * FloatMath.floor(top / TILE_SIZE));
+        right = Math.min(metrics.pageSizeWidth, TILE_SIZE * FloatMath.ceil(right / TILE_SIZE));
+        bottom = Math.min(metrics.pageSizeHeight, TILE_SIZE * FloatMath.ceil(bottom / TILE_SIZE));
+        return new DisplayPortMetrics(left, top, right, bottom, zoom);
+    }
+
+    /**
      * Adjust the given margins so if they are applied on the viewport in the metrics, the resulting rect
      * does not exceed the page bounds. This code will maintain the total margin amount for a given axis;
      * it assumes that margins.left + metrics.getWidth() + margins.right is less than or equal to
@@ -133,9 +214,24 @@ final class DisplayPortCalculator {
     }
 
     /**
+     * Clamp the given rect to the page bounds and return it.
+     */
+    private static RectF clampToPageBounds(RectF rect, ImmutableViewportMetrics metrics) {
+        rect.left = Math.max(rect.left, 0);
+        rect.top = Math.max(rect.top, 0);
+        rect.right = Math.min(rect.right, metrics.pageSizeWidth);
+        rect.bottom = Math.min(rect.bottom, metrics.pageSizeHeight);
+        return rect;
+    }
+
+    /**
      * This class implements the variation where we basically don't bother with a a display port.
      */
-    private static class NoMarginStrategy implements DisplayPortStrategy {
+    private static class NoMarginStrategy extends DisplayPortStrategy {
+        NoMarginStrategy(Map<String, Integer> prefs) {
+            // no prefs in this strategy
+        }
+
         public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
             return new DisplayPortMetrics(metrics.viewportRectLeft,
                     metrics.viewportRectTop,
@@ -147,6 +243,11 @@ final class DisplayPortCalculator {
         public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
             return true;
         }
+
+        @Override
+        public String toString() {
+            return "NoMarginStrategy";
+        }
     }
 
     /**
@@ -157,15 +258,21 @@ final class DisplayPortCalculator {
      * and/or (b) increasing the buffer on the other axis to compensate for the reduced buffer on
      * one axis.
      */
-    private static class FixedMarginStrategy implements DisplayPortStrategy {
+    private static class FixedMarginStrategy extends DisplayPortStrategy {
         // The length of each axis of the display port will be the corresponding view length
         // multiplied by this factor.
-        private static final float SIZE_MULTIPLIER = 1.5f;
+        private final float SIZE_MULTIPLIER;
 
         // If the visible rect is within the danger zone (measured as a fraction of the view size
         // from the edge of the displayport) we start redrawing to minimize checkerboarding.
-        private static final float DANGER_ZONE_X_MULTIPLIER = 0.10f;
-        private static final float DANGER_ZONE_Y_MULTIPLIER = 0.20f;
+        private final float DANGER_ZONE_X_MULTIPLIER;
+        private final float DANGER_ZONE_Y_MULTIPLIER;
+
+        FixedMarginStrategy(Map<String, Integer> prefs) {
+            SIZE_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_FM_MULTIPLIER, 2000);
+            DANGER_ZONE_X_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_FM_DANGER_X, 100);
+            DANGER_ZONE_Y_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_FM_DANGER_Y, 200);
+        }
 
         public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
             float displayPortWidth = metrics.getWidth() * SIZE_MULTIPLIER;
@@ -191,15 +298,7 @@ final class DisplayPortCalculator {
             margins.bottom = verticalBuffer - margins.top;
             margins = shiftMarginsForPageBounds(margins, metrics);
 
-            // note that unless the viewport size changes, or the page dimensions change (either because of
-            // content changes or zooming), the size of the display port should remain constant. this
-            // is intentional to avoid re-creating textures and all sorts of other reallocations in the
-            // draw and composition code.
-            return new DisplayPortMetrics(metrics.viewportRectLeft - margins.left,
-                    metrics.viewportRectTop - margins.top,
-                    metrics.viewportRectRight + margins.right,
-                    metrics.viewportRectBottom + margins.bottom,
-                    metrics.zoomFactor);
+            return getTileAlignedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
         }
 
         public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
@@ -209,6 +308,11 @@ final class DisplayPortCalculator {
             RectF adjustedViewport = expandByDangerZone(metrics.getViewport(), DANGER_ZONE_X_MULTIPLIER, DANGER_ZONE_Y_MULTIPLIER, metrics);
             return !displayPort.contains(adjustedViewport);
         }
+
+        @Override
+        public String toString() {
+            return "FixedMarginStrategy mult=" + SIZE_MULTIPLIER + ", dangerX=" + DANGER_ZONE_X_MULTIPLIER + ", dangerY=" + DANGER_ZONE_Y_MULTIPLIER;
+        }
     }
 
     /**
@@ -217,14 +321,67 @@ final class DisplayPortCalculator {
      * they are affected by the panning velocity. Specifically, if we are panning on one axis,
      * we remove the margins on the other axis because we are likely axis-locked. Also once
      * we are panning in one direction above a certain threshold velocity, we shift the buffer
-     * so that it is entirely in the direction of the pan.
+     * so that it is almost entirely in the direction of the pan, with a little bit in the
+     * reverse direction.
      */
-    private static class VelocityBiasStrategy implements DisplayPortStrategy {
+    private static class VelocityBiasStrategy extends DisplayPortStrategy {
         // The length of each axis of the display port will be the corresponding view length
         // multiplied by this factor.
-        private static final float SIZE_MULTIPLIER = 1.2f;
+        private final float SIZE_MULTIPLIER;
         // The velocity above which we apply the velocity bias
-        private static final float VELOCITY_THRESHOLD = LOKitShell.getDpi() / 32f;
+        private final float VELOCITY_THRESHOLD;
+        // How much of the buffer to keep in the reverse direction of the velocity
+        private final float REVERSE_BUFFER;
+        // If the visible rect is within the danger zone we start redrawing to minimize
+        // checkerboarding. the danger zone amount is a linear function of the form:
+        //    viewportsize * (base + velocity * incr)
+        // where base and incr are configurable values.
+        private final float DANGER_ZONE_BASE_X_MULTIPLIER;
+        private final float DANGER_ZONE_BASE_Y_MULTIPLIER;
+        private final float DANGER_ZONE_INCR_X_MULTIPLIER;
+        private final float DANGER_ZONE_INCR_Y_MULTIPLIER;
+
+        VelocityBiasStrategy(Map<String, Integer> prefs) {
+            SIZE_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_MULTIPLIER, 2000);
+            VELOCITY_THRESHOLD = LOKitShell.getDpi() * getFloatPref(prefs, PREF_DISPLAYPORT_VB_VELOCITY_THRESHOLD, 32);
+            REVERSE_BUFFER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_REVERSE_BUFFER, 200);
+            DANGER_ZONE_BASE_X_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_DANGER_X_BASE, 1000);
+            DANGER_ZONE_BASE_Y_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_DANGER_Y_BASE, 1000);
+            DANGER_ZONE_INCR_X_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_DANGER_X_INCR, 0);
+            DANGER_ZONE_INCR_Y_MULTIPLIER = getFloatPref(prefs, PREF_DISPLAYPORT_VB_DANGER_Y_INCR, 0);
+        }
+
+        /**
+         * Split the given amounts into margins based on the VELOCITY_THRESHOLD and REVERSE_BUFFER values.
+         * If the velocity is above the VELOCITY_THRESHOLD on an axis, split the amount into REVERSE_BUFFER
+         * and 1.0 - REVERSE_BUFFER fractions. The REVERSE_BUFFER fraction is set as the margin in the
+         * direction opposite to the velocity, and the remaining fraction is set as the margin in the direction
+         * of the velocity. If the velocity is lower than VELOCITY_THRESHOLD, split the amount evenly into the
+         * two margins on that axis.
+         */
+        private RectF velocityBiasedMargins(float xAmount, float yAmount, PointF velocity) {
+            RectF margins = new RectF();
+
+            if (velocity.x > VELOCITY_THRESHOLD) {
+                margins.left = xAmount * REVERSE_BUFFER;
+            } else if (velocity.x < -VELOCITY_THRESHOLD) {
+                margins.left = xAmount * (1.0f - REVERSE_BUFFER);
+            } else {
+                margins.left = xAmount / 2.0f;
+            }
+            margins.right = xAmount - margins.left;
+
+            if (velocity.y > VELOCITY_THRESHOLD) {
+                margins.top = yAmount * REVERSE_BUFFER;
+            } else if (velocity.y < -VELOCITY_THRESHOLD) {
+                margins.top = yAmount * (1.0f - REVERSE_BUFFER);
+            } else {
+                margins.top = yAmount / 2.0f;
+            }
+            margins.bottom = yAmount - margins.top;
+
+            return margins;
+        }
 
         public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
             float displayPortWidth = metrics.getWidth() * SIZE_MULTIPLIER;
@@ -245,43 +402,43 @@ final class DisplayPortCalculator {
             float horizontalBuffer = displayPortWidth - metrics.getWidth();
             float verticalBuffer = displayPortHeight - metrics.getHeight();
 
-            // if we're panning above the VELOCITY_THRESHOLD on an axis, apply the margin so that it
-            // is entirely in the direction of panning. Otherwise, split the margin evenly on both sides of
-            // the display port.
-            RectF margins = new RectF();
-            if (velocity.x > VELOCITY_THRESHOLD) {
-                margins.right = horizontalBuffer;
-            } else if (velocity.x < -VELOCITY_THRESHOLD) {
-                margins.left = horizontalBuffer;
-            } else {
-                margins.left = horizontalBuffer / 2.0f;
-                margins.right = horizontalBuffer - margins.left;
-            }
-            if (velocity.y > VELOCITY_THRESHOLD) {
-                margins.bottom = verticalBuffer;
-            } else if (velocity.y < -VELOCITY_THRESHOLD) {
-                margins.top = verticalBuffer;
-            } else {
-                margins.top = verticalBuffer / 2.0f;
-                margins.bottom = verticalBuffer - margins.top;
-            }
-            // and finally shift the margins to account for page bounds
+            // split the buffer amounts into margins based on velocity, and shift it to
+            // take into account the page bounds
+            RectF margins = velocityBiasedMargins(horizontalBuffer, verticalBuffer, velocity);
             margins = shiftMarginsForPageBounds(margins, metrics);
 
-            return new DisplayPortMetrics(metrics.viewportRectLeft - margins.left,
-                    metrics.viewportRectTop - margins.top,
-                    metrics.viewportRectRight + margins.right,
-                    metrics.viewportRectBottom + margins.bottom,
-                    metrics.zoomFactor);
+            return getTileAlignedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
         }
 
         public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
-            // Since we have such a small margin, we want to be drawing more aggressively. At the start of a
-            // pan the velocity is going to be large so we're almost certainly going to go into checkerboard
-            // on every frame, so drawing all the time seems like the right thing. At the end of the pan we
-            // want to re-center the displayport and draw stuff on all sides, so again we don't want to throttle
-            // there. When we're not panning we're not drawing anyway so it doesn't make a difference there.
-            return true;
+            // calculate the danger zone amounts based on the prefs
+            float dangerZoneX = metrics.getWidth() * (DANGER_ZONE_BASE_X_MULTIPLIER + (velocity.x * DANGER_ZONE_INCR_X_MULTIPLIER));

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list