[Libreoffice-commits] core.git: 11 commits - android/experimental

Tomaž Vajngerl tomaz.vajngerl at collabora.com
Sun Sep 28 13:31:04 PDT 2014


 android/experimental/LOAndroid3/src/java/org/libreoffice/LOEvent.java                        |   10 
 android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java                    |   35 -
 android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitTileProvider.java              |    6 
 android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java        |    3 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ZoomConstraints.java              |   46 ++
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoLayerClient.java         |   20 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java |  170 ++++++-
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerController.java          |  118 +----
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerRenderer.java            |   56 +-
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/MultiTileLayer.java           |   54 --
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/PointUtils.java               |   16 
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/RectUtils.java                |   35 -
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScreenshotLayer.java          |   60 ++
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java          |   95 ----
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java                      |   92 ++--
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java         |  214 ++++------
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomTarget.java             |   24 +
 android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/SubdocumentScrollHelper.java   |   16 
 18 files changed, 589 insertions(+), 481 deletions(-)

New commits:
commit 27953d41222ba0410769b381525c59f4395301e3
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Sun Sep 28 22:30:38 2014 +0200

    android: minimize the rounding error, clean-up MultiTileLayer
    
    Change-Id: Ib167acf5914596b69ee240255aaab173a0570038

diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
index f808f57..ba7102e 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
@@ -28,10 +28,7 @@ public class LOKitThread extends Thread {
     }
 
     private boolean draw() throws InterruptedException {
-        int pageWidth = mTileProvider.getPageWidth();
-        int pageHeight = mTileProvider.getPageHeight();
-
-        RectF rect = new RectF(0, 0, pageWidth, pageHeight);
+        RectF rect = new RectF(0, 0, mTileProvider.getPageWidth(), mTileProvider.getPageHeight());
         DisplayMetrics displayMetrics = LibreOfficeMainActivity.mAppContext.getResources().getDisplayMetrics();
         mViewportMetrics = new ImmutableViewportMetrics(displayMetrics);
         mViewportMetrics = mViewportMetrics.setPageRect(rect, rect);
@@ -72,6 +69,8 @@ public class LOKitThread extends Thread {
         boolean isReady = mTileProvider.isReady();
         if (isReady) {
             updateCheckbardImage();
+            RectF rect = new RectF(0, 0, mTileProvider.getPageWidth(), mTileProvider.getPageHeight());
+            mController.setPageRect(rect, rect);
             mController.setForceRedraw();
         }
         return isReady;
@@ -79,13 +78,9 @@ public class LOKitThread extends Thread {
 
     private void updateCheckbardImage() {
         if (!mCheckboardImageSet) {
-            Log.i(LOGTAG, "Generate thumbnail!");
             Bitmap bitmap = mTileProvider.thumbnail();
-            Log.i(LOGTAG, "Done generate thumbnail!");
             if (bitmap != null) {
-                Log.i(LOGTAG, "Setting checkboard image!");
                 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/libreoffice/LOKitTileProvider.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitTileProvider.java
index 5b1b718..77a833e 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitTileProvider.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitTileProvider.java
@@ -112,7 +112,11 @@ public class LOKitTileProvider implements TileProvider {
         Bitmap bitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Bitmap.Config.ARGB_8888);
 
         if (mDocument != null) {
-            mDocument.paintTile(buffer, TILE_SIZE, TILE_SIZE, Math.round(pixelToTwip(x, mDPI)/zoom), Math.round(pixelToTwip(y, mDPI)/ zoom), Math.round(mTileWidth / zoom), Math.round(mTileHeight/zoom));
+            float twipX = pixelToTwip(x, mDPI) / zoom;
+            float twipY = pixelToTwip(y, mDPI) / zoom;
+            float twipWidth  = mTileWidth / zoom;
+            float twipHeight = mTileHeight / zoom;
+            mDocument.paintTile(buffer, TILE_SIZE, TILE_SIZE, (int) twipX, (int) twipY, (int)twipWidth, (int)twipHeight);
         } else {
             Log.e(LOGTAG, "Document is null!!");
         }
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 c4a445d..14a08e2 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 org.libreoffice.TileProvider;
 import org.mozilla.gecko.util.FloatUtils;
@@ -51,23 +50,16 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
-/**
- * Encapsulates the logic needed to draw a layer made of multiple tiles.
- */
 public class MultiTileLayer extends Layer {
     private static final String LOGTAG = "MultiTileLayer";
 
     private static int TILE_SIZE = 256;
-    private final List<SubTile> mTiles;
+    private final List<SubTile> mTiles = new CopyOnWriteArrayList<SubTile>();
     private TileProvider tileProvider;
-    private float currentZoomFactor;
     private RectF tileViewPort = new RectF();
-    private boolean shouldRefreshZoom = true;
-    private RectF currentPageRect = new RectF();
 
     public MultiTileLayer() {
         super();
-        mTiles = new CopyOnWriteArrayList<SubTile>();
     }
 
     public void invalidate() {
@@ -131,10 +123,10 @@ public class MultiTileLayer extends Layer {
 
             if (origin != null) {
                 Rect position = layer.getPosition();
-                int positionX = origin.x + Math.round(layer.x / layer.zoom);
-                int positionY = origin.y + Math.round(layer.y / layer.zoom);
-                int tileSize = Math.round(256.0f / layer.zoom);
-                position.set(positionX, positionY, positionX + tileSize, positionY + tileSize);
+                float positionX = origin.x + (layer.x / layer.zoom);
+                float positionY = origin.y + (layer.y / layer.zoom);
+                float tileSize = TILE_SIZE / layer.zoom;
+                position.set((int) positionX, (int) positionY, (int) (positionX + tileSize + 1), (int) (positionY + tileSize + 1));
                 layer.setPosition(position);
             }
             if (resolution >= 0.0f) {
@@ -170,10 +162,6 @@ public class MultiTileLayer extends Layer {
         super.endTransaction();
     }
 
-    private RectF normlizeRect(RectF rect, FloatSize pageSize) {
-        return new RectF(rect.left / pageSize.width, rect.top / pageSize.height, rect.right / pageSize.width, rect.bottom / pageSize.height);
-    }
-
     private RectF roundToTileSize(RectF input, int tileSize) {
         float minX = (Math.round(input.left) / tileSize) * tileSize;
         float minY = (Math.round(input.top) / tileSize) * tileSize;
@@ -198,12 +186,6 @@ public class MultiTileLayer extends Layer {
 
     @Override
     public void draw(RenderContext context) {
-        if (tileProvider == null) {
-            return;
-        }
-
-        currentPageRect = context.pageRect;
-
         for (SubTile layer : mTiles) {
             // Avoid work, only draw tiles that intersect with the viewport
             RectF layerBounds = layer.getBounds(context);
@@ -229,19 +211,13 @@ public class MultiTileLayer extends Layer {
     }
 
     public void reevaluateTiles(ImmutableViewportMetrics viewportMetrics) {
-        if (currentZoomFactor != viewportMetrics.zoomFactor) {
-            currentZoomFactor = viewportMetrics.zoomFactor;
-        }
-
         RectF newTileViewPort = inflate(roundToTileSize(viewportMetrics.getViewport(), TILE_SIZE), TILE_SIZE);
 
-        Log.i(LOGTAG, "reevaluateTiles ( " + viewportMetrics + " )");
-
         if (tileViewPort != newTileViewPort) {
             tileViewPort = newTileViewPort;
             cleanTiles();
-            addNewTiles();
-            markTiles();
+            addNewTiles(viewportMetrics);
+            markTiles(viewportMetrics);
         }
     }
 
@@ -256,24 +232,24 @@ public class MultiTileLayer extends Layer {
         mTiles.removeAll(tilesToRemove);
     }
 
-    private void addNewTiles() {
+    private void addNewTiles(ImmutableViewportMetrics viewportMetrics) {
         for (float y = tileViewPort.top; y < tileViewPort.bottom; y += TILE_SIZE) {
-            if (y > currentPageRect.height()) {
+            if (y > viewportMetrics.getPageHeight()) {
                 continue;
             }
             for (float x = tileViewPort.left; x < tileViewPort.right; x += TILE_SIZE) {
-                if (x > currentPageRect.width()) {
+                if (x > viewportMetrics.getPageWidth()) {
                     continue;
                 }
                 boolean contains = false;
                 for (SubTile tile : mTiles) {
-                    if (tile.x == x && tile.y == y) {
+                    if (tile.x == x && tile.y == y && tile.zoom == viewportMetrics.zoomFactor) {
                         contains = true;
                     }
                 }
                 if (!contains) {
-                    CairoImage image = tileProvider.createTile(x, y, currentZoomFactor);
-                    SubTile tile = new SubTile(image, (int)x, (int)y, currentZoomFactor);
+                    CairoImage image = tileProvider.createTile(x, y, viewportMetrics.zoomFactor);
+                    SubTile tile = new SubTile(image, (int)x, (int)y, viewportMetrics.zoomFactor);
                     tile.beginTransaction();
                     mTiles.add(tile);
                 }
@@ -281,9 +257,9 @@ public class MultiTileLayer extends Layer {
         }
     }
 
-    private void markTiles() {
+    private void markTiles(ImmutableViewportMetrics viewportMetrics) {
         for (SubTile tile : mTiles) {
-            if (FloatUtils.fuzzyEquals(tile.zoom, currentZoomFactor)) {
+            if (FloatUtils.fuzzyEquals(tile.zoom, viewportMetrics.zoomFactor)) {
                 RectF tileRect = new RectF(tile.x, tile.y, tile.x + TILE_SIZE, tile.y + TILE_SIZE);
                 if (!RectF.intersects(tileViewPort, tileRect)) {
                     tile.markForRemoval();
commit 295f3c12dfc76b6faa438074bc77f8ac8ecadf06
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Sun Sep 28 21:44:56 2014 +0200

    android: LayerRendered - rename member to mScreenShotLayer
    
    Change-Id: I16fbda06c75bbf80e7d2c2b045418297589c6ff7

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 85ef846..dcedaae 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
@@ -48,7 +48,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
 
     private final LayerView mView;
     private final SingleTileLayer mBackgroundLayer;
-    private final ScreenshotLayer mCheckerboardLayer;
+    private final ScreenshotLayer mScreenshotLayer;
     private final NinePatchTileLayer mShadowLayer;
     private TextLayer mFrameRateLayer;
     private final ScrollbarLayer mHorizScrollLayer;
@@ -82,9 +82,6 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
     private int mSampleHandle;
     private int mTMatrixHandle;
 
-    private int mSurfaceWidth;
-    private int mSurfaceHeight;
-
     // column-major matrix applied to each vertex to shift the viewport from
     // one ranging from (-1, -1),(1,1) to (0,0),(1,1) and to scale all sizes by
     // a factor of 2 to fill up the screen
@@ -129,33 +126,33 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
         "}\n";
 
     public void setCheckerboardBitmap(Bitmap bitmap, float pageWidth, float pageHeight) {
-        mCheckerboardLayer.setBitmap(bitmap);
-        mCheckerboardLayer.beginTransaction();
+        mScreenshotLayer.setBitmap(bitmap);
+        mScreenshotLayer.beginTransaction();
         try {
-            mCheckerboardLayer.setPosition(new Rect(0, 0, Math.round(pageWidth),
+            mScreenshotLayer.setPosition(new Rect(0, 0, Math.round(pageWidth),
                                                     Math.round(pageHeight)));
-            mCheckerboardLayer.invalidate();
+            mScreenshotLayer.invalidate();
         } finally {
-            mCheckerboardLayer.endTransaction();
+            mScreenshotLayer.endTransaction();
         }
     }
 
     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();
+        mScreenshotLayer.updateBitmap(bitmap, x, y, width, height);
+        mScreenshotLayer.beginTransaction();
         try {
-            mCheckerboardLayer.setPosition(new Rect(0, 0, Math.round(pageWidth),
+            mScreenshotLayer.setPosition(new Rect(0, 0, Math.round(pageWidth),
                                                     Math.round(pageHeight)));
-            mCheckerboardLayer.invalidate();
+            mScreenshotLayer.invalidate();
         } finally {
-            mCheckerboardLayer.endTransaction();
+            mScreenshotLayer.endTransaction();
         }
     }
 
     public void resetCheckerboard() {
-        mCheckerboardLayer.reset();
+        mScreenshotLayer.reset();
     }
 
     public LayerRenderer(LayerView view) {
@@ -166,7 +163,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
         CairoImage backgroundImage = new BufferedCairoImage(controller.getBackgroundPattern());
         mBackgroundLayer = new SingleTileLayer(true, backgroundImage);
 
-        mCheckerboardLayer = ScreenshotLayer.create();
+        mScreenshotLayer = ScreenshotLayer.create();
 
         CairoImage shadowImage = new BufferedCairoImage(controller.getShadowPattern());
         mShadowLayer = new NinePatchTileLayer(shadowImage);
@@ -196,6 +193,20 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
         }
     }
 
+    public void destroy() {
+        DirectBufferAllocator.free(mCoordByteBuffer);
+        mCoordByteBuffer = null;
+        mCoordBuffer = null;
+        mScreenshotLayer.destroy();
+        mBackgroundLayer.destroy();
+        mShadowLayer.destroy();
+        mHorizScrollLayer.destroy();
+        mVertScrollLayer.destroy();
+        if (mFrameRateLayer != null) {
+            mFrameRateLayer.destroy();
+        }
+    }
+
     public void onSurfaceCreated(GL10 gl, EGLConfig config) {
         checkMonitoringEnabled();
         createDefaultProgram();
@@ -322,9 +333,6 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
     }
 
     public void onSurfaceChanged(GL10 gl, final int width, final int height) {
-        mSurfaceWidth = width;
-        mSurfaceHeight = height;
-
         GLES20.glViewport(0, 0, width, height);
 
         if (mFrameRateLayer != null) {
@@ -510,7 +518,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
             if (rootLayer != null) mUpdated &= rootLayer.update(mPageContext);  // called on compositor thread
             mUpdated &= mBackgroundLayer.update(mScreenContext);    // called on compositor thread
             mUpdated &= mShadowLayer.update(mPageContext);  // called on compositor thread
-            mUpdated &= mCheckerboardLayer.update(mPageContext);   // called on compositor thread
+            mUpdated &= mScreenshotLayer.update(mPageContext);   // called on compositor thread
             if (mFrameRateLayer != null) mUpdated &= mFrameRateLayer.update(mScreenContext); // called on compositor thread
             mUpdated &= mVertScrollLayer.update(mPageContext);  // called on compositor thread
             mUpdated &= mHorizScrollLayer.update(mPageContext); // called on compositor thread
@@ -591,13 +599,13 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
             if (mView.getController().checkerboardShouldShowChecks()) {
                 /* Find the area the root layer will render into, to mask the checkerboard layer */
                 Rect rootMask = getMaskForLayer(mView.getController().getRoot());
-                mCheckerboardLayer.setMask(rootMask);
+                mScreenshotLayer.setMask(rootMask);
 
                 /* Scissor around the page-rect, in case the page has shrunk
                  * since the screenshot layer was last updated.
                  */
                 setScissorRect(); // Calls glEnable(GL_SCISSOR_TEST))
-                mCheckerboardLayer.draw(mPageContext);
+                mScreenshotLayer.draw(mPageContext);
             }
         }
 
@@ -644,7 +652,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
 
                 /* restrict the viewport to page bounds so we don't
                  * count overscroll as checkerboard */
-                if (!viewport.intersect(0, 0, mPageRect.width(), mPageRect.height())) {
+                if (!viewport.intersect(mPageRect)) {
                     /* if the rectangles don't intersect
                        intersect() doesn't change viewport
                        so we set it to empty by hand */
commit d935e7be2c8818d028ab5ce718e7c9c5126cbf32
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Sun Sep 28 11:58:58 2014 +0200

    android: revert ScreenshotLayer draw method back
    
    Own draw method expands the picture correctly but for some reason
    the SingleTileLayer draw method does not so revert back.
    
    Change-Id: Ie8e39fc46b84ae410439a781928ff79bc1503d10

diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScreenshotLayer.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScreenshotLayer.java
index 5f82213..c52b39b 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScreenshotLayer.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ScreenshotLayer.java
@@ -8,10 +8,13 @@ package org.mozilla.gecko.gfx;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Rect;
+import android.opengl.GLES20;
 
 import org.libreoffice.kit.DirectBufferAllocator;
 
 import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
 
 public class ScreenshotLayer extends SingleTileLayer {
     private static final int SCREENSHOT_SIZE_LIMIT = 1048576;
@@ -75,8 +78,61 @@ public class ScreenshotLayer extends SingleTileLayer {
 
     @Override
     public void draw(RenderContext context) {
-        if (mHasImage)
-            super.draw(context);
+        // mTextureIDs may be null here during startup if Layer.java's draw method
+        // failed to acquire the transaction lock and call performUpdates.
+        if (!initialized())
+            return;
+
+        float txl, txr, txb, txt;
+
+        Rect position = getPosition();
+
+        float bw = mBufferSize.width;
+        float bh = mBufferSize.height;
+        float iw = mImageSize.width;
+        float ih = mImageSize.height;
+
+        float pw = context.pageRect.width();
+        float ph = context.pageRect.height();
+
+        float vl = context.viewport.left;
+        float vr = context.viewport.right;
+        float vt = context.viewport.top;
+        float vb = context.viewport.bottom;
+
+        float vw =  vr - vl;
+        float vh =  vb - vt;
+
+        txl = (iw/bw) * (vl / pw);
+        txr = (iw/bw) * (vr / pw);
+        txt = 1.0f - ((ih/bh) * (vt / ph));
+        txb = 1.0f - ((ih/bh) * (vb / ph));
+
+        float[] coords = {
+                0.0f, 0.0f, 0.0f, txl, txb,
+                0.0f, 1.0f, 0.0f, txl, txt,
+                1.0f, 0.0f, 0.0f, txr, txb,
+                1.0f, 1.0f, 0.0f, txr, txt,
+        };
+
+        FloatBuffer coordBuffer = context.coordBuffer;
+        int positionHandle = context.positionHandle;
+        int textureHandle = context.textureHandle;
+
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID());
+
+        // Make sure we are at position zero in the buffer
+        coordBuffer.position(0);
+        coordBuffer.put(coords);
+
+        // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+        coordBuffer.position(0);
+        GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer);
+
+        // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
+        coordBuffer.position(3);
+        GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer);
+        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
     }
 
     /** A Cairo image that simply saves a buffer of pixel data. */
commit ebc25c427936708b8e32e563d6735575d6ac9f92
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Sat Sep 27 23:38:19 2014 +0200

    android: replace with ImmutableViewportMetrics (Fennec import)
    
    Change-Id: I46509f8be4dc49dac45eb98059dad25e150988dd

diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOEvent.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOEvent.java
index f16b2da..80210a7 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOEvent.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOEvent.java
@@ -2,8 +2,8 @@ package org.libreoffice;
 
 import android.graphics.Rect;
 
+import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.IntSize;
-import org.mozilla.gecko.gfx.ViewportMetrics;
 
 public class LOEvent {
 
@@ -15,7 +15,7 @@ public class LOEvent {
     public static final int LOAD = 6;
 
     public int mType;
-    private ViewportMetrics mViewportMetrics;
+    private ImmutableViewportMetrics mViewportMetrics;
     private String mTypeString;
     private int mPartIndex;
     private String mFilename;
@@ -35,7 +35,7 @@ public class LOEvent {
         mTypeString = "Tile size";
     }
 
-    public LOEvent(int type, ViewportMetrics viewportMetrics) {
+    public LOEvent(int type, ImmutableViewportMetrics viewportMetrics) {
         mType = type;
         mTypeString = "Viewport";
         mViewportMetrics = viewportMetrics;
@@ -64,7 +64,7 @@ public class LOEvent {
         return new LOEvent(TILE_SIZE, tileSize);
     }
 
-    public static LOEvent viewport(ViewportMetrics viewportMetrics) {
+    public static LOEvent viewport(ImmutableViewportMetrics viewportMetrics) {
         return new LOEvent(VIEWPORT, viewportMetrics);
     }
 
@@ -80,7 +80,7 @@ public class LOEvent {
         return mTypeString;
     }
 
-    public ViewportMetrics getViewport() {
+    public ImmutableViewportMetrics getViewport() {
         return mViewportMetrics;
     }
 
diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
index ddc18d4..f808f57 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
@@ -7,8 +7,8 @@ import android.util.DisplayMetrics;
 import android.util.Log;
 
 import org.mozilla.gecko.gfx.GeckoLayerClient;
+import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.LayerController;
-import org.mozilla.gecko.gfx.ViewportMetrics;
 
 import java.util.concurrent.LinkedBlockingQueue;
 
@@ -18,7 +18,7 @@ public class LOKitThread extends Thread {
     public LinkedBlockingQueue<LOEvent> mEventQueue = new LinkedBlockingQueue<LOEvent>();
     private LibreOfficeMainActivity mApplication;
     private TileProvider mTileProvider;
-    private ViewportMetrics mViewportMetrics;
+    private ImmutableViewportMetrics mViewportMetrics;
     private boolean mCheckboardImageSet = false;
     private GeckoLayerClient mLayerClient;
     private LayerController mController;
@@ -33,8 +33,8 @@ public class LOKitThread extends Thread {
 
         RectF rect = new RectF(0, 0, pageWidth, pageHeight);
         DisplayMetrics displayMetrics = LibreOfficeMainActivity.mAppContext.getResources().getDisplayMetrics();
-        mViewportMetrics = new ViewportMetrics(displayMetrics);
-        mViewportMetrics.setPageRect(rect, rect);
+        mViewportMetrics = new ImmutableViewportMetrics(displayMetrics);
+        mViewportMetrics = mViewportMetrics.setPageRect(rect, rect);
 
         GeckoLayerClient layerClient = mApplication.getLayerClient();
 
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 e3f5793..e847c01 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
@@ -65,10 +65,10 @@ public class GeckoLayerClient implements LayerView.Listener {
     private MultiTileLayer mRootLayer;
 
     /* The viewport that Gecko is currently displaying. */
-    private ViewportMetrics mGeckoViewport;
+    private ImmutableViewportMetrics mGeckoViewport;
 
     /* The viewport that Gecko will display when drawing is finished */
-    private ViewportMetrics mNewGeckoViewport;
+    private ImmutableViewportMetrics mNewGeckoViewport;
     private Context mContext;
     private boolean mPendingViewportAdjust;
     private boolean mViewportSizeChanged;
@@ -109,7 +109,7 @@ public class GeckoLayerClient implements LayerView.Listener {
 
     }
 
-    public void endDrawing(ViewportMetrics viewportMetrics) {
+    public void endDrawing(ImmutableViewportMetrics viewportMetrics) {
         synchronized (mLayerController) {
             try {
                 mNewGeckoViewport = viewportMetrics;
@@ -128,19 +128,18 @@ public class GeckoLayerClient implements LayerView.Listener {
         // java is the One True Source of this information, and allowing JS
         // to override can lead to race conditions where this data gets clobbered.
         FloatSize viewportSize = mLayerController.getViewportSize();
-        mGeckoViewport = mNewGeckoViewport;
-        mGeckoViewport.setSize(viewportSize);
+        mGeckoViewport = mNewGeckoViewport.setViewportSize(viewportSize.width, viewportSize.height);
 
         RectF position = mGeckoViewport.getViewport();
         mRootLayer.setPosition(RectUtils.round(position));
-        mRootLayer.setResolution(mGeckoViewport.getZoomFactor());
+        mRootLayer.setResolution(mGeckoViewport.zoomFactor);
 
         Log.e(LOGTAG, "### updateViewport onlyUpdatePageSize=" + onlyUpdatePageSize + " getTileViewport " + mGeckoViewport);
 
         if (onlyUpdatePageSize) {
             // Don't adjust page size when zooming unless zoom levels are
             // approximately equal.
-            if (FloatUtils.fuzzyEquals(mLayerController.getViewportMetrics().zoomFactor, mGeckoViewport.getZoomFactor())) {
+            if (FloatUtils.fuzzyEquals(mLayerController.getViewportMetrics().zoomFactor, mGeckoViewport.zoomFactor)) {
                 mLayerController.setPageRect(mGeckoViewport.getPageRect(), mGeckoViewport.getCssPageRect());
             }
         } else {
@@ -193,8 +192,7 @@ public class GeckoLayerClient implements LayerView.Listener {
     void adjustViewport(DisplayPortMetrics displayPort) {
         ImmutableViewportMetrics metrics = mLayerController.getViewportMetrics();
 
-        ViewportMetrics clampedMetrics = new ViewportMetrics(metrics);
-        clampedMetrics.setViewport(clampedMetrics.getClampedViewport());
+        ImmutableViewportMetrics clampedMetrics = metrics.clamp();
 
         if (displayPort == null) {
             displayPort = DisplayPortCalculator.calculate(metrics,
@@ -244,7 +242,7 @@ public class GeckoLayerClient implements LayerView.Listener {
         }
     }
 
-    public ViewportMetrics getGeckoViewportMetrics() {
+    public ImmutableViewportMetrics getGeckoViewportMetrics() {
         return mGeckoViewport;
     }
 
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java
index 5403c80..35b4175 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ImmutableViewportMetrics.java
@@ -7,6 +7,9 @@ package org.mozilla.gecko.gfx;
 
 import android.graphics.PointF;
 import android.graphics.RectF;
+import android.util.DisplayMetrics;
+
+import org.mozilla.gecko.util.FloatUtils;
 
 /**
  * ImmutableViewportMetrics are used to store the viewport metrics
@@ -31,26 +34,33 @@ public class ImmutableViewportMetrics {
     public final float viewportRectBottom;
     public final float zoomFactor;
 
-    public ImmutableViewportMetrics(ViewportMetrics m) {
-        RectF viewportRect = m.getViewport();
-        viewportRectLeft = viewportRect.left;
-        viewportRectTop = viewportRect.top;
-        viewportRectRight = viewportRect.right;
-        viewportRectBottom = viewportRect.bottom;
-
-        RectF pageRect = m.getPageRect();
-        pageRectLeft = pageRect.left;
-        pageRectTop = pageRect.top;
-        pageRectRight = pageRect.right;
-        pageRectBottom = pageRect.bottom;
-
-        RectF cssPageRect = m.getCssPageRect();
-        cssPageRectLeft = cssPageRect.left;
-        cssPageRectTop = cssPageRect.top;
-        cssPageRectRight = cssPageRect.right;
-        cssPageRectBottom = cssPageRect.bottom;
+    public ImmutableViewportMetrics(DisplayMetrics metrics) {
+        viewportRectLeft   = pageRectLeft   = cssPageRectLeft   = 0;
+        viewportRectTop    = pageRectTop    = cssPageRectTop    = 0;
+        viewportRectRight  = pageRectRight  = cssPageRectRight  = metrics.widthPixels;
+        viewportRectBottom = pageRectBottom = cssPageRectBottom = metrics.heightPixels;
+        zoomFactor = 1.0f;
+    }
 
-        zoomFactor = m.getZoomFactor();
+    private ImmutableViewportMetrics(float aPageRectLeft, float aPageRectTop,
+        float aPageRectRight, float aPageRectBottom, float aCssPageRectLeft,
+        float aCssPageRectTop, float aCssPageRectRight, float aCssPageRectBottom,
+        float aViewportRectLeft, float aViewportRectTop, float aViewportRectRight,
+        float aViewportRectBottom, float aZoomFactor)
+    {
+        pageRectLeft = aPageRectLeft;
+        pageRectTop = aPageRectTop;
+        pageRectRight = aPageRectRight;
+        pageRectBottom = aPageRectBottom;
+        cssPageRectLeft = aCssPageRectLeft;
+        cssPageRectTop = aCssPageRectTop;
+        cssPageRectRight = aCssPageRectRight;
+        cssPageRectBottom = aCssPageRectBottom;
+        viewportRectLeft = aViewportRectLeft;
+        viewportRectTop = aViewportRectTop;
+        viewportRectRight = aViewportRectRight;
+        viewportRectBottom = aViewportRectBottom;
+        zoomFactor = aZoomFactor;
     }
 
     public float getWidth() {
@@ -61,8 +71,6 @@ public class ImmutableViewportMetrics {
         return viewportRectBottom - viewportRectTop;
     }
 
-    // some helpers to make ImmutableViewportMetrics act more like ViewportMetrics
-
     public PointF getOrigin() {
         return new PointF(viewportRectLeft, viewportRectTop);
     }
@@ -98,6 +106,126 @@ public class ImmutableViewportMetrics {
         return new RectF(cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom);
     }
 
+    /*
+     * Returns the viewport metrics that represent a linear transition between "this" and "to" at
+     * time "t", which is on the scale [0, 1). This function interpolates all values stored in
+     * the viewport metrics.
+     */
+    public ImmutableViewportMetrics interpolate(ImmutableViewportMetrics to, float t) {
+        return new ImmutableViewportMetrics(
+            FloatUtils.interpolate(pageRectLeft, to.pageRectLeft, t),
+            FloatUtils.interpolate(pageRectTop, to.pageRectTop, t),
+            FloatUtils.interpolate(pageRectRight, to.pageRectRight, t),
+            FloatUtils.interpolate(pageRectBottom, to.pageRectBottom, t),
+            FloatUtils.interpolate(cssPageRectLeft, to.cssPageRectLeft, t),
+            FloatUtils.interpolate(cssPageRectTop, to.cssPageRectTop, t),
+            FloatUtils.interpolate(cssPageRectRight, to.cssPageRectRight, t),
+            FloatUtils.interpolate(cssPageRectBottom, to.cssPageRectBottom, t),
+            FloatUtils.interpolate(viewportRectLeft, to.viewportRectLeft, t),
+            FloatUtils.interpolate(viewportRectTop, to.viewportRectTop, t),
+            FloatUtils.interpolate(viewportRectRight, to.viewportRectRight, t),
+            FloatUtils.interpolate(viewportRectBottom, to.viewportRectBottom, t),
+            FloatUtils.interpolate(zoomFactor, to.zoomFactor, t));
+    }
+
+    public ImmutableViewportMetrics setViewportSize(float width, float height) {
+        return new ImmutableViewportMetrics(
+            pageRectLeft, pageRectTop, pageRectRight, pageRectBottom,
+            cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
+            viewportRectLeft, viewportRectTop, viewportRectLeft + width, viewportRectTop + height,
+            zoomFactor);
+    }
+
+    public ImmutableViewportMetrics setViewportOrigin(float newOriginX, float newOriginY) {
+        return new ImmutableViewportMetrics(
+            pageRectLeft, pageRectTop, pageRectRight, pageRectBottom,
+            cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
+            newOriginX, newOriginY, newOriginX + getWidth(), newOriginY + getHeight(),
+            zoomFactor);
+    }
+
+    public ImmutableViewportMetrics setZoomFactor(float newZoomFactor) {
+        return new ImmutableViewportMetrics(
+            pageRectLeft, pageRectTop, pageRectRight, pageRectBottom,
+            cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
+            viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom,
+            newZoomFactor);
+    }
+
+    public ImmutableViewportMetrics offsetViewportBy(float dx, float dy) {
+        return setViewportOrigin(viewportRectLeft + dx, viewportRectTop + dy);
+    }
+
+    public ImmutableViewportMetrics setPageRect(RectF pageRect, RectF cssPageRect) {
+        return new ImmutableViewportMetrics(
+            pageRect.left, pageRect.top, pageRect.right, pageRect.bottom,
+            cssPageRect.left, cssPageRect.top, cssPageRect.right, cssPageRect.bottom,
+            viewportRectLeft, viewportRectTop, viewportRectRight, viewportRectBottom,
+            zoomFactor);
+    }
+
+    /* This will set the zoom factor and re-scale page-size and viewport offset
+     * accordingly. The given focus will remain at the same point on the screen
+     * after scaling.
+     */
+    public ImmutableViewportMetrics scaleTo(float newZoomFactor, PointF focus) {
+        // cssPageRect* is invariant, since we're setting the scale factor
+        // here. The page rect is based on the CSS page rect.
+        float newPageRectLeft = cssPageRectLeft * newZoomFactor;
+        float newPageRectTop = cssPageRectTop * newZoomFactor;
+        float newPageRectRight = cssPageRectLeft + ((cssPageRectRight - cssPageRectLeft) * newZoomFactor);
+        float newPageRectBottom = cssPageRectTop + ((cssPageRectBottom - cssPageRectTop) * newZoomFactor);
+
+        PointF origin = getOrigin();
+        origin.offset(focus.x, focus.y);
+        origin = PointUtils.scale(origin, newZoomFactor / zoomFactor);
+        origin.offset(-focus.x, -focus.y);
+
+        return new ImmutableViewportMetrics(
+            newPageRectLeft, newPageRectTop, newPageRectRight, newPageRectBottom,
+            cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
+            origin.x, origin.y, origin.x + getWidth(), origin.y + getHeight(),
+            newZoomFactor);
+    }
+
+    /** Clamps the viewport to remain within the page rect. */
+    public ImmutableViewportMetrics clamp() {
+        RectF newViewport = getViewport();
+
+        // The viewport bounds ought to never exceed the page bounds.
+        if (newViewport.right > pageRectRight)
+            newViewport.offset(pageRectRight - newViewport.right, 0);
+        if (newViewport.left < pageRectLeft)
+            newViewport.offset(pageRectLeft - newViewport.left, 0);
+
+        if (newViewport.bottom > pageRectBottom)
+            newViewport.offset(0, pageRectBottom - newViewport.bottom);
+        if (newViewport.top < pageRectTop)
+            newViewport.offset(0, pageRectTop - newViewport.top);
+
+        return new ImmutableViewportMetrics(
+            pageRectLeft, pageRectTop, pageRectRight, pageRectBottom,
+            cssPageRectLeft, cssPageRectTop, cssPageRectRight, cssPageRectBottom,
+            newViewport.left, newViewport.top, newViewport.right, newViewport.bottom,
+            zoomFactor);
+    }
+
+    public boolean fuzzyEquals(ImmutableViewportMetrics other) {
+        return FloatUtils.fuzzyEquals(pageRectLeft, other.pageRectLeft)
+            && FloatUtils.fuzzyEquals(pageRectTop, other.pageRectTop)
+            && FloatUtils.fuzzyEquals(pageRectRight, other.pageRectRight)
+            && FloatUtils.fuzzyEquals(pageRectBottom, other.pageRectBottom)
+            && FloatUtils.fuzzyEquals(cssPageRectLeft, other.cssPageRectLeft)
+            && FloatUtils.fuzzyEquals(cssPageRectTop, other.cssPageRectTop)
+            && FloatUtils.fuzzyEquals(cssPageRectRight, other.cssPageRectRight)
+            && FloatUtils.fuzzyEquals(cssPageRectBottom, other.cssPageRectBottom)
+            && FloatUtils.fuzzyEquals(viewportRectLeft, other.viewportRectLeft)
+            && FloatUtils.fuzzyEquals(viewportRectTop, other.viewportRectTop)
+            && FloatUtils.fuzzyEquals(viewportRectRight, other.viewportRectRight)
+            && FloatUtils.fuzzyEquals(viewportRectBottom, other.viewportRectBottom)
+            && FloatUtils.fuzzyEquals(zoomFactor, other.zoomFactor);
+    }
+
     @Override
     public String toString() {
         return "ImmutableViewportMetrics v=(" + viewportRectLeft + "," + viewportRectTop + ","
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 ca02e1c..2e6a4a0 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
@@ -67,7 +67,7 @@ public class LayerController implements PanZoomTarget {
         mContext = context;
         mForceRedraw = true;
         DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
-        mViewportMetrics = new ImmutableViewportMetrics(new ViewportMetrics(displayMetrics));
+        mViewportMetrics = new ImmutableViewportMetrics(displayMetrics);
         mPanZoomController = new PanZoomController(this);
         mView = new LayerView(context, this);
         mCheckerboardShouldShowChecks = true;
@@ -127,9 +127,7 @@ public class LayerController implements PanZoomTarget {
      * result in an infinite loop.
      */
     public void setViewportSize(FloatSize size) {
-        ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
-        viewportMetrics.setSize(size);
-        mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
+        mViewportMetrics = mViewportMetrics.setViewportSize(size.width, size.height);
 
         if (mLayerClient != null) {
             mLayerClient.viewportSizeChanged();
@@ -144,9 +142,7 @@ public class LayerController implements PanZoomTarget {
         if (mViewportMetrics.getCssPageRect().equals(cssRect))
             return;
 
-        ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
-        viewportMetrics.setPageRect(rect, cssRect);
-        mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
+        mViewportMetrics = mViewportMetrics.setPageRect(rect, cssRect);
 
         // Page size is owned by the layer client, so no need to notify it of
         // this change.
@@ -163,20 +159,19 @@ public class LayerController implements PanZoomTarget {
      * Sets the entire viewport metrics at once.
      * You must hold the monitor while calling this.
      */
-    public void setViewportMetrics(ViewportMetrics viewport) {
-        mViewportMetrics = new ImmutableViewportMetrics(viewport);
+    public void setViewportMetrics(ImmutableViewportMetrics viewport) {
+        mViewportMetrics = viewport;
         mView.requestRender();
         notifyLayerClientOfGeometryChange();
     }
 
-    public void setAnimationTarget(ViewportMetrics viewport) {
+    public void setAnimationTarget(ImmutableViewportMetrics viewport) {
         if (mLayerClient != null) {
             // We know what the final viewport of the animation is going to be, so
             // immediately request a draw of that area by setting the display port
             // accordingly. This way we should have the content pre-rendered by the
             // time the animation is done.
-            ImmutableViewportMetrics metrics = new ImmutableViewportMetrics(viewport);
-            DisplayPortMetrics displayPort = DisplayPortCalculator.calculate(metrics, null);
+            DisplayPortMetrics displayPort = DisplayPortCalculator.calculate(viewport, null);
             mLayerClient.adjustViewport(displayPort);
         }
     }
@@ -232,9 +227,9 @@ public class LayerController implements PanZoomTarget {
         ImmutableViewportMetrics viewportMetrics = mViewportMetrics;
         PointF origin = viewportMetrics.getOrigin();
         float zoom = viewportMetrics.zoomFactor;
-        ViewportMetrics geckoViewport = mLayerClient.getGeckoViewportMetrics();
+        ImmutableViewportMetrics geckoViewport = mLayerClient.getGeckoViewportMetrics();
         PointF geckoOrigin = geckoViewport.getOrigin();
-        float geckoZoom = geckoViewport.getZoomFactor();
+        float geckoZoom = geckoViewport.zoomFactor;
 
         // viewPoint + origin gives the coordinate in device pixels from the top-left corner of the page.
         // Divided by zoom, this gives us the coordinate in CSS pixels from the top-left corner of the page.
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/PointUtils.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/PointUtils.java
index cdacc31..4eb07a3 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/PointUtils.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/PointUtils.java
@@ -10,7 +10,6 @@ import android.graphics.PointF;
 
 import org.json.JSONException;
 import org.json.JSONObject;
-import org.mozilla.gecko.util.FloatUtils;
 
 public final class PointUtils {
     public static PointF add(PointF one, PointF two) {
@@ -29,19 +28,10 @@ public final class PointUtils {
         return new Point(Math.round(point.x), Math.round(point.y));
     }
 
-    /* Returns a new point that is a linear interpolation between start and end points. weight conrols the weighting
-     * of each of the original points (weight = 1 returns endPoint, weight = 0 returns startPoint)
-     */
-    public static PointF interpolate(PointF startPoint, PointF endPoint, float weight) {
-        float x = FloatUtils.interpolate(startPoint.x, endPoint.x, weight);
-        float y = FloatUtils.interpolate(startPoint.y, endPoint.y, weight);
-        return new PointF(x, y);
-    }
-
-    /* Computes the magnitude of the given vector. */
-    public static float distance(PointF point) {
+   /* Computes the magnitude of the given vector. */
+   public static float distance(PointF point) {
         return (float)Math.sqrt(point.x * point.x + point.y * point.y);
-    }
+   }
 
     /** Computes the scalar distance between two points. */
     public static float distance(PointF one, PointF two) {
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/RectUtils.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/RectUtils.java
index 92ea78f..1608e91 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/RectUtils.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/RectUtils.java
@@ -10,22 +10,10 @@ import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 
-import org.json.JSONException;
-import org.json.JSONObject;
 import org.mozilla.gecko.util.FloatUtils;
 
 public final class RectUtils {
-    public static Rect create(JSONObject json) {
-        try {
-            int x = json.getInt("x");
-            int y = json.getInt("y");
-            int width = json.getInt("width");
-            int height = json.getInt("height");
-            return new Rect(x, y, x + width, y + height);
-        } catch (JSONException e) {
-            throw new RuntimeException(e);
-        }
-    }
+    private RectUtils() {}
 
     public static RectF expand(RectF rect, float moreWidth, float moreHeight) {
         float halfMoreWidth = moreWidth / 2;
@@ -63,8 +51,14 @@ public final class RectUtils {
 
     /** Returns the nearest integer rect of the given rect. */
     public static Rect round(RectF rect) {
-        return new Rect(Math.round(rect.left), Math.round(rect.top),
-                        Math.round(rect.right), Math.round(rect.bottom));
+        Rect r = new Rect();
+        round(rect, r);
+        return r;
+    }
+
+    public static void round(RectF rect, Rect dest) {
+        dest.set(Math.round(rect.left), Math.round(rect.top),
+                 Math.round(rect.right), Math.round(rect.bottom));
     }
 
     public static Rect roundIn(RectF rect) {
@@ -84,17 +78,6 @@ public final class RectUtils {
         return new PointF(rect.left, rect.top);
     }
 
-    /*
-     * Returns the rect that represents a linear transition between `from` and `to` at time `t`,
-     * which is on the scale [0, 1).
-     */
-    public static RectF interpolate(RectF from, RectF to, float t) {
-        return new RectF(FloatUtils.interpolate(from.left, to.left, t),
-                         FloatUtils.interpolate(from.top, to.top, t),
-                         FloatUtils.interpolate(from.right, to.right, t),
-                         FloatUtils.interpolate(from.bottom, to.bottom, t));
-    }
-
     public static boolean fuzzyEquals(RectF a, RectF b) {
         if (a == null && b == null)
             return true;
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java
index 789bb0b..f8b5c2e0 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/ViewportMetrics.java
@@ -11,7 +11,6 @@ import android.util.DisplayMetrics;
 
 import org.json.JSONException;
 import org.json.JSONObject;
-import org.mozilla.gecko.util.FloatUtils;
 
 /**
  * ViewportMetrics manages state and contains some utility functions related to
@@ -76,6 +75,16 @@ public class ViewportMetrics {
         mZoomFactor = zoom;
     }
 
+    public ViewportMetrics(float x, float y, float width, float height,
+                           float pageLeft, float pageTop, float pageRight, float pageBottom,
+                           float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom,
+                           float zoom) {
+        mPageRect = new RectF(pageLeft, pageTop, pageRight, pageBottom);
+        mCssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
+        mViewportRect = new RectF(x, y, x + width, y + height);
+        mZoomFactor = zoom;
+    }
+
     public PointF getOrigin() {
         return new PointF(mViewportRect.left, mViewportRect.top);
     }
@@ -92,24 +101,6 @@ public class ViewportMetrics {
         return RectUtils.scale(mViewportRect, 1/mZoomFactor);
     }
 
-    /** Returns the viewport rectangle, clamped within the page-size. */
-    public RectF getClampedViewport() {
-        RectF clampedViewport = new RectF(mViewportRect);
-
-        // The viewport bounds ought to never exceed the page bounds.
-        if (clampedViewport.right > mPageRect.right)
-            clampedViewport.offset(mPageRect.right - clampedViewport.right, 0);
-        if (clampedViewport.left < mPageRect.left)
-            clampedViewport.offset(mPageRect.left - clampedViewport.left, 0);
-
-        if (clampedViewport.bottom > mPageRect.bottom)
-            clampedViewport.offset(0, mPageRect.bottom - clampedViewport.bottom);
-        if (clampedViewport.top < mPageRect.top)
-            clampedViewport.offset(0, mPageRect.top - clampedViewport.top);
-
-        return clampedViewport;
-    }
-
     public RectF getPageRect() {
         return mPageRect;
     }
@@ -146,48 +137,6 @@ public class ViewportMetrics {
         mZoomFactor = zoomFactor;
     }
 
-    /* This will set the zoom factor and re-scale page-size and viewport offset
-     * accordingly. The given focus will remain at the same point on the screen
-     * after scaling.
-     */
-    public void scaleTo(float newZoomFactor, PointF focus) {
-        // mCssPageRect is invariant, since we're setting the scale factor
-        // here. The page rect is based on the CSS page rect.
-        mPageRect = RectUtils.scale(mCssPageRect, newZoomFactor);
-
-        float scaleFactor = newZoomFactor / mZoomFactor;
-        PointF origin = getOrigin();
-
-        origin.offset(focus.x, focus.y);
-        origin = PointUtils.scale(origin, scaleFactor);
-        origin.offset(-focus.x, -focus.y);
-
-        setOrigin(origin);
-
-        mZoomFactor = newZoomFactor;
-    }
-
-    /*
-     * Returns the viewport metrics that represent a linear transition between `from` and `to` at
-     * time `t`, which is on the scale [0, 1). This function interpolates the viewport rect, the
-     * page size, the offset, and the zoom factor.
-     */
-    public ViewportMetrics interpolate(ViewportMetrics to, float t) {
-        ViewportMetrics result = new ViewportMetrics(this);
-        result.mPageRect = RectUtils.interpolate(mPageRect, to.mPageRect, t);
-        result.mCssPageRect = RectUtils.interpolate(mCssPageRect, to.mCssPageRect, t);
-        result.mZoomFactor = FloatUtils.interpolate(mZoomFactor, to.mZoomFactor, t);
-        result.mViewportRect = RectUtils.interpolate(mViewportRect, to.mViewportRect, t);
-        return result;
-    }
-
-    public boolean fuzzyEquals(ViewportMetrics other) {
-        return RectUtils.fuzzyEquals(mPageRect, other.mPageRect)
-            && RectUtils.fuzzyEquals(mCssPageRect, other.mCssPageRect)
-            && RectUtils.fuzzyEquals(mViewportRect, other.mViewportRect)
-            && FloatUtils.fuzzyEquals(mZoomFactor, other.mZoomFactor);
-    }
-
     public String toJSON() {
         // Round off height and width. Since the height and width are the size of the screen, it
         // makes no sense to send non-integer coordinates to Gecko.
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 02e45da..555c7ebe 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
@@ -16,7 +16,6 @@ import org.libreoffice.LOKitShell;
 import org.libreoffice.LibreOfficeMainActivity;
 import org.mozilla.gecko.ZoomConstraints;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
-import org.mozilla.gecko.gfx.ViewportMetrics;
 import org.mozilla.gecko.util.FloatUtils;
 
 import java.util.Timer;
@@ -118,10 +117,6 @@ public class PanZoomController
         return mTarget.getViewportMetrics();
     }
 
-    private ViewportMetrics getMutableMetrics() {
-        return new ViewportMetrics(getMetrics());
-    }
-
     // for debugging bug 713011; it can be taken out once that is resolved.
     private void checkMainThread() {
         if (mMainThread != Thread.currentThread()) {
@@ -195,8 +190,8 @@ public class PanZoomController
     public void pageRectUpdated() {
         if (mState == PanZoomState.NOTHING) {
             synchronized (mTarget.getLock()) {
-                ViewportMetrics validated = getValidViewportMetrics();
-                if (! getMutableMetrics().fuzzyEquals(validated)) {
+                ImmutableViewportMetrics validated = getValidViewportMetrics();
+                if (!getMetrics().fuzzyEquals(validated)) {
                     // page size changed such that we are now in overscroll. snap to the
                     // the nearest valid viewport
                     mTarget.setViewportMetrics(validated);
@@ -421,13 +416,9 @@ public class PanZoomController
         updatePosition();
     }
 
-    private void scrollBy(PointF point) {
-        ViewportMetrics viewportMetrics = getMutableMetrics();
-        PointF origin = viewportMetrics.getOrigin();
-        origin.offset(point.x, point.y);
-        viewportMetrics.setOrigin(origin);
-
-        mTarget.setViewportMetrics(viewportMetrics);
+    private void scrollBy(float dx, float dy) {
+        ImmutableViewportMetrics scrolled = getMetrics().offsetViewportBy(dx, dy);
+        mTarget.setViewportMetrics(scrolled);
     }
 
     private void fling() {
@@ -443,10 +434,10 @@ public class PanZoomController
     }
 
     /* Performs a bounce-back animation to the given viewport metrics. */
-    private void bounce(ViewportMetrics metrics) {
+    private void bounce(ImmutableViewportMetrics metrics) {
         stopAnimationTimer();
 
-        ViewportMetrics bounceStartMetrics = getMutableMetrics();
+        ImmutableViewportMetrics bounceStartMetrics = getMetrics();
         if (bounceStartMetrics.fuzzyEquals(metrics)) {
             setState(PanZoomState.NOTHING);
             return;
@@ -520,7 +511,7 @@ public class PanZoomController
         }
         if (! mSubscroller.scrollBy(displacement)) {
             synchronized (mTarget.getLock()) {
-                scrollBy(displacement);
+                scrollBy(displacement.x, displacement.y);
             }
         }
     }
@@ -558,10 +549,10 @@ public class PanZoomController
          * The viewport metrics that represent the start and end of the bounce-back animation,
          * respectively.
          */
-        private ViewportMetrics mBounceStartMetrics;
-        private ViewportMetrics mBounceEndMetrics;
+        private ImmutableViewportMetrics mBounceStartMetrics;
+        private ImmutableViewportMetrics mBounceEndMetrics;
 
-        BounceRunnable(ViewportMetrics startMetrics, ViewportMetrics endMetrics) {
+        BounceRunnable(ImmutableViewportMetrics startMetrics, ImmutableViewportMetrics endMetrics) {
             mBounceStartMetrics = startMetrics;
             mBounceEndMetrics = endMetrics;
         }
@@ -593,7 +584,7 @@ public class PanZoomController
         private void advanceBounce() {
             synchronized (mTarget.getLock()) {
                 float t = easeOut(mBounceFrame * Axis.MS_PER_FRAME / 256f);
-                ViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t);
+                ImmutableViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t);
                 mTarget.setViewportMetrics(newMetrics);
                 mBounceFrame++;
             }
@@ -667,13 +658,13 @@ public class PanZoomController
     }
 
     /* Returns the nearest viewport metrics with no overscroll visible. */
-    private ViewportMetrics getValidViewportMetrics() {
-        return getValidViewportMetrics(getMutableMetrics());
+    private ImmutableViewportMetrics getValidViewportMetrics() {
+        return getValidViewportMetrics(getMetrics());
     }
 
-    private ViewportMetrics getValidViewportMetrics(ViewportMetrics viewportMetrics) {
+    private ImmutableViewportMetrics getValidViewportMetrics(ImmutableViewportMetrics viewportMetrics) {
         /* First, we adjust the zoom factor so that we can make no overscrolled area visible. */
-        float zoomFactor = viewportMetrics.getZoomFactor();
+        float zoomFactor = viewportMetrics.zoomFactor;
         RectF pageRect = viewportMetrics.getPageRect();
         RectF viewport = viewportMetrics.getViewport();
 
@@ -718,14 +709,14 @@ public class PanZoomController
             // by different scale factors, we end up scrolled to the end on one axis
             // after applying the scale
             PointF center = new PointF(focusX, focusY);
-            viewportMetrics.scaleTo(minZoomFactor, center);
+            viewportMetrics = viewportMetrics.scaleTo(minZoomFactor, center);
         } else if (zoomFactor > maxZoomFactor) {
             PointF center = new PointF(viewport.width() / 2.0f, viewport.height() / 2.0f);
-            viewportMetrics.scaleTo(maxZoomFactor, center);
+            viewportMetrics = viewportMetrics.scaleTo(maxZoomFactor, center);
         }
 
         /* Now we pan to the right origin. */
-        viewportMetrics.setViewport(viewportMetrics.getClampedViewport());
+        viewportMetrics = viewportMetrics.clamp();
 
         return viewportMetrics;
     }
@@ -826,8 +817,8 @@ public class PanZoomController
                 newZoomFactor = maxZoomFactor + excessZoom;
             }
 
-            scrollBy(new PointF(mLastZoomFocus.x - detector.getFocusX(),
-                    mLastZoomFocus.y - detector.getFocusY()));
+            scrollBy(mLastZoomFocus.x - detector.getFocusX(),
+                     mLastZoomFocus.y - detector.getFocusY());
             PointF focus = new PointF(detector.getFocusX(), detector.getFocusY());
             scaleWithFocus(newZoomFactor, focus);
         }
@@ -854,8 +845,8 @@ public class PanZoomController
      * scale operation. You must hold the monitor while calling this.
      */
     private void scaleWithFocus(float zoomFactor, PointF focus) {
-        ViewportMetrics viewportMetrics = getMutableMetrics();
-        viewportMetrics.scaleTo(zoomFactor, focus);
+        ImmutableViewportMetrics viewportMetrics = getMetrics();
+        viewportMetrics = viewportMetrics.scaleTo(zoomFactor, focus);
         mTarget.setViewportMetrics(viewportMetrics);
     }
 
@@ -931,10 +922,11 @@ public class PanZoomController
 
         float finalZoom = viewport.width() / zoomToRect.width();
 
-        ViewportMetrics finalMetrics = getMutableMetrics();
-        finalMetrics.setOrigin(new PointF(zoomToRect.left * finalMetrics.getZoomFactor(),
-                                          zoomToRect.top * finalMetrics.getZoomFactor()));
-        finalMetrics.scaleTo(finalZoom, new PointF(0.0f, 0.0f));
+        ImmutableViewportMetrics finalMetrics = getMetrics();
+        finalMetrics = finalMetrics.setViewportOrigin(
+            zoomToRect.left * finalMetrics.zoomFactor,
+            zoomToRect.top * finalMetrics.zoomFactor);
+        finalMetrics = finalMetrics.scaleTo(finalZoom, new PointF(0.0f, 0.0f));
 
         // 2. now run getValidViewportMetrics on it, so that the target viewport is
         // clamped down to prevent overscroll, over-zoom, and other bad conditions.
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomTarget.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomTarget.java
index 3ebc4f1..fdac874 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomTarget.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomTarget.java
@@ -9,14 +9,13 @@ import android.graphics.PointF;
 
 import org.mozilla.gecko.ZoomConstraints;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
-import org.mozilla.gecko.gfx.ViewportMetrics;
 
 public interface PanZoomTarget {
     public ImmutableViewportMetrics getViewportMetrics();
     public ZoomConstraints getZoomConstraints();
 
-    public void setAnimationTarget(ViewportMetrics viewport);
-    public void setViewportMetrics(ViewportMetrics viewport);
+    public void setAnimationTarget(ImmutableViewportMetrics viewport);
+    public void setViewportMetrics(ImmutableViewportMetrics viewport);
     public void setForceRedraw();
 
     public boolean post(Runnable action);
commit f66ff689d0b3ba5196cac717c7228f541d853e3f
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Sat Sep 27 22:54:20 2014 +0200

    android: Improve panning smoothness (Fennec import)
    
    Change-Id: I3983709651548eb97e588ebe2c2de608a4a4dfc7

diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java
index fcdcd72..7ae8084 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java
@@ -6,8 +6,8 @@
 package org.mozilla.gecko.ui;
 
 import android.util.Log;
+import android.view.View;
 
-import org.json.JSONArray;
 import org.mozilla.gecko.util.FloatUtils;
 
 import java.util.Map;
@@ -23,7 +23,6 @@ abstract class Axis {
 
     private static final String PREF_SCROLLING_FRICTION_SLOW = "ui.scrolling.friction_slow";
     private static final String PREF_SCROLLING_FRICTION_FAST = "ui.scrolling.friction_fast";
-    private static final String PREF_SCROLLING_VELOCITY_THRESHOLD = "ui.scrolling.velocity_threshold";
     private static final String PREF_SCROLLING_MAX_EVENT_ACCELERATION = "ui.scrolling.max_event_acceleration";
     private static final String PREF_SCROLLING_OVERSCROLL_DECEL_RATE = "ui.scrolling.overscroll_decel_rate";
     private static final String PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT = "ui.scrolling.overscroll_snap_limit";
@@ -59,22 +58,22 @@ abstract class Axis {
         return (value == null || value < 0 ? defaultValue : value);
     }
 
-    static void addPrefNames(JSONArray prefs) {
-        prefs.put(PREF_SCROLLING_FRICTION_FAST);
-        prefs.put(PREF_SCROLLING_FRICTION_SLOW);
-        prefs.put(PREF_SCROLLING_VELOCITY_THRESHOLD);
-        prefs.put(PREF_SCROLLING_MAX_EVENT_ACCELERATION);
-        prefs.put(PREF_SCROLLING_OVERSCROLL_DECEL_RATE);
-        prefs.put(PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT);
-        prefs.put(PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE);
+    static final float MS_PER_FRAME = 4.0f;
+    private static final float FRAMERATE_MULTIPLIER = (1000f/60f) / MS_PER_FRAME;
+
+    //  The values we use for friction are based on a 16.6ms frame, adjust them to MS_PER_FRAME:
+    //  FRICTION^1 = FRICTION_ADJUSTED^(16/MS_PER_FRAME)
+    //  FRICTION_ADJUSTED = e ^ ((ln(FRICTION))/FRAMERATE_MULTIPLIER)
+    static float getFrameAdjustedFriction(float baseFriction) {
+        return (float)Math.pow(Math.E, (Math.log(baseFriction) / FRAMERATE_MULTIPLIER));
     }
 
     static void setPrefs(Map<String, Integer> prefs) {
-        FRICTION_SLOW = getFloatPref(prefs, PREF_SCROLLING_FRICTION_SLOW, 850);
-        FRICTION_FAST = getFloatPref(prefs, PREF_SCROLLING_FRICTION_FAST, 970);
-        VELOCITY_THRESHOLD = getIntPref(prefs, PREF_SCROLLING_VELOCITY_THRESHOLD, 10);
+        FRICTION_SLOW = getFrameAdjustedFriction(getFloatPref(prefs, PREF_SCROLLING_FRICTION_SLOW, 850));
+        FRICTION_FAST = getFrameAdjustedFriction(getFloatPref(prefs, PREF_SCROLLING_FRICTION_FAST, 970));
+        VELOCITY_THRESHOLD = 10 / FRAMERATE_MULTIPLIER;
         MAX_EVENT_ACCELERATION = getFloatPref(prefs, PREF_SCROLLING_MAX_EVENT_ACCELERATION, 12);
-        OVERSCROLL_DECEL_RATE = getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_DECEL_RATE, 40);
+        OVERSCROLL_DECEL_RATE = getFrameAdjustedFriction(getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_DECEL_RATE, 40));
         SNAP_LIMIT = getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT, 300);
         MIN_SCROLLABLE_DISTANCE = getFloatPref(prefs, PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE, 500);
         Log.i(LOGTAG, "Prefs: " + FRICTION_SLOW + "," + FRICTION_FAST + "," + VELOCITY_THRESHOLD + ","
@@ -86,9 +85,6 @@ abstract class Axis {
         setPrefs(null);
     }
 
-    // The number of milliseconds per frame assuming 60 fps
-    private static final float MS_PER_FRAME = 1000.0f / 60.0f;
-
     private enum FlingStates {
         STOPPED,
         PANNING,
@@ -104,6 +100,7 @@ abstract class Axis {
 
     private final SubdocumentScrollHelper mSubscroller;
 
+    private int mOverscrollMode; /* Default to only overscrolling if we're allowed to scroll in a direction */
     private float mFirstTouchPos;           /* Position of the first touch event on the current drag. */
     private float mTouchPos;                /* Position of the most recent touch event on the current drag. */
     private float mLastTouchPos;            /* Position of the touch event before touchPos. */
@@ -121,6 +118,15 @@ abstract class Axis {
 
     Axis(SubdocumentScrollHelper subscroller) {
         mSubscroller = subscroller;
+        mOverscrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS;
+    }
+
+    public void setOverScrollMode(int overscrollMode) {
+        mOverscrollMode = overscrollMode;
+    }
+
+    public int getOverScrollMode() {
+        return mOverscrollMode;
     }
 
     private float getViewportEnd() {
@@ -155,7 +161,7 @@ abstract class Axis {
         // If there's a direction change, or current velocity is very low,
         // allow setting of the velocity outright. Otherwise, use the current
         // velocity and a maximum change factor to set the new velocity.
-        boolean curVelocityIsLow = Math.abs(mVelocity) < 1.0f;
+        boolean curVelocityIsLow = Math.abs(mVelocity) < 1.0f / FRAMERATE_MULTIPLIER;
         boolean directionChange = (mVelocity > 0) != (newVelocity > 0);
         if (curVelocityIsLow || (directionChange && !FloatUtils.fuzzyEquals(newVelocity, 0.0f))) {
             mVelocity = newVelocity;
@@ -200,24 +206,31 @@ abstract class Axis {
      * Returns true if the page is zoomed in to some degree along this axis such that scrolling is
      * possible and this axis has not been scroll locked while panning. Otherwise, returns false.
      */
-    private boolean scrollable() {
+    boolean scrollable() {
         // If we're scrolling a subdocument, ignore the viewport length restrictions (since those
         // apply to the top-level document) and only take into account axis locking.
         if (mSubscroller.scrolling()) {
             return !mScrollingDisabled;
-        } else {
-            return getViewportLength() <= getPageLength() - MIN_SCROLLABLE_DISTANCE &&
-                   !mScrollingDisabled;
         }
+
+        // if we are axis locked, return false
+        if (mScrollingDisabled) {
+            return false;
+        }
+
+        // there is scrollable space, and we're not disabled, or the document fits the viewport
+        // but we always allow overscroll anyway
+        return getViewportLength() <= getPageLength() - MIN_SCROLLABLE_DISTANCE ||
+               getOverScrollMode() == View.OVER_SCROLL_ALWAYS;
     }
 
     /*
      * Returns the resistance, as a multiplier, that should be taken into account when
      * tracking or pinching.
      */
-    float getEdgeResistance() {
+    float getEdgeResistance(boolean forPinching) {
         float excess = getExcess();
-        if (excess > 0.0f) {
+        if (excess > 0.0f && (getOverscroll() == Overscroll.BOTH || !forPinching)) {
             // excess can be greater than viewport length, but the resistance
             // must never drop below 0.0
             return Math.max(0.0f, SNAP_LIMIT - excess / getViewportLength());
@@ -257,7 +270,15 @@ abstract class Axis {
         }
 
         float excess = getExcess();
-        if (mDisableSnap || FloatUtils.fuzzyEquals(excess, 0.0f)) {
+        Overscroll overscroll = getOverscroll();
+        boolean decreasingOverscroll = false;
+        if ((overscroll == Overscroll.MINUS && mVelocity > 0) ||
+            (overscroll == Overscroll.PLUS && mVelocity < 0))
+        {
+            decreasingOverscroll = true;
+        }
+
+        if (mDisableSnap || FloatUtils.fuzzyEquals(excess, 0.0f) || decreasingOverscroll) {
             // If we aren't overscrolled, just apply friction.
             if (Math.abs(mVelocity) >= VELOCITY_THRESHOLD) {
                 mVelocity *= FRICTION_FAST;
@@ -268,7 +289,7 @@ abstract class Axis {
         } else {
             // Otherwise, decrease the velocity linearly.
             float elasticity = 1.0f - excess / (getViewportLength() * SNAP_LIMIT);
-            if (getOverscroll() == Overscroll.MINUS) {
+            if (overscroll == Overscroll.MINUS) {
                 mVelocity = Math.min((mVelocity + OVERSCROLL_DECEL_RATE) * elasticity, 0.0f);
             } else { // must be Overscroll.PLUS
                 mVelocity = Math.max((mVelocity - OVERSCROLL_DECEL_RATE) * elasticity, 0.0f);
@@ -285,14 +306,27 @@ abstract class Axis {
 
     // Performs displacement of the viewport position according to the current velocity.
     void displace() {
-        if (!scrollable()) {
+        // if this isn't scrollable just return
+        if (!scrollable())
             return;
-        }
 
         if (mFlingState == FlingStates.PANNING)
-            mDisplacement += (mLastTouchPos - mTouchPos) * getEdgeResistance();
+            mDisplacement += (mLastTouchPos - mTouchPos) * getEdgeResistance(false);
         else
             mDisplacement += mVelocity;
+
+        // if overscroll is disabled and we're trying to overscroll, reset the displacement
+        // to remove any excess. Using getExcess alone isn't enough here since it relies on
+        // getOverscroll which doesn't take into account any new displacment being applied
+        if (getOverScrollMode() == View.OVER_SCROLL_NEVER) {
+            if (mDisplacement + getOrigin() < getPageStart()) {
+                mDisplacement = getPageStart() - getOrigin();
+                stopFling();
+            } else if (mDisplacement + getViewportEnd() > getPageEnd()) {
+                mDisplacement = getPageEnd() - getViewportEnd();
+                stopFling();
+            }
+        }
     }
 
     float resetDisplacement() {
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 a6b7d27..02e45da 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
@@ -19,8 +19,6 @@ import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 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;
 
@@ -53,26 +51,6 @@ public class PanZoomController
     // The maximum amount we allow you to zoom into a page
     private static final float MAX_ZOOM = 4.0f;
 
-    /* 16 precomputed frames of the _ease-out_ animation from the CSS Transitions specification. */
-    private static float[] ZOOM_ANIMATION_FRAMES = new float[] {
-        0.00000f,   /* 0 */
-        0.10211f,   /* 1 */
-        0.19864f,   /* 2 */
-        0.29043f,   /* 3 */
-        0.37816f,   /* 4 */
-        0.46155f,   /* 5 */
-        0.54054f,   /* 6 */
-        0.61496f,   /* 7 */
-        0.68467f,   /* 8 */
-        0.74910f,   /* 9 */
-        0.80794f,   /* 10 */
-        0.86069f,   /* 11 */
-        0.90651f,   /* 12 */
-        0.94471f,   /* 13 */
-        0.97401f,   /* 14 */
-        0.99309f,   /* 15 */
-    };
-
     private enum PanZoomState {
         NOTHING,        /* no touch-start events received */
         FLING,          /* all touches removed, but we're still scrolling page */
@@ -125,6 +103,13 @@ public class PanZoomController
         mSubscroller.destroy();
     }
 
+    private final static float easeOut(float t) {
+        // ease-out approx.
+        // -(t-1)^2+1
+        t = t-1;
+        return -t*t+1;
+    }
+
     private void setState(PanZoomState state) {
         mState = state;
     }
@@ -145,23 +130,6 @@ 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);
@@ -357,6 +325,8 @@ public class PanZoomController
     }
 
     private boolean onTouchCancel(MotionEvent event) {
+        cancelTouch();
+
         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
@@ -367,7 +337,6 @@ public class PanZoomController
             return false;
         }
 
-        cancelTouch();
         // ensure we snap back if we're overscrolled
         bounce();
         return false;
@@ -392,7 +361,9 @@ public class PanZoomController
         mY.startTouch(y);
         mLastEventTime = time;
 
-        if (angle < AXIS_LOCK_ANGLE || angle > (Math.PI - AXIS_LOCK_ANGLE)) {
+        if (!mX.scrollable() || !mY.scrollable()) {
+            setState(PanZoomState.PANNING);
+        } else if (angle < AXIS_LOCK_ANGLE || angle > (Math.PI - AXIS_LOCK_ANGLE)) {
             mY.setScrollingDisabled(true);
             setState(PanZoomState.PANNING_LOCKED);
         } else if (Math.abs(angle - (Math.PI / 2)) < AXIS_LOCK_ANGLE) {
@@ -507,7 +478,7 @@ public class PanZoomController
         mAnimationTimer.scheduleAtFixedRate(new TimerTask() {
             @Override
             public void run() { mTarget.post(runnable); }
-        }, 0, 1000L/60L);
+        }, 0, (int)Axis.MS_PER_FRAME);
     }
 
     /* Stops the fling or bounce animation. */
@@ -607,7 +578,7 @@ public class PanZoomController
             }
 
             /* Perform the next frame of the bounce-back animation. */
-            if (mBounceFrame < ZOOM_ANIMATION_FRAMES.length) {
+            if (mBounceFrame < (int)(256f/Axis.MS_PER_FRAME)) {
                 advanceBounce();
                 return;
             }
@@ -621,7 +592,7 @@ public class PanZoomController
         /* Performs one frame of a bounce animation. */
         private void advanceBounce() {
             synchronized (mTarget.getLock()) {
-                float t = ZOOM_ANIMATION_FRAMES[mBounceFrame];
+                float t = easeOut(mBounceFrame * Axis.MS_PER_FRAME / 256f);
                 ViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t);
                 mTarget.setViewportMetrics(newMetrics);
                 mBounceFrame++;
@@ -818,7 +789,7 @@ public class PanZoomController
          * Apply edge resistance if we're zoomed out smaller than the page size by scaling the zoom
          * factor toward 1.0.
          */
-        float resistance = Math.min(mX.getEdgeResistance(), mY.getEdgeResistance());
+        float resistance = Math.min(mX.getEdgeResistance(true), mY.getEdgeResistance(true));
         if (spanRatio > 1.0f)
             spanRatio = 1.0f + (spanRatio - 1.0f) * resistance;
         else
@@ -978,4 +949,13 @@ public class PanZoomController
         checkMainThread();
         bounce();
     }
+
+    public void setOverScrollMode(int overscrollMode) {
+        mX.setOverScrollMode(overscrollMode);
+        mY.setOverScrollMode(overscrollMode);
+    }
+
+    public int getOverScrollMode() {
+        return mX.getOverScrollMode();
+    }
 }
commit 590fdf41164347f113cccc01953eefe6ef1020a8
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Sat Sep 27 22:30:57 2014 +0200

    android: solve black screen on load - call setForceRedraw
    
    Change-Id: Ia9ae05a14c0c751fde961186be350d57d5308519

diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
index 349de7b..ddc18d4 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
@@ -7,6 +7,7 @@ import android.util.DisplayMetrics;
 import android.util.Log;
 
 import org.mozilla.gecko.gfx.GeckoLayerClient;
+import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.ViewportMetrics;
 
 import java.util.concurrent.LinkedBlockingQueue;
@@ -19,6 +20,8 @@ public class LOKitThread extends Thread {
     private TileProvider mTileProvider;
     private ViewportMetrics mViewportMetrics;
     private boolean mCheckboardImageSet = false;
+    private GeckoLayerClient mLayerClient;
+    private LayerController mController;
 
     public LOKitThread() {
         TileProviderFactory.initialize();
@@ -55,16 +58,21 @@ public class LOKitThread extends Thread {
         if (mApplication == null) {
             mApplication = LibreOfficeMainActivity.mAppContext;
         }
+
+        mController = mApplication.getLayerController();
+        mLayerClient = mApplication.getLayerClient();
+
         if (mTileProvider != null) {
             mTileProvider.close();
         }
-        GeckoLayerClient layerClient = mApplication.getLayerClient();
-        mTileProvider = TileProviderFactory.create(mApplication.getLayerController(), filename);
-        layerClient.setTileProvider(mTileProvider);
+
+        mTileProvider = TileProviderFactory.create(mController, filename);
+        mLayerClient.setTileProvider(mTileProvider);
 
         boolean isReady = mTileProvider.isReady();
         if (isReady) {
             updateCheckbardImage();
+            mController.setForceRedraw();
         }
         return isReady;
     }
commit 40e919d2a19066bd7bcbbd4ece2eae75a1fd20a5
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Sat Sep 27 12:54:47 2014 +0200

    android: remove notifyLayerClientOfGeometryChange (Fennec import)
    
    Change-Id: Ibc1f4d11dcfdf177cd45fcf689b518d975b13709

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 0898397..ca02e1c 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
@@ -87,6 +87,7 @@ public class LayerController implements PanZoomTarget {
 
     public void setForceRedraw() {
         mForceRedraw = true;
+        notifyLayerClientOfGeometryChange();
     }
 
     public Layer getRoot()                        { return mRootLayer; }
@@ -159,14 +160,13 @@ public class LayerController implements PanZoomTarget {
     }
 
     /**
-     * Sets the entire viewport metrics at once. This function does not notify the layer client or
-     * the pan/zoom controller, so you will need to call notifyLayerClientOfGeometryChange() or
-     * notifyPanZoomControllerOfGeometryChange() after calling this. You must hold the monitor
-     * while calling this.
+     * Sets the entire viewport metrics at once.
+     * You must hold the monitor while calling this.
      */
     public void setViewportMetrics(ViewportMetrics viewport) {
         mViewportMetrics = new ImmutableViewportMetrics(viewport);
         mView.requestRender();
+        notifyLayerClientOfGeometryChange();
     }
 
     public void setAnimationTarget(ViewportMetrics viewport) {
@@ -183,11 +183,7 @@ public class LayerController implements PanZoomTarget {
 
     public boolean post(Runnable action) { return mView.post(action); }
 
-    /**
-     * The view as well as the controller itself use this method to notify the layer client that
-     * the geometry changed.
-     */
-    public void notifyLayerClientOfGeometryChange() {
+    private void notifyLayerClientOfGeometryChange() {
         if (mLayerClient != null)
             mLayerClient.geometryChanged();
     }
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 876f626..a6b7d27 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
@@ -195,7 +195,6 @@ public class PanZoomController
             // transitions.
             synchronized (mTarget.getLock()) {
                 mTarget.setViewportMetrics(getValidViewportMetrics());
-                mTarget.notifyLayerClientOfGeometryChange();
             }
             break;
         }
@@ -233,7 +232,6 @@ public class PanZoomController
                     // page size changed such that we are now in overscroll. snap to the
                     // the nearest valid viewport
                     mTarget.setViewportMetrics(validated);
-                    mTarget.notifyLayerClientOfGeometryChange();
                 }
             }
         }
@@ -254,7 +252,6 @@ public class PanZoomController
             // case this touchstart is just a tap that doesn't end up triggering
             // a redraw
             mTarget.setForceRedraw();
-            mTarget.notifyLayerClientOfGeometryChange();
             // fall through
         case FLING:
         case BOUNCE:
@@ -460,7 +457,6 @@ public class PanZoomController
         viewportMetrics.setOrigin(origin);
 
         mTarget.setViewportMetrics(viewportMetrics);
-        mTarget.notifyLayerClientOfGeometryChange();
     }
 
     private void fling() {
@@ -628,7 +624,6 @@ public class PanZoomController
                 float t = ZOOM_ANIMATION_FRAMES[mBounceFrame];
                 ViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t);
                 mTarget.setViewportMetrics(newMetrics);
-                mTarget.notifyLayerClientOfGeometryChange();
                 mBounceFrame++;
             }
         }
@@ -637,7 +632,6 @@ public class PanZoomController
         private void finishBounce() {
             synchronized (mTarget.getLock()) {
                 mTarget.setViewportMetrics(mBounceEndMetrics);
-                mTarget.notifyLayerClientOfGeometryChange();
                 mBounceFrame = -1;
             }
         }
@@ -699,7 +693,6 @@ public class PanZoomController
 
         // Force a viewport synchronisation
         mTarget.setForceRedraw();
-        mTarget.notifyLayerClientOfGeometryChange();
     }
 
     /* Returns the nearest viewport metrics with no overscroll visible. */
@@ -883,7 +876,6 @@ public class PanZoomController
 
         // Force a viewport synchronisation
         mTarget.setForceRedraw();
-        mTarget.notifyLayerClientOfGeometryChange();
     }
 
     /**
@@ -894,7 +886,6 @@ public class PanZoomController
         ViewportMetrics viewportMetrics = getMutableMetrics();
         viewportMetrics.scaleTo(zoomFactor, focus);
         mTarget.setViewportMetrics(viewportMetrics);
-        mTarget.notifyLayerClientOfGeometryChange();
     }
 
     public boolean getRedrawHint() {
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomTarget.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomTarget.java
index fcbc00f..3ebc4f1 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomTarget.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomTarget.java
@@ -17,8 +17,6 @@ public interface PanZoomTarget {
 
     public void setAnimationTarget(ViewportMetrics viewport);
     public void setViewportMetrics(ViewportMetrics viewport);
-
-    public void notifyLayerClientOfGeometryChange();
     public void setForceRedraw();
 
     public boolean post(Runnable action);
commit 0151bf3f28b476a2fd0993c6a60fbf9a9f555c3a
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Sat Sep 27 12:47:47 2014 +0200

    android: move scrollBy & scaleWithFocus to PZC (Fennec Import)
    
    Change-Id: Ie0d23b302994134f9d382e255b0408f7c9f1c1fb

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 db43db2..0898397 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
@@ -135,18 +135,6 @@ public class LayerController implements PanZoomTarget {
         }
     }
 
-    /** Scrolls the viewport by the given offset. You must hold the monitor while calling this. */
-    public void scrollBy(PointF point) {
-        ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
-        PointF origin = viewportMetrics.getOrigin();
-        origin.offset(point.x, point.y);
-        viewportMetrics.setOrigin(origin);
-        mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
-
-        notifyLayerClientOfGeometryChange();
-        mView.requestRender();
-    }
-
     /** Sets the current page rect. You must hold the monitor while calling this. */
     public void setPageRect(RectF rect, RectF cssRect) {
         // Since the "rect" is always just a multiple of "cssRect" we don't need to
@@ -193,21 +181,6 @@ public class LayerController implements PanZoomTarget {
         }
     }
 
-    /**
-     * Scales the viewport, keeping the given focus point in the same place before and after the
-     * scale operation. You must hold the monitor while calling this.
-     */
-    public void scaleWithFocus(float zoomFactor, PointF focus) {
-        ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
-        viewportMetrics.scaleTo(zoomFactor, focus);
-        mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
-
-        // We assume the zoom level will only be modified by the
-        // PanZoomController, so no need to notify it of this change.
-        notifyLayerClientOfGeometryChange();
-        mView.requestRender();
-    }
-
     public boolean post(Runnable action) { return mView.post(action); }
 
     /**
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 ef45fd3..876f626 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
@@ -133,6 +133,10 @@ public class PanZoomController
         return mTarget.getViewportMetrics();
     }
 
+    private ViewportMetrics getMutableMetrics() {
+        return new ViewportMetrics(getMetrics());
+    }
+
     // for debugging bug 713011; it can be taken out once that is resolved.
     private void checkMainThread() {
         if (mMainThread != Thread.currentThread()) {
@@ -225,7 +229,7 @@ public class PanZoomController
         if (mState == PanZoomState.NOTHING) {
             synchronized (mTarget.getLock()) {
                 ViewportMetrics validated = getValidViewportMetrics();
-                if (! (new ViewportMetrics(getMetrics())).fuzzyEquals(validated)) {
+                if (! getMutableMetrics().fuzzyEquals(validated)) {
                     // page size changed such that we are now in overscroll. snap to the
                     // the nearest valid viewport
                     mTarget.setViewportMetrics(validated);
@@ -449,6 +453,16 @@ public class PanZoomController
         updatePosition();
     }
 
+    private void scrollBy(PointF point) {
+        ViewportMetrics viewportMetrics = getMutableMetrics();
+        PointF origin = viewportMetrics.getOrigin();
+        origin.offset(point.x, point.y);
+        viewportMetrics.setOrigin(origin);
+
+        mTarget.setViewportMetrics(viewportMetrics);
+        mTarget.notifyLayerClientOfGeometryChange();
+    }
+
     private void fling() {
         updatePosition();
 
@@ -465,7 +479,7 @@ public class PanZoomController
     private void bounce(ViewportMetrics metrics) {
         stopAnimationTimer();
 
-        ViewportMetrics bounceStartMetrics = new ViewportMetrics(getMetrics());
+        ViewportMetrics bounceStartMetrics = getMutableMetrics();
         if (bounceStartMetrics.fuzzyEquals(metrics)) {
             setState(PanZoomState.NOTHING);
             return;
@@ -539,7 +553,7 @@ public class PanZoomController
         }
         if (! mSubscroller.scrollBy(displacement)) {
             synchronized (mTarget.getLock()) {
-                mTarget.scrollBy(displacement);
+                scrollBy(displacement);
             }
         }
     }
@@ -690,7 +704,7 @@ public class PanZoomController
 
     /* Returns the nearest viewport metrics with no overscroll visible. */
     private ViewportMetrics getValidViewportMetrics() {
-        return getValidViewportMetrics(new ViewportMetrics(getMetrics()));
+        return getValidViewportMetrics(getMutableMetrics());
     }
 
     private ViewportMetrics getValidViewportMetrics(ViewportMetrics viewportMetrics) {
@@ -848,10 +862,10 @@ public class PanZoomController
                 newZoomFactor = maxZoomFactor + excessZoom;
             }
 
-            mTarget.scrollBy(new PointF(mLastZoomFocus.x - detector.getFocusX(),
+            scrollBy(new PointF(mLastZoomFocus.x - detector.getFocusX(),
                     mLastZoomFocus.y - detector.getFocusY()));
             PointF focus = new PointF(detector.getFocusX(), detector.getFocusY());
-            mTarget.scaleWithFocus(newZoomFactor, focus);
+            scaleWithFocus(newZoomFactor, focus);
         }
 
         mLastZoomFocus.set(detector.getFocusX(), detector.getFocusY());
@@ -872,6 +886,17 @@ public class PanZoomController
         mTarget.notifyLayerClientOfGeometryChange();
     }
 
+    /**
+     * Scales the viewport, keeping the given focus point in the same place before and after the
+     * scale operation. You must hold the monitor while calling this.
+     */
+    private void scaleWithFocus(float zoomFactor, PointF focus) {
+        ViewportMetrics viewportMetrics = getMutableMetrics();
+        viewportMetrics.scaleTo(zoomFactor, focus);
+        mTarget.setViewportMetrics(viewportMetrics);
+        mTarget.notifyLayerClientOfGeometryChange();
+    }
+
     public boolean getRedrawHint() {
         switch (mState) {
             case PINCHING:
@@ -944,7 +969,7 @@ public class PanZoomController
 
         float finalZoom = viewport.width() / zoomToRect.width();
 
-        ViewportMetrics finalMetrics = new ViewportMetrics(getMetrics());
+        ViewportMetrics finalMetrics = getMutableMetrics();
         finalMetrics.setOrigin(new PointF(zoomToRect.left * finalMetrics.getZoomFactor(),
                                           zoomToRect.top * finalMetrics.getZoomFactor()));
         finalMetrics.scaleTo(finalZoom, new PointF(0.0f, 0.0f));
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomTarget.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomTarget.java
index 7ab1c56..fcbc00f 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomTarget.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomTarget.java
@@ -17,8 +17,6 @@ public interface PanZoomTarget {
 
     public void setAnimationTarget(ViewportMetrics viewport);
     public void setViewportMetrics(ViewportMetrics viewport);
-    public void scrollBy(PointF point);
-    public void scaleWithFocus(float zoomFactor, PointF focus);
 
     public void notifyLayerClientOfGeometryChange();
     public void setForceRedraw();
commit 94e7296fc51e1e85715bbab91cf233f3d7ce67f3
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Sat Sep 27 12:42:39 2014 +0200

    android: add PanZoomTarget (Fennec import)
    
    Change-Id: Ib946b7a95cd59833a732e3da6c139e848778e1bc

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 303cb67..db43db2 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
@@ -17,6 +17,7 @@ import android.view.GestureDetector;
 
 import org.mozilla.gecko.ZoomConstraints;
 import org.mozilla.gecko.ui.PanZoomController;
+import org.mozilla.gecko.ui.PanZoomTarget;
 import org.mozilla.gecko.ui.SimpleScaleGestureDetector;
 
 /**
@@ -26,7 +27,7 @@ import org.mozilla.gecko.ui.SimpleScaleGestureDetector;
  *
  * Many methods require that the monitor be held, with a synchronized (controller) { ... } block.
  */
-public class LayerController {
+public class LayerController implements PanZoomTarget {
     private static final String LOGTAG = "GeckoLayerController";
 
     private Layer mRootLayer;                   /* The root layer. */
@@ -92,6 +93,7 @@ public class LayerController {
     public LayerView getView()                    { return mView; }
     public Context getContext()                   { return mContext; }
     public ImmutableViewportMetrics getViewportMetrics()   { return mViewportMetrics; }
+    public Object getLock()                       { return this; }
 
     public FloatSize getViewportSize() {
         return mViewportMetrics.getSize();
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 1d7d52c..ef45fd3 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
@@ -16,7 +16,6 @@ import org.libreoffice.LOKitShell;
 import org.libreoffice.LibreOfficeMainActivity;
 import org.mozilla.gecko.ZoomConstraints;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
-import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.ViewportMetrics;
 import org.mozilla.gecko.util.FloatUtils;
 
@@ -92,7 +91,7 @@ public class PanZoomController
                         prevented the default actions yet. we still need to abort animations. */
     }
 
-    private final LayerController mController;
+    private final PanZoomTarget mTarget;
     private final SubdocumentScrollHelper mSubscroller;
     private final Axis mX;
     private final Axis mY;
@@ -110,8 +109,8 @@ public class PanZoomController
     /* Current state the pan/zoom UI is in. */
     private PanZoomState mState;
 
-    public PanZoomController(LayerController controller) {
-        mController = controller;
+    public PanZoomController(PanZoomTarget target) {
+        mTarget = target;
         mSubscroller = new SubdocumentScrollHelper();
         mX = new AxisX(mSubscroller);
         mY = new AxisY(mSubscroller);
@@ -131,7 +130,7 @@ public class PanZoomController
     }
 
     private ImmutableViewportMetrics getMetrics() {
-        return mController.getViewportMetrics();
+        return mTarget.getViewportMetrics();
     }
 
     // for debugging bug 713011; it can be taken out once that is resolved.
@@ -190,9 +189,9 @@ public class PanZoomController
         case NOTHING:
             // Don't do animations here; they're distracting and can cause flashes on page
             // transitions.
-            synchronized (mController) {
-                mController.setViewportMetrics(getValidViewportMetrics());
-                mController.notifyLayerClientOfGeometryChange();
+            synchronized (mTarget.getLock()) {
+                mTarget.setViewportMetrics(getValidViewportMetrics());
+                mTarget.notifyLayerClientOfGeometryChange();
             }
             break;
         }
@@ -224,13 +223,13 @@ public class PanZoomController
     /** This must be called on the UI thread. */
     public void pageRectUpdated() {
         if (mState == PanZoomState.NOTHING) {
-            synchronized (mController) {
+            synchronized (mTarget.getLock()) {
                 ViewportMetrics validated = getValidViewportMetrics();
                 if (! (new ViewportMetrics(getMetrics())).fuzzyEquals(validated)) {
                     // page size changed such that we are now in overscroll. snap to the
                     // the nearest valid viewport
-                    mController.setViewportMetrics(validated);
-                    mController.notifyLayerClientOfGeometryChange();
+                    mTarget.setViewportMetrics(validated);
+                    mTarget.notifyLayerClientOfGeometryChange();
                 }
             }
         }
@@ -250,8 +249,8 @@ public class PanZoomController
             // 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();
+            mTarget.setForceRedraw();
+            mTarget.notifyLayerClientOfGeometryChange();
             // fall through
         case FLING:
         case BOUNCE:
@@ -476,7 +475,7 @@ public class PanZoomController
         // 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);
+        mTarget.setAnimationTarget(metrics);
         startAnimationTimer(new BounceRunnable(bounceStartMetrics, metrics));
     }
 
@@ -497,7 +496,7 @@ public class PanZoomController
         mAnimationRunnable = runnable;
         mAnimationTimer.scheduleAtFixedRate(new TimerTask() {
             @Override
-            public void run() { mController.post(runnable); }
+            public void run() { mTarget.post(runnable); }
         }, 0, 1000L/60L);
     }
 
@@ -539,8 +538,8 @@ public class PanZoomController
             return;
         }
         if (! mSubscroller.scrollBy(displacement)) {
-            synchronized (mController) {
-                mController.scrollBy(displacement);
+            synchronized (mTarget.getLock()) {
+                mTarget.scrollBy(displacement);
             }
         }
     }
@@ -611,20 +610,20 @@ public class PanZoomController
 
         /* Performs one frame of a bounce animation. */
         private void advanceBounce() {
-            synchronized (mController) {
+            synchronized (mTarget.getLock()) {
                 float t = ZOOM_ANIMATION_FRAMES[mBounceFrame];
                 ViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t);

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list