LOAndroid3: (partially) render page with LOKitTileProvider
+ TileProvider & TileIterator interfaces
+ Clean-up obsolete mozilla stuff
Change-Id: Ief56f11bf7f8fd6da383ffc7be3461b765bf0157
diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
index abcbe65..90825759d 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitThread.java
@@ -16,16 +16,10 @@ import java.nio.ByteBuffer;
import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.libreoffice.kit.LibreOfficeKit;
import org.libreoffice.kit.Office;
import org.libreoffice.kit.Document;
public class LOKitThread extends Thread {
private static final String LOGTAG = "GeckoThread";
private static final int TILE_SIZE = 256;
public Office mOffice;
public Document mDocument;
private TileProvider mTileProvider;
public ConcurrentLinkedQueue<LOEvent> gEvents = new ConcurrentLinkedQueue<LOEvent>();
private ViewportMetrics mViewportMetrics;
@@ -33,55 +27,40 @@ public class LOKitThread extends Thread {
LOKitThread() {
}
private void openDocument() {
// enable debugging messages as the first thing
LibreOfficeKit.putenv("SAL_LOG=+WARN+INFO-INFO.legacy.osl-INFO.i18nlangtag");
LibreOfficeKit.init(LibreOfficeMainActivity.mAppContext);
mOffice = new Office(LibreOfficeKit.getLibreOfficeKitHandle());
String input = "/assets/test1.odt";
mDocument = mOffice.documentLoad(input);
}
private synchronized boolean draw() throws InterruptedException {
private boolean draw() throws InterruptedException {
final LibreOfficeMainActivity application = LibreOfficeMainActivity.mAppContext;
openDocument();
if (mTileProvider == null)
mTileProvider = new LOKitTileProvider(application.getLayerController());
long height = mDocument.getDocumentHeight();
long width = mDocument.getDocumentWidth();
int pageWidth = mTileProvider.getPageWidth();
int pageHeight = mTileProvider.getPageHeight();
Log.e(LOGTAG, "Document Size: " + width + " " + height);
String metadata = createJson(0, 0, pageWidth, pageHeight, pageWidth, pageHeight, 0, 0, 1.0);
mViewportMetrics = new ViewportMetrics();
int pageWidth = 1024;
int pageHeight = 1024;
boolean shouldContinue = application.getLayerClient().beginDrawing(pageWidth, pageHeight, TILE_SIZE, TILE_SIZE, metadata);
String metadata = createJson(0, 0, 256, 256, pageWidth, pageHeight, 0, 0, 1.0);
Rect bufferRect = application.getLayerClient().beginDrawing(256, 256, TILE_SIZE, TILE_SIZE, metadata);
/*if (bufferRect == null) {
Log.e(LOGTAG, "beginDrawing - false");
if (!shouldContinue) {
return false;
}*/
}
Log.e(LOGTAG, "Filling tiles..");
Log.i(LOGTAG, "Filling tiles..");
ByteBuffer buffer = ByteBuffer.allocateDirect(TILE_SIZE * TILE_SIZE * 4);
int x = 0;
int y = 0;
for (Bitmap bitmap : mTileProvider.getTileIterator()) {
application.getLayerClient().addTile(bitmap, x, y);
x += TILE_SIZE;
if (x > pageWidth) {
x = 0;
y += TILE_SIZE;
}
}
Log.e(LOGTAG, "PaintTile..");
Log.i(LOGTAG, "End Draw");
mDocument.paintTile(buffer, 256, 256, 1024, 1024, 4096, 4096);
Log.e(LOGTAG, "EndPaintTile..");
Bitmap bitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
application.getLayerClient().addTile(bitmap, 0, 0);
Log.e(LOGTAG, "EndDrawing..");
application.getLayerClient().endDrawing(0, 0, 256, 256);
application.getLayerClient().endDrawing(0, 0, pageWidth, pageHeight);
return true;
}
@@ -143,22 +122,20 @@ public class LOKitThread extends Thread {
try {
boolean drawn = false;
while (true) {
if (!gEvents.isEmpty()) {
processEvent(gEvents.poll());
} else {
if (!drawn) {
drawn = draw();
}
Thread.sleep(2000L);
Thread.sleep(100L);
}
}
} catch (InterruptedException ex) {
}
}
private synchronized void processEvent(LOEvent event) throws InterruptedException {
private void processEvent(LOEvent event) throws InterruptedException {
switch (event.mType) {
case LOEvent.VIEWPORT:
mViewportMetrics = event.getViewport();
diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitTileProvider.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitTileProvider.java
new file mode 100644
index 0000000..4b6d8fa
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LOKitTileProvider.java
@@ -0,0 +1,103 @@
package org.libreoffice;
import android.graphics.Bitmap;
import android.util.Log;
import org.mozilla.gecko.gfx.LayerController;
import java.nio.ByteBuffer;
import java.util.Iterator;
import org.libreoffice.kit.LibreOfficeKit;
import org.libreoffice.kit.Office;
import org.libreoffice.kit.Document;
public class LOKitTileProvider implements TileProvider {
private final LayerController mLayerController;
public static int TILE_SIZE = 256;
public final Office mOffice;
public final Document mDocument;
public LOKitTileProvider(LayerController layerController) {
this.mLayerController = layerController;
LibreOfficeKit.putenv("SAL_LOG=+WARN+INFO-INFO.legacy.osl-INFO.i18nlangtag");
LibreOfficeKit.init(LibreOfficeMainActivity.mAppContext);
mOffice = new Office(LibreOfficeKit.getLibreOfficeKitHandle());
String input = "/assets/test1.odt";
mDocument = mOffice.documentLoad(input);
}
@Override
public int getPageWidth() {
return (int) (mDocument.getDocumentWidth() / 1440.0 * LOKitShell.getDpi());
}
@Override
public int getPageHeight() {
return (int) (mDocument.getDocumentHeight() / 1440.0 * LOKitShell.getDpi());
}
public TileIterator getTileIterator() {
return new LoKitTileIterator();
}
public class LoKitTileIterator implements TileIterator, Iterator<Bitmap> {
private final double mTileWidth;
private final double mTileHeight;
private boolean mShouldContinue = true;
private double mPositionWidth = 0;
private double mPositionHeight = 0;
private double mPageWidth;
private double mPageHeight;
public LoKitTileIterator() {
mTileWidth = (TILE_SIZE / (double) LOKitShell.getDpi()) * 1440.0;
mTileHeight = (TILE_SIZE / (double) LOKitShell.getDpi()) * 1440.0;
mPageWidth = mDocument.getDocumentWidth();
mPageHeight = mDocument.getDocumentHeight();
}
@Override
public boolean hasNext() {
return mShouldContinue;
}
@Override
public Bitmap next() {
ByteBuffer buffer = ByteBuffer.allocateDirect(TILE_SIZE * TILE_SIZE * 4);
Bitmap bitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Bitmap.Config.ARGB_8888);
mDocument.paintTile(buffer, TILE_SIZE, TILE_SIZE, (int) mPositionWidth, (int) mPositionHeight, (int) mTileWidth, (int) mTileHeight);
mPositionWidth += mTileWidth;
if (mPositionWidth > mPageWidth) {
mPositionHeight += mTileHeight;
mPositionWidth = 0;
}
if (mPositionHeight > mPageHeight || mPositionHeight > 20000) {
mShouldContinue = false;
}
bitmap.copyPixelsFromBuffer(buffer);
return bitmap;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public Iterator<Bitmap> iterator() {
return this;
}
}
}
diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java
index a3144e1..0f12208d 100644
--- a/android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/LibreOfficeMainActivity.java
@@ -11,8 +11,6 @@ import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.os.Environment;
import java.io.File;
import org.mozilla.gecko.gfx.GeckoSoftwareLayerClient;
import org.mozilla.gecko.gfx.LayerController;
@@ -63,9 +61,11 @@ public class LibreOfficeMainActivity extends Activity {
mAppContext = this;
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - onCreate");
setContentView(R.layout.activity_main);
Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - onCreate");
// setup gecko layout
mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/MockTileProvider.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/MockTileProvider.java
new file mode 100644
index 0000000..04ebfb4
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/MockTileProvider.java
@@ -0,0 +1,64 @@
package org.libreoffice;
import android.graphics.Bitmap;
import org.apache.http.MethodNotSupportedException;
import org.mozilla.gecko.gfx.LayerController;
import java.util.Iterator;
import java.util.List;
public class MockTileProvider implements TileProvider {
private final LayerController layerController;
public MockTileProvider(LayerController layerController) {
this.layerController = layerController;
}
@Override
public int getPageWidth() {
return 549;
}
@Override
public int getPageHeight() {
return 630;
}
public TileIterator getTileIterator() {
return new MockTileIterator(layerController);
}
public class MockTileIterator implements TileIterator, Iterator<Bitmap> {
private final LayerController layerController;
private int tileNumber = 1;
public MockTileIterator(LayerController layerController) {
this.layerController = layerController;
}
@Override
public boolean hasNext() {
return tileNumber <= 9;
}
@Override
public Bitmap next() {
String imageName = "d" + tileNumber;
tileNumber++;
Bitmap bitmap = layerController.getDrawable(imageName);
return bitmap;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public Iterator<Bitmap> iterator() {
return this;
}
}
}
diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/TileIterator.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/TileIterator.java
new file mode 100644
index 0000000..68c39e5
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/TileIterator.java
@@ -0,0 +1,6 @@
package org.libreoffice;
import android.graphics.Bitmap;
public interface TileIterator extends Iterable<Bitmap> {
}
diff --git a/android/experimental/LOAndroid3/src/java/org/libreoffice/TileProvider.java b/android/experimental/LOAndroid3/src/java/org/libreoffice/TileProvider.java
new file mode 100644
index 0000000..a405fdf
--- /dev/null
+++ b/android/experimental/LOAndroid3/src/java/org/libreoffice/TileProvider.java
@@ -0,0 +1,14 @@
package org.libreoffice;
import android.graphics.Bitmap;
import java.util.List;
public interface TileProvider {
int getPageWidth();
int getPageHeight();
TileIterator getTileIterator();
}
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 134c406..c196cf8 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
@@ -59,15 +59,7 @@ import org.mozilla.gecko.util.FloatUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
//import org.mozilla.gecko.GeckoApp;
//import org.mozilla.gecko.GeckoAppShell;
//import org.mozilla.gecko.GeckoEvent;
public abstract class GeckoLayerClient extends LayerClient implements GeckoEventListener {
public static final int LAYER_CLIENT_TYPE_NONE = 0;
public static final int LAYER_CLIENT_TYPE_SOFTWARE = 1;
public static final int LAYER_CLIENT_TYPE_GL = 2;
public abstract class GeckoLayerClient implements GeckoEventListener {
private static final String LOGTAG = "GeckoLayerClient";
private static final long MIN_VIEWPORT_CHANGE_DELAY = 25L;
private static Pattern sColorPattern;
@@ -88,53 +80,26 @@ public abstract class GeckoLayerClient extends LayerClient implements GeckoEvent
// inside a transaction, so no synchronization is needed.
private boolean mUpdateViewportOnEndDraw;
private String mLastCheckerboardColor;
/* Used by robocop for testing purposes */
private DrawListener mDrawListener;
protected LayerController mLayerController;
public GeckoLayerClient(Context context) {
mScreenSize = new IntSize(0, 0);
}
// Parses a color from an RGB triple of the form "rgb([0-9]+, [0-9]+, [0-9]+)". If the color
// cannot be parsed, returns white.
private static int parseColorFromGecko(String string) {
if (sColorPattern == null) {
sColorPattern = Pattern.compile("rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)");
}
Matcher matcher = sColorPattern.matcher(string);
if (!matcher.matches()) {
return Color.WHITE;
}
int r = Integer.parseInt(matcher.group(1));
int g = Integer.parseInt(matcher.group(2));
int b = Integer.parseInt(matcher.group(3));
return Color.rgb(r, g, b);
}
protected abstract boolean setupLayer();
protected abstract boolean shouldDrawProceed(int tileWidth, int tileHeight);
protected abstract void updateLayerAfterDraw(Rect updatedRect);
protected abstract IntSize getBufferSize();
protected abstract IntSize getTileSize();
protected abstract void tileLayerUpdated();
public abstract Bitmap getBitmap();
public abstract int getType();
/**
* Attaches the root layer to the layer controller so that Gecko appears.
*/
@Override
public void setLayerController(LayerController layerController) {
super.setLayerController(layerController);
mLayerController = layerController;
layerController.setRoot(mTileLayer);
if (mGeckoViewport != null) {
@@ -144,80 +109,25 @@ public abstract class GeckoLayerClient extends LayerClient implements GeckoEvent
sendResizeEventIfNecessary();
}
public Rect beginDrawing(int width, int height, int tileWidth, int tileHeight, String metadata) {
public boolean beginDrawing(int width, int height, int tileWidth, int tileHeight, String metadata) {
Log.e(LOGTAG, "### beginDrawing " + width + " " + height + " " + tileWidth + " " + tileHeight);
if (setupLayer()) {
if (setupLayer()) {
Log.e(LOGTAG, "### Cancelling due to layer setup");
return null;
return false;
}
if (!shouldDrawProceed(tileWidth, tileHeight)) {
Log.e(LOGTAG, "### Cancelling draw due to shouldDrawProceed()");
return null;
}
LayerController controller = getLayerController();
try {
JSONObject viewportObject = new JSONObject(metadata);
mNewGeckoViewport = new ViewportMetrics(viewportObject);
Log.e(LOGTAG, "### beginDrawing new Gecko viewport " + mNewGeckoViewport);
// Update the background color, if it's present.
String backgroundColorString = viewportObject.optString("backgroundColor");
if (backgroundColorString != null && !backgroundColorString.equals(mLastCheckerboardColor)) {
mLastCheckerboardColor = backgroundColorString;
controller.setCheckerboardColor(parseColorFromGecko(backgroundColorString));
}
} catch (JSONException e) {
Log.e(LOGTAG, "Aborting draw, bad viewport description: " + metadata);
return null;
return false;
}
// Make sure we don't spend time painting areas we aren't interested in.
// Only do this if the Gecko viewport isn't going to override our viewport.
Rect bufferRect = new Rect(0, 0, width, height);
if (!mUpdateViewportOnEndDraw) {
// First, find out our ideal displayport. We do this by taking the
// clamped viewport origin and taking away the optimum viewport offset.
// This would be what we would send to Gecko if adjustViewport were
// called now.
ViewportMetrics currentMetrics = controller.getViewportMetrics();
PointF currentBestOrigin = RectUtils.getOrigin(currentMetrics.getClampedViewport());
PointF viewportOffset = currentMetrics.getOptimumViewportOffset(new IntSize(width, height));
currentBestOrigin.offset(-viewportOffset.x, -viewportOffset.y);
Rect currentRect = RectUtils.round(new RectF(currentBestOrigin.x, currentBestOrigin.y,
currentBestOrigin.x + width, currentBestOrigin.y + height));
// Second, store Gecko's displayport.
PointF currentOrigin = mNewGeckoViewport.getDisplayportOrigin();
bufferRect = RectUtils.round(new RectF(currentOrigin.x, currentOrigin.y,
currentOrigin.x + width, currentOrigin.y + height));
// Take the intersection of the two as the area we're interested in rendering.
if (!bufferRect.intersect(currentRect)) {
// If there's no intersection, we have no need to render anything,
// but make sure to update the viewport size.
beginTransaction(mTileLayer);
try {
updateViewport(true);
} finally {
endTransaction(mTileLayer);
}
return null;
}
bufferRect.offset(Math.round(-currentOrigin.x), Math.round(-currentOrigin.y));
}
beginTransaction(mTileLayer);
return bufferRect;
mTileLayer.beginTransaction();
return true;
}
/*
@@ -225,23 +135,17 @@ public abstract class GeckoLayerClient extends LayerClient implements GeckoEvent
* a little more JNI magic.
*/
public void endDrawing(int x, int y, int width, int height) {
synchronized (getLayerController()) {
synchronized (mLayerController) {
try {
updateViewport(!mUpdateViewportOnEndDraw);
mUpdateViewportOnEndDraw = false;
Rect rect = new Rect(x, y, x + width, y + height);
updateLayerAfterDraw(rect);
} finally {
endTransaction(mTileLayer);
mTileLayer.endTransaction();
}
}
Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - endDrawing");
/* Used by robocop for testing purposes */
if (mDrawListener != null) {
mDrawListener.drawFinished(x, y, width, height);
}
}
protected void updateViewport(boolean onlyUpdatePageSize) {
@@ -249,27 +153,25 @@ public abstract class GeckoLayerClient extends LayerClient implements GeckoEvent
// JS-side viewport dimensions override the java-side ones because
// 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 = getLayerController().getViewportSize();
FloatSize viewportSize = mLayerController.getViewportSize();
mGeckoViewport = mNewGeckoViewport;
mGeckoViewport.setSize(viewportSize);
LayerController controller = getLayerController();
PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin();
mTileLayer.setOrigin(PointUtils.round(displayportOrigin));
mTileLayer.setResolution(mGeckoViewport.getZoomFactor());
this.tileLayerUpdated();
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(controller.getZoomFactor(), mGeckoViewport.getZoomFactor())) {
controller.setPageSize(mGeckoViewport.getPageSize());
if (FloatUtils.fuzzyEquals(mLayerController.getZoomFactor(), mGeckoViewport.getZoomFactor())) {
mLayerController.setPageSize(mGeckoViewport.getPageSize());
}
} else {
controller.setViewportMetrics(mGeckoViewport);
controller.abortPanZoomAnimation();
mLayerController.setViewportMetrics(mGeckoViewport);
mLayerController.abortPanZoomAnimation();
}
}
@@ -284,14 +186,15 @@ public abstract class GeckoLayerClient extends LayerClient implements GeckoEvent
// size is zero (which indicates that the rendering surface hasn't been
// allocated yet).
boolean screenSizeChanged = (metrics.widthPixels != mScreenSize.width || metrics.heightPixels != mScreenSize.height);
boolean viewportSizeValid = (getLayerController() != null && getLayerController().getViewportSize().isPositive());
boolean viewportSizeValid = (mLayerController != null && mLayerController.getViewportSize().isPositive());
if (!(force || (screenSizeChanged && viewportSizeValid))) {
return;
}
mScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
IntSize bufferSize = getBufferSize(), tileSize = getTileSize();
IntSize bufferSize = getBufferSize();
IntSize tileSize = getTileSize();
Log.e(LOGTAG, "### Screen-size changed to " + mScreenSize);
@@ -301,13 +204,12 @@ public abstract class GeckoLayerClient extends LayerClient implements GeckoEvent
LOKitShell.sendEvent(event);
}
@Override
public void render() {
adjustViewportWithThrottling();
}
private void adjustViewportWithThrottling() {
if (!getLayerController().getRedrawHint())
if (!mLayerController.getRedrawHint())
return;
if (mPendingViewportAdjust)
@@ -315,7 +217,7 @@ public abstract class GeckoLayerClient extends LayerClient implements GeckoEvent
long timeDelta = System.currentTimeMillis() - mLastViewportChangeTime;
if (timeDelta < MIN_VIEWPORT_CHANGE_DELAY) {
getLayerController().getView().postDelayed(
mLayerController.getView().postDelayed(
new Runnable() {
public void run() {
mPendingViewportAdjust = false;
@@ -330,13 +232,12 @@ public abstract class GeckoLayerClient extends LayerClient implements GeckoEvent
adjustViewport();
}
@Override
public void viewportSizeChanged() {
mViewportSizeChanged = true;
}
private void adjustViewport() {
ViewportMetrics viewportMetrics = new ViewportMetrics(getLayerController().getViewportMetrics());
ViewportMetrics viewportMetrics = new ViewportMetrics(mLayerController.getViewportMetrics());
PointF viewportOffset = viewportMetrics.getOptimumViewportOffset(getBufferSize());
viewportMetrics.setViewportOffset(viewportOffset);
@@ -366,7 +267,6 @@ public abstract class GeckoLayerClient extends LayerClient implements GeckoEvent
}
}
@Override
public void geometryChanged() {
sendResizeEventIfNecessary();
render();
@@ -381,18 +281,4 @@ public abstract class GeckoLayerClient extends LayerClient implements GeckoEvent
private void sendResizeEventIfNecessary() {
sendResizeEventIfNecessary(false);
}
/**
* Used by robocop for testing purposes. Not for production use! This is called via reflection by robocop.
*/
public void setDrawListener(DrawListener listener) {
mDrawListener = listener;
}
/**
* Used by robocop for testing purposes. Not for production use! This is used via reflection by robocop.
*/
public interface DrawListener {
public void drawFinished(int x, int y, int width, int height);
}
}
\ No newline at end of file
\ No newline at end of file
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java
index de8076a..42bc0b6 100644
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java
+++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java
@@ -41,7 +41,7 @@ package org.mozilla.gecko.gfx;
import org.libreoffice.LOKitShell;
import org.mozilla.gecko.gfx.CairoImage;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.LayerClient;
import org.mozilla.gecko.gfx.GeckoLayerClient;
import org.mozilla.gecko.gfx.LayerController;
import org.mozilla.gecko.gfx.LayerRenderer;
import org.mozilla.gecko.gfx.MultiTileLayer;
@@ -77,20 +77,11 @@ public class GeckoSoftwareLayerClient extends GeckoLayerClient {
mFormat = CairoImage.FORMAT_ARGB32;
}
/*protected void finalize() throws Throwable {
try {
if (mBuffer != null)
LOKitShell.freeDirectBuffer(mBuffer);
mBuffer = null;
} finally {
super.finalize();
}
}*/
public void setLayerController(LayerController layerController) {
super.setLayerController(layerController);
layerController.setRoot(mTileLayer);
if (mGeckoViewport != null) {
layerController.setViewportMetrics(mGeckoViewport);
}
@@ -104,7 +95,7 @@ public class GeckoSoftwareLayerClient extends GeckoLayerClient {
if(mTileLayer == null)
mTileLayer = new MultiTileLayer(TILE_SIZE);
getLayerController().setRoot(mTileLayer);
mLayerController.setRoot(mTileLayer);
// Force a resize event to be sent because the results of this
// are different depending on what tile system we're using
@@ -114,22 +105,11 @@ public class GeckoSoftwareLayerClient extends GeckoLayerClient {
}
@Override
protected boolean shouldDrawProceed(int tileWidth, int tileHeight) {
// Make sure the tile-size matches. If it doesn't, we could crash trying
// to access invalid memory.
if (tileWidth != TILE_SIZE.width || tileHeight != TILE_SIZE.height) {
Log.e(LOGTAG, "Aborting draw, incorrect tile size of " + tileWidth + "x" + tileHeight);
return false;
}
return true;
}
public boolean beginDrawing(int width, int height, int tileWidth, int tileHeight, String metadata) {
boolean shouldContinue = super.beginDrawing(width, height, tileWidth, tileHeight, metadata);
@Override
public Rect beginDrawing(int width, int height, int tileWidth, int tileHeight, String metadata) {
Rect bufferRect = super.beginDrawing(width, height, tileWidth, tileHeight, metadata);
if (bufferRect == null) {
return bufferRect;
if (!shouldContinue) {
return shouldContinue;
}
// If the window size has changed, reallocate the buffer to match.
@@ -137,7 +117,7 @@ public class GeckoSoftwareLayerClient extends GeckoLayerClient {
mBufferSize = new IntSize(width, height);
}
return bufferRect;
return shouldContinue;
}
@Override
@@ -147,76 +127,6 @@ public class GeckoSoftwareLayerClient extends GeckoLayerClient {
}
}
/*private void copyPixelsFromMultiTileLayer(Bitmap target) {
Canvas c = new Canvas(target);
ByteBuffer tileBuffer = mBuffer.slice();
int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat) / 8;
for (int y = 0; y < mBufferSize.height; y += TILE_SIZE.height) {
for (int x = 0; x < mBufferSize.width; x += TILE_SIZE.width) {
// Calculate tile size
IntSize tileSize = new IntSize(Math.min(mBufferSize.width - x, TILE_SIZE.width),
Math.min(mBufferSize.height - y, TILE_SIZE.height));
// Create a Bitmap from this tile
Bitmap tile = Bitmap.createBitmap(tileSize.width, tileSize.height,
CairoUtils.cairoFormatTobitmapConfig(mFormat));
tile.copyPixelsFromBuffer(tileBuffer.asIntBuffer());
// Copy the tile to the master Bitmap and recycle it
c.drawBitmap(tile, x, y, null);
tile.recycle();
// Progress the buffer to the next tile
tileBuffer.position(tileSize.getArea() * bpp);
tileBuffer = tileBuffer.slice();
}
}
}*/
@Override
protected void tileLayerUpdated() {
/* No-op. */
}
@Override
public Bitmap getBitmap() {
if (mTileLayer == null)
return null;
// Begin a tile transaction, otherwise the buffer can be destroyed while
// we're reading from it.
/*beginTransaction(mTileLayer);
try {
if (mBuffer == null || mBufferSize.width <= 0 || mBufferSize.height <= 0)
return null;
try {
Bitmap b = null;
if (mTileLayer instanceof MultiTileLayer) {
b = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height,CairoUtils.cairoFormatTobitmapConfig(mFormat));
copyPixelsFromMultiTileLayer(b);
} else {
Log.w(LOGTAG, "getBitmap() called on a layer (" + mTileLayer + ") we don't know how to get a bitmap from");
}
return b;
} catch (OutOfMemoryError oom) {
Log.w(LOGTAG, "Unable to create bitmap", oom);
return null;
}
} finally {
endTransaction(mTileLayer);
}*/
return null;
}
@Override
public int getType() {
return LAYER_CLIENT_TYPE_SOFTWARE;
}
@Override
protected IntSize getBufferSize() {
return new IntSize(
@@ -235,3 +145,4 @@ public class GeckoSoftwareLayerClient extends GeckoLayerClient {
}
}
}
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerClient.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerClient.java
deleted file mode 100644
index 4f46108..0000000
--- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/gfx/LayerClient.java
+++ /dev/null
@@ -1,81 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
/**
* A layer client provides tiles and manages other information used by the layer controller.
*/
public abstract class LayerClient {
private LayerController mLayerController;
public abstract void geometryChanged();
public abstract void viewportSizeChanged();
protected abstract void render();
public LayerController getLayerController() {
return mLayerController;
}
public void setLayerController(LayerController layerController) {
mLayerController = layerController;
}
/**
* A utility function for calling Layer.beginTransaction with the
* appropriate LayerView.
*/
public void beginTransaction(Layer aLayer) {
if (mLayerController != null) {
LayerView view = mLayerController.getView();
if (view != null) {
aLayer.beginTransaction(view);
return;
}
}
aLayer.beginTransaction();
}
// Included for symmetry.
public void endTransaction(Layer aLayer) {
aLayer.endTransaction();
}
}
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 250dc84..e237052 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
@@ -88,7 +88,7 @@ public class LayerController {
private boolean mWaitForTouchListeners;
private PanZoomController mPanZoomController;
private OnTouchListener mOnTouchListener; /* The touch listener. */
private LayerClient mLayerClient; /* The layer client. */
private GeckoLayerClient mLayerClient; /* The layer client. */
/* The new color for the checkerboard. */
private int mCheckerboardColor;
private boolean mCheckerboardShouldShowChecks;
@@ -111,11 +111,11 @@ public class LayerController {
mForceRedraw = true;
}
public LayerClient getLayerClient() {
public GeckoLayerClient getLayerClient() {
return mLayerClient;
}
public void setLayerClient(LayerClient layerClient) {
public void setLayerClient(GeckoLayerClient layerClient) {
mLayerClient = layerClient;
layerClient.setLayerController(this);
}
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 8c0fce4..521e60a 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
@@ -88,7 +88,7 @@ abstract class Axis {
private float mTouchPos; /* Position of the most recent touch event on the current drag. */
private float mLastTouchPos; /* Position of the touch event before touchPos. */
private float mVelocity; /* Velocity in this direction; pixels per animation frame. */
public boolean mScrollingDisabled; /* Whether movement on this axis is locked. */
private boolean mScrollingDisabled; /* Whether movement on this axis is locked. */
private boolean mDisableSnap; /* Whether overscroll snapping is disabled. */
private float mDisplacement;
@@ -147,7 +147,7 @@ abstract class Axis {
}
private Overscroll getOverscroll() {
boolean minus = (getOrigin() < 0.0f);
boolean minus = getOrigin() < 0.0f;
boolean plus = (getViewportEnd() > getPageLength());
if (minus && plus) {
return Overscroll.BOTH;
@@ -164,10 +164,14 @@ abstract class Axis {
// overscrolled on this axis, returns 0.
private float getExcess() {
switch (getOverscroll()) {
case MINUS: return -getOrigin();
case PLUS: return getViewportEnd() - getPageLength();
case BOTH: return getViewportEnd() - getPageLength() - getOrigin();
default: return 0.0f;
case MINUS:
return -getOrigin();
case PLUS:
return getViewportEnd() - getPageLength();
case BOTH:
return getViewportEnd() - getPageLength() - getOrigin();
default:
return 0.0f;
}
}
@@ -176,8 +180,7 @@ abstract class Axis {
* possible and this axis has not been scroll locked while panning. Otherwise, returns false.
*/
private boolean scrollable() {
return getViewportLength() <= getPageLength() - MIN_SCROLLABLE_DISTANCE &&
!mScrollingDisabled;
return getViewportLength() <= getPageLength() - MIN_SCROLLABLE_DISTANCE && !mScrollingDisabled;
}
/*
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 c3caccc..066f4ce 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
@@ -38,22 +38,23 @@
package org.mozilla.gecko.ui;
import org.json.JSONObject;
import org.json.JSONException;
import org.libreoffice.LOKitShell;
import org.libreoffice.LibreOfficeMainActivity;
import org.mozilla.gecko.gfx.FloatSize;
import org.mozilla.gecko.gfx.LayerController;
import org.mozilla.gecko.gfx.PointUtils;
import org.mozilla.gecko.gfx.ViewportMetrics;
import org.mozilla.gecko.util.FloatUtils;
import org.mozilla.gecko.GeckoEventListener;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.FloatMath;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import org.json.JSONObject;
import org.libreoffice.LOKitShell;
import org.libreoffice.LibreOfficeMainActivity;
import org.mozilla.gecko.GeckoEventListener;
import org.mozilla.gecko.gfx.FloatSize;
import org.mozilla.gecko.gfx.LayerController;
import org.mozilla.gecko.gfx.PointUtils;
import org.mozilla.gecko.gfx.ViewportMetrics;
import org.mozilla.gecko.util.FloatUtils;
import java.util.Timer;
import java.util.TimerTask;
@@ -65,29 +66,19 @@ import java.util.TimerTask;
*/
public class PanZoomController
extends GestureDetector.SimpleOnGestureListener
implements SimpleScaleGestureDetector.SimpleScaleGestureListener, GeckoEventListener
{
private static final String LOGTAG = "GeckoPanZoomController";
private static String MESSAGE_ZOOM_RECT = "Browser:ZoomToRect";
private static String MESSAGE_ZOOM_PAGE = "Browser:ZoomToPageWidth";
// Animation stops if the velocity is below this value when overscrolled or panning.
private static final float STOPPED_THRESHOLD = 4.0f;
// Animation stops is the velocity is below this threshold when flinging.
private static final float FLING_STOPPED_THRESHOLD = 0.1f;
implements SimpleScaleGestureDetector.SimpleScaleGestureListener, GeckoEventListener {
// The distance the user has to pan before we recognize it as such (e.g. to avoid 1-pixel pans
// between the touch-down and touch-up of a click). In units of density-independent pixels.
public static final float PAN_THRESHOLD = 1/16f * LOKitShell.getDpi();
public static final float PAN_THRESHOLD = 1 / 16f * LOKitShell.getDpi();
private static final String LOGTAG = "GeckoPanZoomController";
// Animation stops if the velocity is below this value when overscrolled or panning.
private static final float STOPPED_THRESHOLD = 4.0f;
// Animation stops is the velocity is below this threshold when flinging.
private static final float FLING_STOPPED_THRESHOLD = 0.1f;
// Angle from axis within which we stay axis-locked
private static final double AXIS_LOCK_ANGLE = Math.PI / 6.0; // 30 degrees
// 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 final float[] EASE_OUT_ANIMATION_FRAMES = {
0.00000f, /* 0 */
@@ -107,27 +98,13 @@ public class PanZoomController
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 */
TOUCHING, /* one touch-start event received */
PANNING_LOCKED, /* touch-start followed by move (i.e. panning with axis lock) */
PANNING, /* panning without axis lock */
PANNING_HOLD, /* in panning, but not moving.
* similar to TOUCHING but after starting a pan */
PANNING_HOLD_LOCKED, /* like PANNING_HOLD, but axis lock still in effect */
PINCHING, /* nth touch-start, where n > 1. this mode allows pan and zoom */
ANIMATED_ZOOM /* animated zoom to a new rect */
}
private static String MESSAGE_ZOOM_RECT = "Browser:ZoomToRect";
private static String MESSAGE_ZOOM_PAGE = "Browser:ZoomToPageWidth";
private final LayerController mController;
private final SubdocumentScrollHelper mSubscroller;
private final Axis mX;
private final Axis mY;
private Thread mMainThread;
/* The timer that handles flings or bounces. */
private Timer mAnimationTimer;
/* The runnable being scheduled by the animation timer. */
@@ -146,31 +123,19 @@ public class PanZoomController
mY = new AxisY(mSubscroller);
mMainThread = LibreOfficeMainActivity.mAppContext.getMainLooper().getThread();
checkMainThread();
mState = PanZoomState.NOTHING;
//GeckoAppShell.registerGeckoEventListener(MESSAGE_ZOOM_RECT, this);
//GeckoAppShell.registerGeckoEventListener(MESSAGE_ZOOM_PAGE, this);
}
// for debugging bug 713011; it can be taken out once that is resolved.
private void checkMainThread() {
if (mMainThread != Thread.currentThread()) {
// log with full stack trace
Log.e(LOGTAG, "Uh-oh, we're running on the wrong thread!", new Exception());
}
}
public void handleMessage(String event, JSONObject message) {
Log.i(LOGTAG, "Got message: " + event);
try {
if (MESSAGE_ZOOM_RECT.equals(event)) {
float x = (float)message.getDouble("x");
float y = (float)message.getDouble("y");
float x = (float) message.getDouble("x");
float y = (float) message.getDouble("y");
final RectF zoomRect = new RectF(x, y,
x + (float)message.getDouble("w"),
y + (float)message.getDouble("h"));
x + (float) message.getDouble("w"),
y + (float) message.getDouble("h"));
mController.post(new Runnable() {
public void run() {
animatedZoomTo(zoomRect);
@@ -185,9 +150,9 @@ public class PanZoomController
float newHeight = viewableRect.height() * pageSize.width / viewableRect.width();
float dh = viewableRect.height() - newHeight; // increase in the height
final RectF r = new RectF(0.0f,
y + dh/2,
y + dh / 2,
pageSize.width,
y + dh/2 + newHeight);
y + dh / 2 + newHeight);
mController.post(new Runnable() {
public void run() {
animatedZoomTo(r);
@@ -201,17 +166,23 @@ public class PanZoomController
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: return onTouchStart(event);
case MotionEvent.ACTION_MOVE: return onTouchMove(event);
case MotionEvent.ACTION_UP: return onTouchEnd(event);
case MotionEvent.ACTION_CANCEL: return onTouchCancel(event);
default: return false;
case MotionEvent.ACTION_DOWN:
return onTouchStart(event);
case MotionEvent.ACTION_MOVE:
return onTouchMove(event);
case MotionEvent.ACTION_UP:
return onTouchEnd(event);
case MotionEvent.ACTION_CANCEL:
return onTouchCancel(event);
default:
return false;
}
}
/** This function must be called from the UI thread. */
/**
* This function must be called from the UI thread.
*/
public void abortAnimation() {
checkMainThread();
// this happens when gecko changes the viewport on us or if the device is rotated.
// if that's the case, abort any animation in progress and re-zoom so that the page
// snaps to edges. for other cases (where the user's finger(s) are down) don't do
@@ -235,11 +206,13 @@ public class PanZoomController
}
}
/** This must be called on the UI thread. */
/**
* This must be called on the UI thread.
*/
public void pageSizeUpdated() {
if (mState == PanZoomState.NOTHING) {
ViewportMetrics validated = getValidViewportMetrics();
if (! mController.getViewportMetrics().fuzzyEquals(validated)) {
if (!mController.getViewportMetrics().fuzzyEquals(validated)) {
// page size changed such that we are now in overscroll. snap to the
// the nearest valid viewport
mController.setViewportMetrics(validated);
@@ -248,10 +221,6 @@ public class PanZoomController
}
}
/*
* Panning/scrolling
*/
private boolean onTouchStart(MotionEvent event) {
Log.d(LOGTAG, "onTouchStart in state " + mState);
// user is taking control of movement, so stop
@@ -279,6 +248,9 @@ public class PanZoomController
return false;
}
/*
* Panning/scrolling
*/
private boolean onTouchMove(MotionEvent event) {
Log.d(LOGTAG, "onTouchMove in state " + mState);
@@ -295,8 +267,6 @@ public class PanZoomController
}
cancelTouch();
startPanning(event.getX(0), event.getY(0), event.getEventTime());
//GeckoApp.mAppContext.hidePlugins(false /* don't hide layers */);
//GeckoApp.mAutoCompletePopup.hide();
track(event);
return true;
@@ -404,7 +374,7 @@ public class PanZoomController
}
private void track(float x, float y, long time) {
float timeDelta = (float)(time - mLastEventTime);
float timeDelta = (float) (time - mLastEventTime);
if (FloatUtils.fuzzyEquals(timeDelta, 0)) {
// probably a duplicate event, ignore it. using a zero timeDelta will mess
// up our velocity
@@ -490,8 +460,10 @@ public class PanZoomController
mAnimationRunnable = runnable;
mAnimationTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() { mController.post(runnable); }
}, 0, 1000L/60L);
public void run() {
mController.post(runnable);
}
}, 0, 1000L / 60L);
}
/* Stops the fling or bounce animation. */
@@ -504,14 +476,12 @@ public class PanZoomController
mAnimationRunnable.terminate();
mAnimationRunnable = null;
}
//GeckoApp.mAppContext.showPlugins();
}
private float getVelocity() {
float xvel = mX.getRealVelocity();
float yvel = mY.getRealVelocity();
return FloatMath.sqrt(xvel * xvel + yvel * yvel);
float xVelocity = mX.getRealVelocity();
float yVelocity = mY.getRealVelocity();
return FloatMath.sqrt(xVelocity * xVelocity + yVelocity * yVelocity);
}
private boolean stopped() {
@@ -526,13 +496,229 @@ public class PanZoomController
mX.displace();
mY.displace();
PointF displacement = getDisplacement();
if (! mSubscroller.scrollBy(displacement)) {
if (!mSubscroller.scrollBy(displacement)) {
synchronized (mController) {
mController.scrollBy(displacement);
}
}
}
private void finishAnimation() {
Log.d(LOGTAG, "Finishing animation at " + mController.getViewportMetrics());
stopAnimationTimer();
// Force a viewport synchronisation
mController.setForceRedraw();
mController.notifyLayerClientOfGeometryChange();
}
/* Returns the nearest viewport metrics with no overscroll visible. */
private ViewportMetrics getValidViewportMetrics() {
return getValidViewportMetrics(new ViewportMetrics(mController.getViewportMetrics()));
}
private ViewportMetrics getValidViewportMetrics(ViewportMetrics viewportMetrics) {
Log.d(LOGTAG, "generating valid viewport using " + viewportMetrics);
/* First, we adjust the zoom factor so that we can make no overscrolled area visible. */
float zoomFactor = viewportMetrics.getZoomFactor();
FloatSize pageSize = viewportMetrics.getPageSize();
RectF viewport = viewportMetrics.getViewport();
float focusX = viewport.width() / 2.0f;
float focusY = viewport.height() / 2.0f;
float minZoomFactor = 0.0f;
if (viewport.width() > pageSize.width && pageSize.width > 0) {
float scaleFactor = viewport.width() / pageSize.width;
minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor);
focusX = 0.0f;
}
if (viewport.height() > pageSize.height && pageSize.height > 0) {
float scaleFactor = viewport.height() / pageSize.height;
minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor);
focusY = 0.0f;
}
if (!FloatUtils.fuzzyEquals(minZoomFactor, 0.0f)) {
// if one (or both) of the page dimensions is smaller than the viewport,
// zoom using the top/left as the focus on that axis. this prevents the
// scenario where, if both dimensions are smaller than the viewport, but
// 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);
} else if (zoomFactor > MAX_ZOOM) {
PointF center = new PointF(viewport.width() / 2.0f, viewport.height() / 2.0f);
viewportMetrics.scaleTo(MAX_ZOOM, center);
}
/* Now we pan to the right origin. */
viewportMetrics.setViewport(viewportMetrics.getClampedViewport());
Log.d(LOGTAG, "generated valid viewport as " + viewportMetrics);
return viewportMetrics;
}
/*
* Zooming
*/
@Override
public boolean onScaleBegin(SimpleScaleGestureDetector detector) {
Log.d(LOGTAG, "onScaleBegin in " + mState);
if (mState == PanZoomState.ANIMATED_ZOOM)
return false;
mState = PanZoomState.PINCHING;
mLastZoomFocus = new PointF(detector.getFocusX(), detector.getFocusY());
cancelTouch();
return true;
}
@Override
public boolean onScale(SimpleScaleGestureDetector detector) {
Log.d(LOGTAG, "onScale in state " + mState);
if (mState == PanZoomState.ANIMATED_ZOOM)
return false;
float prevSpan = detector.getPreviousSpan();
if (FloatUtils.fuzzyEquals(prevSpan, 0.0f)) {
// let's eat this one to avoid setting the new zoom to infinity (bug 711453)
return true;
}
float spanRatio = detector.getCurrentSpan() / prevSpan;
/*
* 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());
if (spanRatio > 1.0f) {
spanRatio = 1.0f + (spanRatio - 1.0f) * resistance;
} else {
spanRatio = 1.0f - (1.0f - spanRatio) * resistance;
}
synchronized (mController) {
float newZoomFactor = mController.getZoomFactor() * spanRatio;
if (newZoomFactor >= MAX_ZOOM) {
// apply resistance when zooming past MAX_ZOOM,
// such that it asymptotically reaches MAX_ZOOM + 1.0
// but never exceeds that
float excessZoom = newZoomFactor - MAX_ZOOM;
excessZoom = 1.0f - (float) Math.exp(-excessZoom);
newZoomFactor = MAX_ZOOM + excessZoom;
}
mController.scrollBy(new PointF(mLastZoomFocus.x - detector.getFocusX(),
mLastZoomFocus.y - detector.getFocusY()));
PointF focus = new PointF(detector.getFocusX(), detector.getFocusY());
mController.scaleWithFocus(newZoomFactor, focus);
}
mLastZoomFocus.set(detector.getFocusX(), detector.getFocusY());
return true;
}
@Override
public void onScaleEnd(SimpleScaleGestureDetector detector) {
Log.d(LOGTAG, "onScaleEnd in " + mState);
if (mState == PanZoomState.ANIMATED_ZOOM)
return;
// switch back to the touching state
startTouch(detector.getFocusX(), detector.getFocusY(), detector.getEventTime());
// Force a viewport synchronisation
//GeckoApp.mAppContext.showPlugins();
mController.setForceRedraw();
mController.notifyLayerClientOfGeometryChange();
}
public boolean getRedrawHint() {
return (mState != PanZoomState.PINCHING && mState != PanZoomState.ANIMATED_ZOOM);
}
@Override
public void onLongPress(MotionEvent motionEvent) {
}
@Override
public boolean onDown(MotionEvent motionEvent) {
return false;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
return true;
}
@Override
public boolean onDoubleTap(MotionEvent motionEvent) {
return true;
}
public void cancelTouch() {
}
private boolean animatedZoomTo(RectF zoomToRect) {
mState = PanZoomState.ANIMATED_ZOOM;
final float startZoom = mController.getZoomFactor();
RectF viewport = mController.getViewport();
// 1. adjust the aspect ratio of zoomToRect to match that of the current viewport,
// enlarging as necessary (if it gets too big, it will get shrunk in the next step).
// while enlarging make sure we enlarge equally on both sides to keep the target rect
// centered.
float targetRatio = viewport.width() / viewport.height();
float rectRatio = zoomToRect.width() / zoomToRect.height();
if (FloatUtils.fuzzyEquals(targetRatio, rectRatio)) {
// all good, do nothing
} else if (targetRatio < rectRatio) {
// need to increase zoomToRect height
float newHeight = zoomToRect.width() / targetRatio;
zoomToRect.top -= (newHeight - zoomToRect.height()) / 2;
zoomToRect.bottom = zoomToRect.top + newHeight;
} else { // targetRatio > rectRatio) {
// need to increase zoomToRect width
float newWidth = targetRatio * zoomToRect.height();
zoomToRect.left -= (newWidth - zoomToRect.width()) / 2;
zoomToRect.right = zoomToRect.left + newWidth;
}
float finalZoom = viewport.width() * startZoom / zoomToRect.width();
ViewportMetrics finalMetrics = new ViewportMetrics(mController.getViewportMetrics());
finalMetrics.setOrigin(new PointF(zoomToRect.left, zoomToRect.top));
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.
finalMetrics = getValidViewportMetrics(finalMetrics);
bounce(finalMetrics);
return true;
}
private enum PanZoomState {
NOTHING, /* no touch-start events received */
FLING, /* all touches removed, but we're still scrolling page */
TOUCHING, /* one touch-start event received */
PANNING_LOCKED, /* touch-start followed by move (i.e. panning with axis lock) */
PANNING, /* panning without axis lock */
PANNING_HOLD, /* in panning, but not moving.
* similar to TOUCHING but after starting a pan */
PANNING_HOLD_LOCKED, /* like PANNING_HOLD, but axis lock still in effect */
PINCHING, /* nth touch-start, where n > 1. this mode allows pan and zoom */
ANIMATED_ZOOM /* animated zoom to a new rect */
}
private abstract class AnimationRunnable implements Runnable {
private boolean mAnimationTerminated;
@@ -667,255 +853,45 @@ public class PanZoomController
}
}
private void finishAnimation() {
checkMainThread();
Log.d(LOGTAG, "Finishing animation at " + mController.getViewportMetrics());
stopAnimationTimer();
// Force a viewport synchronisation
//GeckoApp.mAppContext.showPlugins();
mController.setForceRedraw();
mController.notifyLayerClientOfGeometryChange();
}
/* Returns the nearest viewport metrics with no overscroll visible. */
private ViewportMetrics getValidViewportMetrics() {
return getValidViewportMetrics(new ViewportMetrics(mController.getViewportMetrics()));
}
private ViewportMetrics getValidViewportMetrics(ViewportMetrics viewportMetrics) {
Log.d(LOGTAG, "generating valid viewport using " + viewportMetrics);
/* First, we adjust the zoom factor so that we can make no overscrolled area visible. */
float zoomFactor = viewportMetrics.getZoomFactor();
FloatSize pageSize = viewportMetrics.getPageSize();
RectF viewport = viewportMetrics.getViewport();
float focusX = viewport.width() / 2.0f;
float focusY = viewport.height() / 2.0f;
float minZoomFactor = 0.0f;
if (viewport.width() > pageSize.width && pageSize.width > 0) {
float scaleFactor = viewport.width() / pageSize.width;
minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor);
focusX = 0.0f;
}
if (viewport.height() > pageSize.height && pageSize.height > 0) {
float scaleFactor = viewport.height() / pageSize.height;
minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor);
focusY = 0.0f;
}
if (!FloatUtils.fuzzyEquals(minZoomFactor, 0.0f)) {
// if one (or both) of the page dimensions is smaller than the viewport,
// zoom using the top/left as the focus on that axis. this prevents the
// scenario where, if both dimensions are smaller than the viewport, but
// 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);
} else if (zoomFactor > MAX_ZOOM) {
PointF center = new PointF(viewport.width() / 2.0f, viewport.height() / 2.0f);
viewportMetrics.scaleTo(MAX_ZOOM, center);
}
/* Now we pan to the right origin. */
viewportMetrics.setViewport(viewportMetrics.getClampedViewport());
Log.d(LOGTAG, "generated valid viewport as " + viewportMetrics);
return viewportMetrics;
}
private class AxisX extends Axis {
AxisX(SubdocumentScrollHelper subscroller) { super(subscroller); }
AxisX(SubdocumentScrollHelper subscroller) {
super(subscroller);
}
@Override
public float getOrigin() { return mController.getOrigin().x; }
public float getOrigin() {
return mController.getOrigin().x;
}
@Override
protected float getViewportLength() { return mController.getViewportSize().width; }
protected float getViewportLength() {
return mController.getViewportSize().width;
}
@Override
protected float getPageLength() { return mController.getPageSize().width; }
protected float getPageLength() {
return mController.getPageSize().width;
}
}
private class AxisY extends Axis {
AxisY(SubdocumentScrollHelper subscroller) { super(subscroller); }
AxisY(SubdocumentScrollHelper subscroller) {
super(subscroller);
}
@Override
public float getOrigin() { return mController.getOrigin().y; }
public float getOrigin() {
return mController.getOrigin().y;
}
@Override
protected float getViewportLength() { return mController.getViewportSize().height; }
protected float getViewportLength() {
return mController.getViewportSize().height;
}
@Override
protected float getPageLength() { return mController.getPageSize().height; }
}
/*
* Zooming
*/
@Override
public boolean onScaleBegin(SimpleScaleGestureDetector detector) {
Log.d(LOGTAG, "onScaleBegin in " + mState);
if (mState == PanZoomState.ANIMATED_ZOOM)
return false;
mState = PanZoomState.PINCHING;
mLastZoomFocus = new PointF(detector.getFocusX(), detector.getFocusY());
//GeckoApp.mAppContext.hidePlugins(false /* don't hide layers, only views */);
//GeckoApp.mAutoCompletePopup.hide();
cancelTouch();
return true;
}
@Override
public boolean onScale(SimpleScaleGestureDetector detector) {
Log.d(LOGTAG, "onScale in state " + mState);
if (mState == PanZoomState.ANIMATED_ZOOM)
return false;
float prevSpan = detector.getPreviousSpan();
if (FloatUtils.fuzzyEquals(prevSpan, 0.0f)) {
// let's eat this one to avoid setting the new zoom to infinity (bug 711453)
return true;
protected float getPageLength() {
return mController.getPageSize().height;
}
float spanRatio = detector.getCurrentSpan() / prevSpan;
/*
* 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());
if (spanRatio > 1.0f)
spanRatio = 1.0f + (spanRatio - 1.0f) * resistance;
else
spanRatio = 1.0f - (1.0f - spanRatio) * resistance;
synchronized (mController) {
float newZoomFactor = mController.getZoomFactor() * spanRatio;
if (newZoomFactor >= MAX_ZOOM) {
// apply resistance when zooming past MAX_ZOOM,
// such that it asymptotically reaches MAX_ZOOM + 1.0
// but never exceeds that
float excessZoom = newZoomFactor - MAX_ZOOM;
excessZoom = 1.0f - (float)Math.exp(-excessZoom);
newZoomFactor = MAX_ZOOM + excessZoom;
}
mController.scrollBy(new PointF(mLastZoomFocus.x - detector.getFocusX(),
mLastZoomFocus.y - detector.getFocusY()));
PointF focus = new PointF(detector.getFocusX(), detector.getFocusY());
mController.scaleWithFocus(newZoomFactor, focus);
}
mLastZoomFocus.set(detector.getFocusX(), detector.getFocusY());
return true;
}
@Override
public void onScaleEnd(SimpleScaleGestureDetector detector) {
Log.d(LOGTAG, "onScaleEnd in " + mState);
if (mState == PanZoomState.ANIMATED_ZOOM)
return;
// switch back to the touching state
startTouch(detector.getFocusX(), detector.getFocusY(), detector.getEventTime());
// Force a viewport synchronisation
//GeckoApp.mAppContext.showPlugins();
mController.setForceRedraw();
mController.notifyLayerClientOfGeometryChange();
}
public boolean getRedrawHint() {
return (mState != PanZoomState.PINCHING && mState != PanZoomState.ANIMATED_ZOOM);
}
private void sendPointToGecko(String event, MotionEvent motionEvent) {
String json;
try {
PointF point = new PointF(motionEvent.getX(), motionEvent.getY());
point = mController.convertViewPointToLayerPoint(point);
if (point == null) {
return;
}
json = PointUtils.toJSON(point).toString();
} catch (Exception e) {
Log.e(LOGTAG, "Unable to convert point to JSON for " + event, e);
return;
}
//GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(event, json));
}
@Override
public void onLongPress(MotionEvent motionEvent) {
sendPointToGecko("Gesture:LongPress", motionEvent);
}
@Override
public boolean onDown(MotionEvent motionEvent) {
sendPointToGecko("Gesture:ShowPress", motionEvent);
return false;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
//GeckoApp.mAutoCompletePopup.hide();
sendPointToGecko("Gesture:SingleTap", motionEvent);
return true;
}
@Override
public boolean onDoubleTap(MotionEvent motionEvent) {
sendPointToGecko("Gesture:DoubleTap", motionEvent);
return true;
}
public void cancelTouch() {
//GeckoEvent e = GeckoEvent.createBroadcastEvent("Gesture:CancelTouch", "");
//GeckoAppShell.sendEventToGecko(e);
}
private boolean animatedZoomTo(RectF zoomToRect) {
//GeckoApp.mAutoCompletePopup.hide();
mState = PanZoomState.ANIMATED_ZOOM;
final float startZoom = mController.getZoomFactor();
RectF viewport = mController.getViewport();
// 1. adjust the aspect ratio of zoomToRect to match that of the current viewport,
// enlarging as necessary (if it gets too big, it will get shrunk in the next step).
// while enlarging make sure we enlarge equally on both sides to keep the target rect
// centered.
float targetRatio = viewport.width() / viewport.height();
float rectRatio = zoomToRect.width() / zoomToRect.height();
if (FloatUtils.fuzzyEquals(targetRatio, rectRatio)) {
// all good, do nothing
} else if (targetRatio < rectRatio) {
// need to increase zoomToRect height
float newHeight = zoomToRect.width() / targetRatio;
zoomToRect.top -= (newHeight - zoomToRect.height()) / 2;
zoomToRect.bottom = zoomToRect.top + newHeight;
} else { // targetRatio > rectRatio) {
// need to increase zoomToRect width
float newWidth = targetRatio * zoomToRect.height();
zoomToRect.left -= (newWidth - zoomToRect.width()) / 2;
zoomToRect.right = zoomToRect.left + newWidth;
}
float finalZoom = viewport.width() * startZoom / zoomToRect.width();
ViewportMetrics finalMetrics = new ViewportMetrics(mController.getViewportMetrics());
finalMetrics.setOrigin(new PointF(zoomToRect.left, zoomToRect.top));
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.
finalMetrics = getValidViewportMetrics(finalMetrics);
bounce(finalMetrics);
return true;
}
}