[Libreoffice-commits] core.git: Branch 'feature/android-viewer' - 3 commits - android/experimental

Tomaž Vajngerl tomaz.vajngerl at collabora.com
Mon Jun 23 07:37:09 PDT 2014


 android/experimental/LOAndroid/.idea/misc.xml                                                          |    3 
 android/experimental/LOAndroid2/.idea/libraries/appcompat_v7_19_1_0.xml                                |   10 
 android/experimental/LOAndroid2/.idea/libraries/support_v4_19_1_0.xml                                  |   11 
 android/experimental/LOAndroid2/.idea/modules.xml                                                      |    1 
 android/experimental/LOAndroid2/LOAndroid2.iml                                                         |   12 
 android/experimental/LOAndroid2/app/app.iml                                                            |    4 
 android/experimental/LOAndroid2/app/build.gradle                                                       |    1 
 android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOEvent.java                         |    6 
 android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitShell.java                      |   13 
 android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitThread.java                     |   64 -
 android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LibreOfficeMainActivity.java         |   18 
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CairoGLInfo.java               |   35 
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CheckerboardImage.java         |    6 
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/FlexibleGLSurfaceView.java     |  218 ++++
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/FloatSize.java                 |   25 
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GLController.java              |  279 +++++
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GLThread.java                  |  181 +++
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoGLLayerClient.java        |  265 +++++
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java          |  414 ++++++++
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java  |  435 ++------
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/Layer.java                     |   71 +
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/LayerController.java           |  101 +-
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/LayerRenderer.java             |  499 ++++++----
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/LayerView.java                 |   27 
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/MultiTileLayer.java            |  190 ++-
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/RectUtils.java                 |   47 
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/ScrollbarLayer.java            |  282 ++++-
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/SingleTileLayer.java           |   51 -
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/TextureReaper.java             |    7 
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/TileLayer.java                 |   74 -
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/ViewTransform.java             |   51 +
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/ViewportMetrics.java           |  126 +-
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/VirtualLayer.java              |   80 +
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/WidgetTileLayer.java           |   69 +
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/ui/Axis.java                       |    4 
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/ui/PanZoomController.java          |   30 
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/ui/SimpleScaleGestureDetector.java |   54 -
 android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/ui/SubdocumentScrollHelper.java    |   16 
 android/experimental/LOAndroid2/app/src/main/res/menu/main.xml                                         |    3 
 39 files changed, 2801 insertions(+), 982 deletions(-)

New commits:
commit 94bfbe588ea1716f86f9993eacbf66eb97efe7c6
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Mon Jun 23 11:03:19 2014 +0200

    LODroid2: GLES2 fixes, code cleanup
    
    Change-Id: I368f8caaf0433387a330a63182aab292defef0ca

diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitThread.java b/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitThread.java
index 08c4949..ea3472b 100644
--- a/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitThread.java
+++ b/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitThread.java
@@ -28,8 +28,8 @@ public class LOKitThread extends Thread {
     private boolean draw() throws InterruptedException {
         final LibreOfficeMainActivity application = LibreOfficeMainActivity.mAppContext;
 
-        Bitmap bitmap = application.getLayerClient().getLayerController().getDrawable16("dummy_page");
-        bitmap = convert(bitmap, Bitmap.Config.RGB_565);
+        Bitmap bitmap = application.getLayerClient().getLayerController().getDrawable("docu");
+        //bitmap = convert(bitmap, Bitmap.Config.ARGB_8888);
 
         StringWriter stringWriter = new StringWriter();
 
@@ -41,11 +41,11 @@ public class LOKitThread extends Thread {
                 writer.name("y").value(0);
                 writer.name("width").value(bitmap.getWidth());
                 writer.name("height").value(bitmap.getHeight());
-                writer.name("pageWidth").value(1000);
-                writer.name("pageHeight").value(5000);
+                writer.name("pageWidth").value(bitmap.getWidth());
+                writer.name("pageHeight").value(bitmap.getHeight());
                 writer.name("offsetX").value(0);
                 writer.name("offsetY").value(0);
-                writer.name("zoom").value(1.0);
+                writer.name("zoom").value(0.5);
             } else {
                 writer.name("x").value(mViewportMetrics.getOrigin().x);
                 writer.name("y").value(mViewportMetrics.getOrigin().y);
diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java
index 97cbfb4..fa1d5ad 100644
--- a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java
+++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java
@@ -80,7 +80,7 @@ public class GeckoSoftwareLayerClient extends GeckoLayerClient {
     public GeckoSoftwareLayerClient(Context context) {
         super(context);
 
-        mFormat = CairoImage.FORMAT_RGB16_565;
+        mFormat = CairoImage.FORMAT_ARGB32;
 
         mCairoImage = new CairoImage() {
             @Override
diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/MultiTileLayer.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/MultiTileLayer.java
index 3ade6c1..3514b42 100644
--- a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/MultiTileLayer.java
+++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/MultiTileLayer.java
@@ -38,30 +38,26 @@
 
 package org.mozilla.gecko.gfx;
 
-import org.mozilla.gecko.gfx.CairoImage;
-import org.mozilla.gecko.gfx.IntSize;
-import org.mozilla.gecko.gfx.SingleTileLayer;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
-import android.util.Log;
+
 import java.nio.ByteBuffer;
-import java.nio.FloatBuffer;
 import java.util.ArrayList;
 
 /**
  * Encapsulates the logic needed to draw a layer made of multiple tiles.
- *
+ * <p/>
  * TODO: Support repeating.
  */
 public class MultiTileLayer extends Layer {
     private static final String LOGTAG = "GeckoMultiTileLayer";
 
     private final CairoImage mImage;
+    private final ArrayList<SubTile> mTiles;
     private IntSize mTileSize;
     private IntSize mBufferSize;
-    private final ArrayList<SubTile> mTiles;
 
     public MultiTileLayer(CairoImage image, IntSize tileSize) {
         super();
@@ -181,7 +177,7 @@ public class MultiTileLayer extends Layer {
                 if (!RectF.intersects(layerBounds, context.viewport)) {
                     if (firstDirtyTile == null)
                         firstDirtyTile = layer;
-                    dirtyTiles ++;
+                    dirtyTiles++;
                     invalid = true;
                 } else {
                     // This tile intersects with the screen and is dirty,
@@ -207,7 +203,7 @@ public class MultiTileLayer extends Layer {
         if (!screenUpdateDone && firstDirtyTile != null) {
             firstDirtyTile.setSkipTextureUpdate(false);
             firstDirtyTile.performUpdates(context);
-            dirtyTiles --;
+            dirtyTiles--;
         }
 
         return (dirtyTiles == 0);
@@ -217,7 +213,7 @@ public class MultiTileLayer extends Layer {
         IntSize size = getSize();
         for (SubTile layer : mTiles) {
             if (!inTransaction) {
-                layer.beginTransaction(null);
+                layer.beginTransaction();
             }
 
             if (origin != null) {
@@ -246,11 +242,11 @@ public class MultiTileLayer extends Layer {
     }
 
     @Override
-    public void beginTransaction(LayerView aView) {
-        super.beginTransaction(aView);
+    public void beginTransaction() {
+        super.beginTransaction();
 
         for (SubTile layer : mTiles) {
-            layer.beginTransaction(aView);
+            layer.beginTransaction();
         }
     }
 
commit b71cf4031426f8bf4e282143c92e3774d79bbdb7
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Mon Jun 23 11:00:59 2014 +0200

    LODroid2: Remove sdk v7 and v4 support libraries
    
    Change-Id: I41a30d44c44cb439dc2e60e212eea18b00f316dd

diff --git a/android/experimental/LOAndroid2/.idea/libraries/appcompat_v7_19_1_0.xml b/android/experimental/LOAndroid2/.idea/libraries/appcompat_v7_19_1_0.xml
deleted file mode 100644
index 7efbfdf..0000000
--- a/android/experimental/LOAndroid2/.idea/libraries/appcompat_v7_19_1_0.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<component name="libraryTable">
-  <library name="appcompat-v7-19.1.0">
-    <CLASSES>
-      <root url="jar://$PROJECT_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/19.1.0/classes.jar!/" />
-      <root url="file://$PROJECT_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/19.1.0/res" />
-    </CLASSES>
-    <JAVADOC />
-    <SOURCES />
-  </library>
-</component>
\ No newline at end of file
diff --git a/android/experimental/LOAndroid2/.idea/libraries/support_v4_19_1_0.xml b/android/experimental/LOAndroid2/.idea/libraries/support_v4_19_1_0.xml
deleted file mode 100644
index 1ca1ac6..0000000
--- a/android/experimental/LOAndroid2/.idea/libraries/support_v4_19_1_0.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
-  <library name="support-v4-19.1.0">
-    <CLASSES>
-      <root url="jar://$USER_HOME$/Programs/android-sdk-linux/extras/android/m2repository/com/android/support/support-v4/19.1.0/support-v4-19.1.0.jar!/" />
-    </CLASSES>
-    <JAVADOC />
-    <SOURCES>
-      <root url="jar://$USER_HOME$/Programs/android-sdk-linux/extras/android/m2repository/com/android/support/support-v4/19.1.0/support-v4-19.1.0-sources.jar!/" />
-    </SOURCES>
-  </library>
-</component>
\ No newline at end of file
diff --git a/android/experimental/LOAndroid2/app/app.iml b/android/experimental/LOAndroid2/app/app.iml
index 34b87a5..2fc60e0 100644
--- a/android/experimental/LOAndroid2/app/app.iml
+++ b/android/experimental/LOAndroid2/app/app.iml
@@ -61,8 +61,6 @@
     </content>
     <orderEntry type="jdk" jdkName="Android API 19 Platform" jdkType="Android SDK" />
     <orderEntry type="sourceFolder" forTests="false" />
-    <orderEntry type="library" exported="" name="appcompat-v7-19.1.0" level="project" />
-    <orderEntry type="library" exported="" name="support-v4-19.1.0" level="project" />
   </component>
 </module>
 
diff --git a/android/experimental/LOAndroid2/app/build.gradle b/android/experimental/LOAndroid2/app/build.gradle
index 7e98dd4c..6053f91 100644
--- a/android/experimental/LOAndroid2/app/build.gradle
+++ b/android/experimental/LOAndroid2/app/build.gradle
@@ -20,5 +20,4 @@ android {
 
 dependencies {
     compile fileTree(dir: 'libs', include: ['*.jar'])
-    compile 'com.android.support:appcompat-v7:19.+'
 }
diff --git a/android/experimental/LOAndroid2/app/src/main/res/menu/main.xml b/android/experimental/LOAndroid2/app/src/main/res/menu/main.xml
index 6768fd3..e9709c9 100644
--- a/android/experimental/LOAndroid2/app/src/main/res/menu/main.xml
+++ b/android/experimental/LOAndroid2/app/src/main/res/menu/main.xml
@@ -4,6 +4,5 @@
     tools:context="org.libreoffice.MainActivity" >
     <item android:id="@+id/action_settings"
         android:title="@string/action_settings"
-        android:orderInCategory="100"
-        app:showAsAction="never" />
+        android:orderInCategory="100" />
 </menu>
diff --git a/android/experimental/LOAndroid2/gradlew b/android/experimental/LOAndroid2/gradlew
old mode 100644
new mode 100755
commit ae642816dae500310d1ae2db1542d5651953bd92
Author: Tomaž Vajngerl <tomaz.vajngerl at collabora.com>
Date:   Tue Jun 17 13:05:33 2014 +0200

    LOAndroid2: use GLES2, latest GeckoSoftwareLayerClient
    
    Additionally add GeckoGLLayerClient as an alternative LayerClient
    
    Change-Id: I250b5d806f520231ad9a9b84ef8387e8830bb364

diff --git a/android/experimental/LOAndroid/.idea/misc.xml b/android/experimental/LOAndroid/.idea/misc.xml
index d0225fc..589a3ed 100644
--- a/android/experimental/LOAndroid/.idea/misc.xml
+++ b/android/experimental/LOAndroid/.idea/misc.xml
@@ -3,6 +3,9 @@
   <component name="DaemonCodeAnalyzer">
     <disable_hints />
   </component>
+  <component name="EntryPointsManager">
+    <entry_points version="2.0" />
+  </component>
   <component name="ProjectInspectionProfilesVisibleTreeState">
     <entry key="Project Default">
       <profile-state>
diff --git a/android/experimental/LOAndroid2/.idea/modules.xml b/android/experimental/LOAndroid2/.idea/modules.xml
index f08135d..d5e166f 100644
--- a/android/experimental/LOAndroid2/.idea/modules.xml
+++ b/android/experimental/LOAndroid2/.idea/modules.xml
@@ -3,6 +3,7 @@
   <component name="ProjectModuleManager">
     <modules>
       <module fileurl="file://$PROJECT_DIR$/LOAndroid.iml" filepath="$PROJECT_DIR$/LOAndroid.iml" />
+      <module fileurl="file://$PROJECT_DIR$/LOAndroid2.iml" filepath="$PROJECT_DIR$/LOAndroid2.iml" />
       <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
     </modules>
   </component>
diff --git a/android/experimental/LOAndroid2/LOAndroid2.iml b/android/experimental/LOAndroid2/LOAndroid2.iml
new file mode 100644
index 0000000..edb62a6
--- /dev/null
+++ b/android/experimental/LOAndroid2/LOAndroid2.iml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/.gradle" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
+
diff --git a/android/experimental/LOAndroid2/app/app.iml b/android/experimental/LOAndroid2/app/app.iml
index e89240b..34b87a5 100644
--- a/android/experimental/LOAndroid2/app/app.iml
+++ b/android/experimental/LOAndroid2/app/app.iml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="LOAndroid" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
+<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="LOAndroid2" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
   <component name="FacetManager">
     <facet type="android-gradle" name="Android-Gradle">
       <configuration>
diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOEvent.java b/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOEvent.java
index a539669..bf4f98b 100644
--- a/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOEvent.java
+++ b/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOEvent.java
@@ -20,7 +20,7 @@ public class LOEvent {
 
     ViewportMetrics viewportMetrics;
 
-    public LOEvent(int type, int width, int height, int widthPixels, int heightPixels) {
+    public LOEvent(int type, int width, int height, int widthPixels, int heightPixels, int tileWidth, int tileHeight) {
         mType = type;
         mTypeString = "Size Changed";
     }
@@ -45,8 +45,8 @@ public class LOEvent {
         return new LOEvent(DRAW, rect);
     }
 
-    public static LOEvent sizeChanged(int width, int height, int widthPixels, int heightPixels) {
-        return new LOEvent(SIZE_CHANGED, width, height, widthPixels, heightPixels);
+    public static LOEvent sizeChanged(int width, int height, int widthPixels, int heightPixels, int tileWidth, int tileHeight) {
+        return new LOEvent(SIZE_CHANGED, width, height, widthPixels, heightPixels, tileWidth, tileHeight);
     }
 
     public static LOEvent tileSize(IntSize tileSize) {
diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitShell.java b/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitShell.java
index 69b634c..9dde790 100644
--- a/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitShell.java
+++ b/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitShell.java
@@ -115,4 +115,17 @@ public class LOKitShell {
 
         layerController.notifyLayerClientOfGeometryChange();*/
     }
+
+    public static void viewSizeChanged() {
+    }
+
+    public static void scheduleComposite() {
+    }
+
+    public static void schedulePauseComposition() {
+    }
+
+    public static void scheduleResumeComposition() {
+
+    }
 }
diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitThread.java b/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitThread.java
index 37bb51c..08c4949 100644
--- a/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitThread.java
+++ b/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LOKitThread.java
@@ -4,6 +4,7 @@ import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.Rect;
 import android.util.JsonWriter;
 
 import org.mozilla.gecko.gfx.ViewportMetrics;
@@ -11,8 +12,6 @@ import org.mozilla.gecko.gfx.ViewportMetrics;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.nio.ByteBuffer;
-import java.nio.ShortBuffer;
-import java.util.Arrays;
 import java.util.Random;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
@@ -26,28 +25,11 @@ public class LOKitThread extends Thread {
     LOKitThread() {
     }
 
-    private void draw() throws InterruptedException {
+    private boolean draw() throws InterruptedException {
         final LibreOfficeMainActivity application = LibreOfficeMainActivity.mAppContext;
 
-        Bitmap bitmap = application.getSoftwareLayerClient().getLayerController().getDrawable16("dummy_page");
-        bitmap  = convert(bitmap, Bitmap.Config.RGB_565);
-
-        application.getSoftwareLayerClient().beginDrawing(bitmap.getWidth(), bitmap.getHeight());
-        //application.getSoftwareLayerClient().beginDrawing(500,500);
-
-        ByteBuffer buffer = application.getSoftwareLayerClient().getBuffer();
-        bitmap.copyPixelsToBuffer(buffer.asIntBuffer());
-
-        /*short mainColor16 = convertTo16Bit(rand.nextInt());
-
-        short[] mainPattern = new short[500];
-        Arrays.fill(mainPattern, mainColor16);
-
-        buffer.rewind();
-        ShortBuffer shortBuffer = buffer.asShortBuffer();
-        for (int i = 0; i < 500; i++) {
-            shortBuffer.put(mainPattern);
-        }*/
+        Bitmap bitmap = application.getLayerClient().getLayerController().getDrawable16("dummy_page");
+        bitmap = convert(bitmap, Bitmap.Config.RGB_565);
 
         StringWriter stringWriter = new StringWriter();
 
@@ -64,7 +46,6 @@ public class LOKitThread extends Thread {
                 writer.name("offsetX").value(0);
                 writer.name("offsetY").value(0);
                 writer.name("zoom").value(1.0);
-                writer.name("allowZoom").value(true);
             } else {
                 writer.name("x").value(mViewportMetrics.getOrigin().x);
                 writer.name("y").value(mViewportMetrics.getOrigin().y);
@@ -75,7 +56,6 @@ public class LOKitThread extends Thread {
                 writer.name("offsetX").value(mViewportMetrics.getViewportOffset().x);
                 writer.name("offsetY").value(mViewportMetrics.getViewportOffset().y);
                 writer.name("zoom").value(mViewportMetrics.getZoomFactor());
-                writer.name("allowZoom").value(mViewportMetrics.getAllowZoom());
             }
             writer.name("backgroundColor").value("rgb(255,255,255)");
             writer.endObject();
@@ -83,15 +63,24 @@ public class LOKitThread extends Thread {
         } catch (IOException ex) {
         }
 
-        application.getSoftwareLayerClient().endDrawing(0, 0, bitmap.getWidth(), bitmap.getHeight(), stringWriter.toString(), false);
-        //application.getSoftwareLayerClient().endDrawing(0, 0, 500, 500, stringWriter.toString(), false);
-        application.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                application.getSoftwareLayerClient().handleMessage("Viewport:UpdateLater", null);
-            }
-        });
+        Rect bufferRect = application.getLayerClient().beginDrawing(bitmap.getWidth(), bitmap.getHeight(), 256, 256, stringWriter.toString(), false);
+
+        if (bufferRect == null) {
+            return false;
+        }
+            ByteBuffer buffer = application.getLayerClient().lockBuffer();
+            bitmap.copyPixelsToBuffer(buffer.asIntBuffer());
+            application.getLayerClient().unlockBuffer();
 
+            application.getLayerClient().endDrawing(0, 0, bitmap.getWidth(), bitmap.getHeight());
+
+            application.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    application.getLayerClient().handleMessage("Viewport:UpdateLater", null);
+                }
+            });
+        return true;
     }
 
     private short convertTo16Bit(int color) {
@@ -119,9 +108,8 @@ public class LOKitThread extends Thread {
                 if (!gEvents.isEmpty()) {
                     processEvent(gEvents.poll());
                 } else {
-                    if(!drawn) {
-                        draw();
-                        drawn = true;
+                    if (!drawn) {
+                        drawn = draw();
                     }
                     Thread.sleep(100L);
                 }
diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LibreOfficeMainActivity.java b/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LibreOfficeMainActivity.java
index a4fdf03..934464d 100644
--- a/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LibreOfficeMainActivity.java
+++ b/android/experimental/LOAndroid2/app/src/main/java/org/libreoffice/LibreOfficeMainActivity.java
@@ -23,7 +23,7 @@ public class LibreOfficeMainActivity extends Activity {
     private LinearLayout mMainLayout;
     private RelativeLayout mGeckoLayout;
     private static LayerController mLayerController;
-    private static GeckoSoftwareLayerClient mSoftwareLayerClient;
+    private static GeckoSoftwareLayerClient mLayerClient;
     private static LOKitThread sLOKitThread;
 
     public static LibreOfficeMainActivity mAppContext;
@@ -74,8 +74,12 @@ public class LibreOfficeMainActivity extends Activity {
 
         if (mLayerController == null) {
             mLayerController = new LayerController(this);
-            mSoftwareLayerClient = new GeckoSoftwareLayerClient(this);
-            mLayerController.setLayerClient(mSoftwareLayerClient);
+
+            Log.e(LOGTAG, "### Creating GeckoSoftwareLayerClient");
+            mLayerClient = new GeckoSoftwareLayerClient(this);
+            Log.e(LOGTAG, "### Done creating GeckoSoftwareLayerClient");
+
+            mLayerController.setLayerClient(mLayerClient);
             mGeckoLayout.addView(mLayerController.getView(), 0);
         }
 
@@ -88,7 +92,11 @@ public class LibreOfficeMainActivity extends Activity {
         Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - UI almost up");
     }
 
-    public static GeckoSoftwareLayerClient getSoftwareLayerClient() {
-        return mSoftwareLayerClient;
+    public static GeckoSoftwareLayerClient getLayerClient() {
+        return mLayerClient;
+    }
+
+    public static LayerController getLayerController() {
+        return mLayerController;
     }
 }
\ No newline at end of file
diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CairoGLInfo.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CairoGLInfo.java
index 16417e1..bd4eedc 100644
--- a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CairoGLInfo.java
+++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CairoGLInfo.java
@@ -39,7 +39,9 @@ package org.mozilla.gecko.gfx;
 
 import javax.microedition.khronos.opengles.GL10;
 
-/** Information needed to render Cairo bitmaps using OpenGL ES. */
+/**
+ * Information needed to render Cairo bitmaps using OpenGL ES.
+ */
 public class CairoGLInfo {
     public final int internalFormat;
     public final int format;
@@ -47,20 +49,23 @@ public class CairoGLInfo {
 
     public CairoGLInfo(int cairoFormat) {
         switch (cairoFormat) {
-        case CairoImage.FORMAT_ARGB32:
-            internalFormat = format = GL10.GL_RGBA; type = GL10.GL_UNSIGNED_BYTE;
-            break;
-        case CairoImage.FORMAT_RGB24:
-            internalFormat = format = GL10.GL_RGB; type = GL10.GL_UNSIGNED_BYTE;
-            break;
-        case CairoImage.FORMAT_RGB16_565:
-            internalFormat = format = GL10.GL_RGB; type = GL10.GL_UNSIGNED_SHORT_5_6_5;
-            break;
-        case CairoImage.FORMAT_A8:
-        case CairoImage.FORMAT_A1:
-            throw new RuntimeException("Cairo FORMAT_A1 and FORMAT_A8 unsupported");
-        default:
-            throw new RuntimeException("Unknown Cairo format");
+            case CairoImage.FORMAT_ARGB32:
+                internalFormat = format = GL10.GL_RGBA;
+                type = GL10.GL_UNSIGNED_BYTE;
+                break;
+            case CairoImage.FORMAT_RGB24:
+                internalFormat = format = GL10.GL_RGB;
+                type = GL10.GL_UNSIGNED_BYTE;
+                break;
+            case CairoImage.FORMAT_RGB16_565:
+                internalFormat = format = GL10.GL_RGB;
+                type = GL10.GL_UNSIGNED_SHORT_5_6_5;
+                break;
+            case CairoImage.FORMAT_A8:
+            case CairoImage.FORMAT_A1:
+                throw new RuntimeException("Cairo FORMAT_A1 and FORMAT_A8 unsupported");
+            default:
+                throw new RuntimeException("Unknown Cairo format");
         }
     }
 }
diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CheckerboardImage.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CheckerboardImage.java
index f9ff966..392d7e8 100644
--- a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CheckerboardImage.java
+++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/CheckerboardImage.java
@@ -38,7 +38,6 @@
 package org.mozilla.gecko.gfx;
 
 import org.libreoffice.LOKitShell;
-//import org.mozilla.gecko.GeckoAppShell;
 import android.graphics.Color;
 import java.nio.ByteBuffer;
 import java.nio.ShortBuffer;
@@ -62,7 +61,7 @@ public class CheckerboardImage extends CairoImage {
     /** Creates a new checkerboard image. */
     public CheckerboardImage() {
         int bpp = CairoUtils.bitsPerPixelForCairoFormat(FORMAT);
-        mBuffer = /*GeckoAppShell*/LOKitShell.allocateDirectBuffer(SIZE * SIZE * bpp / 8);
+        mBuffer = LOKitShell.allocateDirectBuffer(SIZE * SIZE * bpp / 8);
         update(true, Color.WHITE);
     }
 
@@ -146,7 +145,7 @@ public class CheckerboardImage extends CairoImage {
     protected void finalize() throws Throwable {
         try {
             if (mBuffer != null) {
-                /*GeckoAppShell*/LOKitShell.freeDirectBuffer(mBuffer);
+                LOKitShell.freeDirectBuffer(mBuffer);
             }
         } finally {
             super.finalize();
@@ -168,3 +167,4 @@ public class CheckerboardImage extends CairoImage {
         return FORMAT;
     }
 }
+
diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/FlexibleGLSurfaceView.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/FlexibleGLSurfaceView.java
new file mode 100644
index 0000000..dc20077
--- /dev/null
+++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/FlexibleGLSurfaceView.java
@@ -0,0 +1,218 @@
+/* -*- 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) 2011-2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton at mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+//import org.mozilla.gecko.GeckoApp;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import org.libreoffice.LibreOfficeMainActivity;
+
+public class FlexibleGLSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
+    private static final String LOGTAG = "GeckoFlexibleGLSurfaceView";
+
+    private GLSurfaceView.Renderer mRenderer;
+    private GLThread mGLThread; // Protected by this class's monitor.
+    private GLController mController;
+    private Listener mListener;
+
+    public FlexibleGLSurfaceView(Context context) {
+        super(context);
+        init();
+    }
+
+    public FlexibleGLSurfaceView(Context context, AttributeSet attributeSet) {
+        super(context, attributeSet);
+        init();
+    }
+
+    public void init() {
+        SurfaceHolder holder = getHolder();
+        holder.addCallback(this);
+        holder.setFormat(PixelFormat.RGB_565);
+
+        mController = new GLController(this);
+    }
+
+    public void setRenderer(GLSurfaceView.Renderer renderer) {
+        mRenderer = renderer;
+    }
+
+    public GLSurfaceView.Renderer getRenderer() {
+        return mRenderer;
+    }
+
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+    public synchronized void requestRender() {
+        if (mGLThread != null) {
+            mGLThread.renderFrame();
+        }
+        if (mListener != null) {
+            mListener.renderRequested();
+        }
+    }
+
+    /**
+     * Creates a Java GL thread. After this is called, the FlexibleGLSurfaceView may be used just
+     * like a GLSurfaceView. It is illegal to access the controller after this has been called.
+     */
+    public synchronized void createGLThread() {
+        if (mGLThread != null) {
+            throw new FlexibleGLSurfaceViewException("createGLThread() called with a GL thread " +
+                    "already in place!");
+        }
+
+        Log.e(LOGTAG, "### Creating GL thread!");
+        mGLThread = new GLThread(mController);
+        mGLThread.start();
+        notifyAll();
+    }
+
+    /**
+     * Destroys the Java GL thread. Returns a Thread that completes when the Java GL thread is
+     * fully shut down.
+     */
+    public synchronized Thread destroyGLThread() {
+        // Wait for the GL thread to be started.
+        Log.e(LOGTAG, "### Waiting for GL thread to be created...");
+        while (mGLThread == null) {
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        Log.e(LOGTAG, "### Destroying GL thread!");
+        Thread glThread = mGLThread;
+        mGLThread.shutdown();
+        mGLThread = null;
+        return glThread;
+    }
+
+    public synchronized void recreateSurface() {
+        if (mGLThread == null) {
+            throw new FlexibleGLSurfaceViewException("recreateSurface() called with no GL " +
+                    "thread active!");
+        }
+
+        mGLThread.recreateSurface();
+    }
+
+    public synchronized GLController getGLController() {
+        if (mGLThread != null) {
+            throw new FlexibleGLSurfaceViewException("getGLController() called with a GL thread " +
+                    "active; shut down the GL thread first!");
+        }
+
+        return mController;
+    }
+
+    public synchronized void surfaceChanged(SurfaceHolder holder, int format, int width,
+                                            int height) {
+        mController.sizeChanged(width, height);
+        if (mGLThread != null) {
+            mGLThread.surfaceChanged(width, height);
+        }
+
+        if (mListener != null) {
+            mListener.surfaceChanged(width, height);
+        }
+    }
+
+    public synchronized void surfaceCreated(SurfaceHolder holder) {
+        mController.surfaceCreated();
+        if (mGLThread != null) {
+            mGLThread.surfaceCreated();
+        }
+    }
+
+    public synchronized void surfaceDestroyed(SurfaceHolder holder) {
+        mController.surfaceDestroyed();
+        if (mGLThread != null) {
+            mGLThread.surfaceDestroyed();
+        }
+
+        if (mListener != null) {
+            mListener.compositionPauseRequested();
+        }
+    }
+
+    // Called from the compositor thread
+    public static GLController registerCxxCompositor() {
+        try {
+            Log.e(LOGTAG, "### registerCxxCompositor point A");
+            System.out.println("register layer comp");
+            Log.e(LOGTAG, "### registerCxxCompositor point B");
+            FlexibleGLSurfaceView flexView = (FlexibleGLSurfaceView) /*GeckoApp*/LibreOfficeMainActivity.mAppContext.getLayerController().getView();
+            Log.e(LOGTAG, "### registerCxxCompositor point C: " + flexView);
+            try {
+                flexView.destroyGLThread().join();
+            } catch (InterruptedException e) {}
+            Log.e(LOGTAG, "### registerCxxCompositor point D: " + flexView.getGLController());
+            return flexView.getGLController();
+        } catch (Exception e) {
+            Log.e(LOGTAG, "### Exception! " + e);
+            return null;
+        }
+    }
+
+    public interface Listener {
+        void renderRequested();
+        void compositionPauseRequested();
+        void compositionResumeRequested();
+        void surfaceChanged(int width, int height);
+    }
+
+    public static class FlexibleGLSurfaceViewException extends RuntimeException {
+        public static final long serialVersionUID = 1L;
+
+        FlexibleGLSurfaceViewException(String e) {
+            super(e);
+        }
+    }
+}
+
diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/FloatSize.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/FloatSize.java
index 21a712c..5fb73ec 100644
--- a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/FloatSize.java
+++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/FloatSize.java
@@ -38,21 +38,32 @@
 
 package org.mozilla.gecko.gfx;
 
-import org.mozilla.gecko.util.FloatUtils;
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.mozilla.gecko.util.FloatUtils;
 
 public class FloatSize {
     public final float width, height;
 
-    public FloatSize(FloatSize size) { width = size.width; height = size.height; }
-    public FloatSize(IntSize size) { width = size.width; height = size.height; }
-    public FloatSize(float aWidth, float aHeight) { width = aWidth; height = aHeight; }
+    public FloatSize(FloatSize size) {
+        width = size.width;
+        height = size.height;
+    }
+
+    public FloatSize(IntSize size) {
+        width = size.width;
+        height = size.height;
+    }
+
+    public FloatSize(float aWidth, float aHeight) {
+        width = aWidth;
+        height = aHeight;
+    }
 
     public FloatSize(JSONObject json) {
         try {
-            width = (float)json.getDouble("width");
-            height = (float)json.getDouble("height");
+            width = (float) json.getDouble("width");
+            height = (float) json.getDouble("height");
         } catch (JSONException e) {
             throw new RuntimeException(e);
         }
@@ -82,7 +93,7 @@ public class FloatSize {
      */
     public FloatSize interpolate(FloatSize to, float t) {
         return new FloatSize(FloatUtils.interpolate(width, to.width, t),
-                             FloatUtils.interpolate(height, to.height, t));
+                FloatUtils.interpolate(height, to.height, t));
     }
 }
 
diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GLController.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GLController.java
new file mode 100644
index 0000000..e8f2012
--- /dev/null
+++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GLController.java
@@ -0,0 +1,279 @@
+/* -*- 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) 2011-2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton at mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGL11;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL;
+import javax.microedition.khronos.opengles.GL10;
+
+public class GLController {
+    private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+    private static final String LOGTAG = "GeckoGLController";
+
+    private FlexibleGLSurfaceView mView;
+    private int mGLVersion;
+    private boolean mSurfaceValid;
+    private int mWidth, mHeight;
+
+    private EGL10 mEGL;
+    private EGLDisplay mEGLDisplay;
+    private EGLConfig mEGLConfig;
+    private EGLContext mEGLContext;
+    private EGLSurface mEGLSurface;
+
+    private GL mGL;
+
+    private static final int LOCAL_EGL_OPENGL_ES2_BIT = 4;
+
+    private static final int[] CONFIG_SPEC = {
+            EGL10.EGL_RED_SIZE, 5,
+            EGL10.EGL_GREEN_SIZE, 6,
+            EGL10.EGL_BLUE_SIZE, 5,
+            EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
+            EGL10.EGL_RENDERABLE_TYPE, LOCAL_EGL_OPENGL_ES2_BIT,
+            EGL10.EGL_NONE
+    };
+
+    public GLController(FlexibleGLSurfaceView view) {
+        mView = view;
+        mGLVersion = 2;
+        mSurfaceValid = false;
+    }
+
+    public void setGLVersion(int version) {
+        mGLVersion = version;
+    }
+
+    /** You must call this on the same thread you intend to use OpenGL on. */
+    public void initGLContext() {
+        initEGLContext();
+        createEGLSurface();
+    }
+
+    public void disposeGLContext() {
+        if (!mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
+                EGL10.EGL_NO_CONTEXT)) {
+            throw new GLControllerException("EGL context could not be released!");
+        }
+
+        if (mEGLSurface != null) {
+            if (!mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface)) {
+                throw new GLControllerException("EGL surface could not be destroyed!");
+            }
+
+            mEGLSurface = null;
+        }
+
+        if (mEGLContext == null) {
+            if (!mEGL.eglDestroyContext(mEGLDisplay, mEGLContext)) {
+                throw new GLControllerException("EGL context could not be destroyed!");
+            }
+
+            mGL = null;
+            mEGLDisplay = null;
+            mEGLConfig = null;
+            mEGLContext = null;
+        }
+    }
+
+    public GL getGL()                       { return mEGLContext.getGL(); }
+    public EGLDisplay getEGLDisplay()       { return mEGLDisplay;         }
+    public EGLConfig getEGLConfig()         { return mEGLConfig;          }
+    public EGLContext getEGLContext()       { return mEGLContext;         }
+    public EGLSurface getEGLSurface()       { return mEGLSurface;         }
+    public FlexibleGLSurfaceView getView()  { return mView;               }
+
+    public boolean hasSurface() {
+        return mEGLSurface != null;
+    }
+
+    public boolean swapBuffers() {
+        return mEGL.eglSwapBuffers(mEGLDisplay, mEGLSurface);
+    }
+
+    public boolean checkForLostContext() {
+        if (mEGL.eglGetError() != EGL11.EGL_CONTEXT_LOST) {
+            return false;
+        }
+
+        mEGLDisplay = null;
+        mEGLConfig = null;
+        mEGLContext = null;
+        mEGLSurface = null;
+        mGL = null;
+        return true;
+    }
+
+    public synchronized void waitForValidSurface() {
+        while (!mSurfaceValid) {
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    public synchronized int getWidth() {
+        return mWidth;
+    }
+
+    public synchronized int getHeight() {
+        return mHeight;
+    }
+
+    synchronized void surfaceCreated() {
+        mSurfaceValid = true;
+        notifyAll();
+    }
+
+    synchronized void surfaceDestroyed() {
+        mSurfaceValid = false;
+        notifyAll();
+    }
+
+    synchronized void sizeChanged(int newWidth, int newHeight) {
+        mWidth = newWidth;
+        mHeight = newHeight;
+    }
+
+    private void initEGL() {
+        mEGL = (EGL10)EGLContext.getEGL();
+
+        mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+        if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) {
+            throw new GLControllerException("eglGetDisplay() failed");
+        }
+
+        int[] version = new int[2];
+        if (!mEGL.eglInitialize(mEGLDisplay, version)) {
+            throw new GLControllerException("eglInitialize() failed");
+        }
+
+        mEGLConfig = chooseConfig();
+    }
+
+    private void initEGLContext() {
+        initEGL();
+
+        int[] attribList = { EGL_CONTEXT_CLIENT_VERSION, mGLVersion, EGL10.EGL_NONE };
+        mEGLContext = mEGL.eglCreateContext(mEGLDisplay, mEGLConfig, EGL10.EGL_NO_CONTEXT,
+                attribList);
+        if (mEGLContext == null || mEGLContext == EGL10.EGL_NO_CONTEXT) {
+            throw new GLControllerException("createContext() failed");
+        }
+    }
+
+    private EGLConfig chooseConfig() {
+        int[] numConfigs = new int[1];
+        if (!mEGL.eglChooseConfig(mEGLDisplay, CONFIG_SPEC, null, 0, numConfigs) ||
+                numConfigs[0] <= 0) {
+            throw new GLControllerException("No available EGL configurations");
+        }
+
+        EGLConfig[] configs = new EGLConfig[numConfigs[0]];
+        if (!mEGL.eglChooseConfig(mEGLDisplay, CONFIG_SPEC, configs, numConfigs[0], numConfigs)) {
+            throw new GLControllerException("No EGL configuration for that specification");
+        }
+
+        // Select the first 565 RGB configuration.
+        int[] red = new int[1], green = new int[1], blue = new int[1];
+        for (EGLConfig config : configs) {
+            mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_RED_SIZE, red);
+            mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_GREEN_SIZE, green);
+            mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_BLUE_SIZE, blue);
+            if (red[0] == 5 && green[0] == 6 && blue[0] == 5) {
+                return config;
+            }
+        }
+
+        throw new GLControllerException("No suitable EGL configuration found");
+    }
+
+    private void createEGLSurface() {
+        SurfaceHolder surfaceHolder = mView.getHolder();
+        mEGLSurface = mEGL.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surfaceHolder, null);
+        if (mEGLSurface == null || mEGLSurface == EGL10.EGL_NO_SURFACE) {
+            throw new GLControllerException("EGL window surface could not be created!");
+        }
+
+        if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
+            throw new GLControllerException("EGL surface could not be made into the current " +
+                    "surface!");
+        }
+
+        mGL = mEGLContext.getGL();
+
+        if (mView.getRenderer() != null) {
+            mView.getRenderer().onSurfaceCreated((GL10)mGL, mEGLConfig);
+            mView.getRenderer().onSurfaceChanged((GL10)mGL, mView.getWidth(), mView.getHeight());
+        }
+    }
+
+    // Provides an EGLSurface without assuming ownership of this surface.
+    private EGLSurface provideEGLSurface() {
+        if (mEGL == null) {
+            initEGL();
+        }
+
+        SurfaceHolder surfaceHolder = mView.getHolder();
+        mEGLSurface = mEGL.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surfaceHolder, null);
+        if (mEGLSurface == null || mEGLSurface == EGL10.EGL_NO_SURFACE) {
+            throw new GLControllerException("EGL window surface could not be created!");
+        }
+
+        return mEGLSurface;
+    }
+
+    public static class GLControllerException extends RuntimeException {
+        public static final long serialVersionUID = 1L;
+
+        GLControllerException(String e) {
+            super(e);
+        }
+    }
+}
+
diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GLThread.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GLThread.java
new file mode 100644
index 0000000..4f788f6
--- /dev/null
+++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GLThread.java
@@ -0,0 +1,181 @@
+/* -*- 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) 2011-2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton at mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import android.opengl.GLSurfaceView;
+import android.view.SurfaceHolder;
+import javax.microedition.khronos.opengles.GL10;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+
+// A GL thread managed by Java. It is not necessary to use this class to use the
+// FlexibleGLSurfaceView, but it can be helpful, especially if the GL rendering is to be done
+// entirely in Java.
+class GLThread extends Thread {
+    private LinkedBlockingQueue<Runnable> mQueue;
+    private GLController mController;
+    private boolean mRenderQueued;
+
+    public GLThread(GLController controller) {
+        mQueue = new LinkedBlockingQueue<Runnable>();
+        mController = controller;
+    }
+
+    @Override
+    public void run() {
+        while (true) {
+            Runnable runnable;
+            try {
+                runnable = mQueue.take();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+
+            runnable.run();
+            if (runnable instanceof ShutdownMessage) {
+                break;
+            }
+        }
+    }
+
+    public void recreateSurface() {
+        mQueue.add(new RecreateSurfaceMessage());
+    }
+
+    public void renderFrame() {
+        // Make sure there's only one render event in the queue at a time.
+        synchronized (this) {
+            if (!mRenderQueued) {
+                mQueue.add(new RenderFrameMessage());
+                mRenderQueued = true;
+            }
+        }
+    }
+
+    public void shutdown() {
+        mQueue.add(new ShutdownMessage());
+    }
+
+    public void surfaceChanged(int width, int height) {
+        mQueue.add(new SizeChangedMessage(width, height));
+    }
+
+    public void surfaceCreated() {
+        mQueue.add(new SurfaceCreatedMessage());
+    }
+
+    public void surfaceDestroyed() {
+        mQueue.add(new SurfaceDestroyedMessage());
+    }
+
+    private void doRecreateSurface() {
+        mController.disposeGLContext();
+        mController.initGLContext();
+    }
+
+    private GLSurfaceView.Renderer getRenderer() {
+        return mController.getView().getRenderer();
+    }
+
+    private class RecreateSurfaceMessage implements Runnable {
+        public void run() {
+            doRecreateSurface();
+        }
+    }
+
+    private class RenderFrameMessage implements Runnable {
+        public void run() {
+            synchronized (GLThread.this) {
+                mRenderQueued = false;
+            }
+
+            // Bail out if the surface was lost.
+            if (mController.getEGLSurface() == null) {
+                return;
+            }
+
+            GLSurfaceView.Renderer renderer = getRenderer();
+            if (renderer != null) {
+                renderer.onDrawFrame((GL10)mController.getGL());
+            }
+
+            mController.swapBuffers();
+            //if (!mController.swapBuffers() && mController.checkForLostContext()) {
+            //    doRecreateSurface();
+            //}
+        }
+    }
+
+    private class ShutdownMessage implements Runnable {
+        public void run() {
+            mController.disposeGLContext();
+            mController = null;
+        }
+    }
+
+    private class SizeChangedMessage implements Runnable {
+        private int mWidth, mHeight;
+
+        public SizeChangedMessage(int width, int height) {
+            mWidth = width;
+            mHeight = height;
+        }
+
+        public void run() {
+            GLSurfaceView.Renderer renderer = getRenderer();
+            if (renderer != null) {
+                renderer.onSurfaceChanged((GL10)mController.getGL(), mWidth, mHeight);
+            }
+        }
+    }
+
+    private class SurfaceCreatedMessage implements Runnable {
+        public void run() {
+            if (!mController.hasSurface()) {
+                mController.initGLContext();
+            }
+        }
+    }
+
+    private class SurfaceDestroyedMessage implements Runnable {
+        public void run() {
+            mController.disposeGLContext();
+        }
+    }
+}
+
diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoGLLayerClient.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoGLLayerClient.java
new file mode 100644
index 0000000..9e4f376
--- /dev/null
+++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoGLLayerClient.java
@@ -0,0 +1,265 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton at mozilla.com>
+ *
+ * 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;
+
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.View;
+
+import org.libreoffice.LOEvent;
+import org.libreoffice.LOKitShell;
+
+public class GeckoGLLayerClient extends GeckoLayerClient
+        implements FlexibleGLSurfaceView.Listener, VirtualLayer.Listener {
+    private static final String LOGTAG = "GeckoGLLayerClient";
+
+    private LayerRenderer mLayerRenderer;
+    private boolean mLayerRendererInitialized;
+
+    public GeckoGLLayerClient(Context context) {
+        super(context);
+    }
+
+    @Override
+    public Rect beginDrawing(int width, int height, int tileWidth, int tileHeight,
+                             String metadata, boolean hasDirectTexture) {
+        Rect bufferRect = super.beginDrawing(width, height, tileWidth, tileHeight,
+                metadata, hasDirectTexture);
+        if (bufferRect == null) {
+            return null;
+        }
+
+        // Be sure to adjust the buffer size; if it's not at least as large as the viewport size,
+        // ViewportMetrics.getOptimumViewportOffset() gets awfully confused and severe display
+        // corruption results!
+        if (mBufferSize.width != width || mBufferSize.height != height) {
+            mBufferSize = new IntSize(width, height);
+        }
+
+        return bufferRect;
+    }
+
+    @Override
+    protected boolean handleDirectTextureChange(boolean hasDirectTexture) {
+        Log.e(LOGTAG, "### handleDirectTextureChange");
+        if (mTileLayer != null) {
+            return false;
+        }
+
+        Log.e(LOGTAG, "### Creating virtual layer");
+        VirtualLayer virtualLayer = new VirtualLayer();
+        virtualLayer.setListener(this);
+        virtualLayer.setSize(getBufferSize());
+        getLayerController().setRoot(virtualLayer);
+        mTileLayer = virtualLayer;
+
+        sendResizeEventIfNecessary(true);
+        return true;
+    }
+
+    @Override
+    public void setLayerController(LayerController layerController) {
+        super.setLayerController(layerController);
+
+        LayerView view = layerController.getView();
+        view.setListener(this);
+
+        mLayerRenderer = new LayerRenderer(view);
+    }
+
+    @Override
+    protected boolean shouldDrawProceed(int tileWidth, int tileHeight) {
+        Log.e(LOGTAG, "### shouldDrawProceed");
+        // Always draw.
+        return true;
+    }
+
+    @Override
+    protected void updateLayerAfterDraw(Rect updatedRect) {
+        Log.e(LOGTAG, "### updateLayerAfterDraw");
+        // Nothing to do.
+    }
+
+    @Override
+    protected IntSize getBufferSize() {
+        View view = (View) getLayerController().getView();
+        IntSize size = new IntSize(view.getWidth(), view.getHeight());
+        Log.e(LOGTAG, "### getBufferSize " + size);
+        return size;
+    }
+
+    @Override
+    protected IntSize getTileSize() {
+        Log.e(LOGTAG, "### getTileSize " + getBufferSize());
+        return getBufferSize();
+    }
+
+    @Override
+    protected void tileLayerUpdated() {
+        // Set the new origin and resolution instantly.
+        mTileLayer.performUpdates(null);
+    }
+
+    @Override
+    public Bitmap getBitmap() {
+        Log.e(LOGTAG, "### getBitmap");
+        IntSize size = getBufferSize();
+        try {
+            return Bitmap.createBitmap(size.width, size.height, Bitmap.Config.RGB_565);
+        } catch (OutOfMemoryError oom) {
+            Log.e(LOGTAG, "Unable to create bitmap", oom);
+            return null;
+        }
+    }
+
+    @Override
+    public int getType() {
+        Log.e(LOGTAG, "### getType");
+        return LAYER_CLIENT_TYPE_GL;
+    }
+
+    public void dimensionsChanged(Point newOrigin, float newResolution) {
+        Log.e(LOGTAG, "### dimensionsChanged " + newOrigin + " " + newResolution);
+    }
+
+    /* Informs Gecko that the screen size has changed. */
+    @Override
+    protected void sendResizeEventIfNecessary(boolean force) {
+        Log.e(LOGTAG, "### sendResizeEventIfNecessary " + force);
+
+        IntSize newSize = getBufferSize();
+        if (!force && mScreenSize != null && mScreenSize.equals(newSize)) {
+            return;
+        }
+
+        mScreenSize = newSize;
+
+        Log.e(LOGTAG, "### Screen-size changed to " + mScreenSize);
+        //GeckoEvent event = GeckoEvent.createSizeChangedEvent(mScreenSize.width, mScreenSize.height,
+        //        mScreenSize.width, mScreenSize.height,
+        //        mScreenSize.width, mScreenSize.height);
+        //GeckoAppShell.sendEventToGecko(event);
+        LOEvent event = LOEvent.sizeChanged(mScreenSize.width, mScreenSize.height,
+                                            mScreenSize.width, mScreenSize.height,
+                                            mScreenSize.width, mScreenSize.height);
+        LOKitShell.sendEvent(event);
+
+    }
+
+    /**
+     * For Gecko to use.
+     */
+    public ViewTransform getViewTransform() {
+        Log.e(LOGTAG, "### getViewTransform()");
+
+        // NB: We don't begin a transaction here because this can be called in a synchronous
+        // manner between beginDrawing() and endDrawing(), and that will cause a deadlock.
+
+        LayerController layerController = getLayerController();
+        synchronized (layerController) {
+            ViewportMetrics viewportMetrics = layerController.getViewportMetrics();
+            PointF viewportOrigin = viewportMetrics.getOrigin();
+            Point tileOrigin = mTileLayer.getOrigin();
+            float scrollX = viewportOrigin.x;
+            float scrollY = viewportOrigin.y;
+            float zoomFactor = viewportMetrics.getZoomFactor();
+            Log.e(LOGTAG, "### Viewport metrics = " + viewportMetrics + " tile reso = " +
+                    mTileLayer.getResolution());
+            return new ViewTransform(scrollX, scrollY, zoomFactor);
+        }
+    }
+
+    public void renderRequested() {
+        Log.e(LOGTAG, "### Render requested, scheduling composite");
+        LOKitShell.scheduleComposite();
+    }
+
+    public void compositionPauseRequested() {
+        Log.e(LOGTAG, "### Scheduling PauseComposition");
+        LOKitShell.schedulePauseComposition();
+    }
+
+    public void compositionResumeRequested() {
+        Log.e(LOGTAG, "### Scheduling ResumeComposition");
+        LOKitShell.scheduleResumeComposition();
+    }
+
+    public void surfaceChanged(int width, int height) {
+        compositionPauseRequested();
+        LayerController layerController = getLayerController();
+        layerController.setViewportSize(new FloatSize(width, height));
+        compositionResumeRequested();
+        renderRequested();
+    }
+
+    /**
+     * For Gecko to use.
+     */
+    public LayerRenderer.Frame createFrame() {
+        // Create the shaders and textures if necessary.
+        if (!mLayerRendererInitialized) {
+            mLayerRenderer.createProgram();
+            mLayerRendererInitialized = true;
+        }
+
+        // Build the contexts and create the frame.
+        Layer.RenderContext pageContext = mLayerRenderer.createPageContext();
+        Layer.RenderContext screenContext = mLayerRenderer.createScreenContext();
+        return mLayerRenderer.createFrame(pageContext, screenContext);
+    }
+
+    /**
+     * For Gecko to use.
+     */
+    public void activateProgram() {
+        mLayerRenderer.activateProgram();
+    }
+
+    /**
+     * For Gecko to use.
+     */
+    public void deactivateProgram() {
+        mLayerRenderer.deactivateProgram();
+    }
+}
+
diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
new file mode 100644
index 0000000..09349b4
--- /dev/null
+++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
@@ -0,0 +1,414 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton at mozilla.com>
+ *   Chris Lord <chrislord.net at gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.libreoffice.LOEvent;
+import org.libreoffice.LOKitShell;
+import org.libreoffice.LibreOfficeMainActivity;
+import org.mozilla.gecko.util.FloatUtils;
+//import org.mozilla.gecko.GeckoApp;
+//import org.mozilla.gecko.GeckoAppShell;
+//import org.mozilla.gecko.GeckoEvent;
+import org.mozilla.gecko.GeckoEventListener;
+import org.json.JSONException;
+import org.json.JSONObject;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.SystemClock;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public abstract class GeckoLayerClient extends LayerClient implements GeckoEventListener {
+    private static final String LOGTAG = "GeckoLayerClient";
+
+    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;
+
+    protected IntSize mScreenSize;
+    protected IntSize mBufferSize;
+
+    protected Layer mTileLayer;
+
+    /* The viewport that Gecko is currently displaying. */
+    protected ViewportMetrics mGeckoViewport;
+
+    /* The viewport that Gecko will display when drawing is finished */
+    protected ViewportMetrics mNewGeckoViewport;
+
+    private static final long MIN_VIEWPORT_CHANGE_DELAY = 25L;
+    private long mLastViewportChangeTime;
+    private boolean mPendingViewportAdjust;
+    private boolean mViewportSizeChanged;
+
+    // mUpdateViewportOnEndDraw is used to indicate that we received a
+    // viewport update notification while drawing. therefore, when the
+    // draw finishes, we need to update the entire viewport rather than
+    // just the page size. this boolean should always be accessed from
+    // inside a transaction, so no synchronization is needed.
+    private boolean mUpdateViewportOnEndDraw;
+
+    private String mLastCheckerboardColor;
+
+    private static Pattern sColorPattern;
+
+    /* Used by robocop for testing purposes */
+    private DrawListener mDrawListener;
+
+    protected abstract boolean handleDirectTextureChange(boolean hasDirectTexture);
+    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();
+
+    public GeckoLayerClient(Context context) {
+        mScreenSize = new IntSize(0, 0);
+        mBufferSize = new IntSize(0, 0);
+    }
+
+    /** Attaches the root layer to the layer controller so that Gecko appears. */
+    @Override
+    public void setLayerController(LayerController layerController) {
+        super.setLayerController(layerController);
+
+        layerController.setRoot(mTileLayer);
+        if (mGeckoViewport != null) {
+            layerController.setViewportMetrics(mGeckoViewport);
+        }
+
+        //GeckoAppShell.registerGeckoEventListener("Viewport:UpdateAndDraw", this);
+        //GeckoAppShell.registerGeckoEventListener("Viewport:UpdateLater", this);
+
+        sendResizeEventIfNecessary();
+    }
+
+    public Rect beginDrawing(int width, int height, int tileWidth, int tileHeight,
+                             String metadata, boolean hasDirectTexture) {
+        Log.e(LOGTAG, "### beginDrawing " + width + " " + height + " " + tileWidth + " " +
+                tileHeight + " " + hasDirectTexture);
+
+        // If we've changed surface types, cancel this draw
+        if (handleDirectTextureChange(hasDirectTexture)) {
+            Log.e(LOGTAG, "### Cancelling draw due to direct texture change");
+            return null;
+        }
+
+        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;
+        }
+
+
+        // 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;
+    }
+
+    /*
+     * TODO: Would be cleaner if this took an android.graphics.Rect instead, but that would require
+     * a little more JNI magic.
+     */
+    public void endDrawing(int x, int y, int width, int height) {
+        synchronized (getLayerController()) {
+            try {
+                updateViewport(!mUpdateViewportOnEndDraw);
+                mUpdateViewportOnEndDraw = false;
+
+                Rect rect = new Rect(x, y, x + width, y + height);
+                updateLayerAfterDraw(rect);
+            } finally {
+                endTransaction(mTileLayer);
+            }
+        }
+        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) {
+        // save and restore the viewport size stored in java; never let the
+        // 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();
+        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());
+        } else {
+            controller.setViewportMetrics(mGeckoViewport);
+            controller.abortPanZoomAnimation();
+        }
+    }
+
+    /* Informs Gecko that the screen size has changed. */
+    protected void sendResizeEventIfNecessary(boolean force) {
+        Log.e(LOGTAG, "### sendResizeEventIfNecessary " + force);
+
+        DisplayMetrics metrics = new DisplayMetrics();
+        /*GeckoApp*/LibreOfficeMainActivity.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+
+        // Return immediately if the screen size hasn't changed or the viewport
+        // size is zero (which indicates that the rendering surface hasn't been
+        // allocated yet).
+        boolean screenSizeChanged = (metrics.widthPixels != mScreenSize.width ||
+                metrics.heightPixels != mScreenSize.height);
+        boolean viewportSizeValid = (getLayerController() != null &&
+                getLayerController().getViewportSize().isPositive());
+        if (!(force || (screenSizeChanged && viewportSizeValid))) {
+            return;
+        }
+
+        mScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
+        IntSize bufferSize = getBufferSize(), tileSize = getTileSize();
+
+        Log.e(LOGTAG, "### Screen-size changed to " + mScreenSize);
+        //GeckoEvent event = GeckoEvent.createSizeChangedEvent(bufferSize.width, bufferSize.height,
+        //        metrics.widthPixels, metrics.heightPixels,
+        //        tileSize.width, tileSize.height);
+        //GeckoAppShell.sendEventToGecko(event);
+        LOEvent event = LOEvent.sizeChanged(bufferSize.width, bufferSize.height,
+                                            metrics.widthPixels, metrics.heightPixels,
+                                            tileSize.width, tileSize.height);
+    }
+
+    // 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);
+    }
+
+    @Override
+    public void render() {
+        adjustViewportWithThrottling();
+    }
+
+    private void adjustViewportWithThrottling() {
+        if (!getLayerController().getRedrawHint())
+            return;
+
+        if (mPendingViewportAdjust)
+            return;
+
+        long timeDelta = System.currentTimeMillis() - mLastViewportChangeTime;
+        if (timeDelta < MIN_VIEWPORT_CHANGE_DELAY) {
+            getLayerController().getView().postDelayed(
+                    new Runnable() {
+                        public void run() {
+                            mPendingViewportAdjust = false;
+                            adjustViewport();
+                        }
+                    }, MIN_VIEWPORT_CHANGE_DELAY - timeDelta);
+            mPendingViewportAdjust = true;
+            return;
+        }
+
+        adjustViewport();
+    }
+
+    @Override
+    public void viewportSizeChanged() {
+        mViewportSizeChanged = true;
+    }
+
+    private void adjustViewport() {
+        ViewportMetrics viewportMetrics =
+                new ViewportMetrics(getLayerController().getViewportMetrics());
+
+        PointF viewportOffset = viewportMetrics.getOptimumViewportOffset(mBufferSize);
+        viewportMetrics.setViewportOffset(viewportOffset);
+        viewportMetrics.setViewport(viewportMetrics.getClampedViewport());
+
+        //GeckoAppShell.sendEventToGecko(GeckoEvent.createViewportEvent(viewportMetrics));
+        LOKitShell.sendEvent(LOEvent.viewport(viewportMetrics));
+        if (mViewportSizeChanged) {
+            mViewportSizeChanged = false;
+            //GeckoAppShell.viewSizeChanged();
+            LOKitShell.viewSizeChanged();
+        }
+
+        mLastViewportChangeTime = System.currentTimeMillis();
+    }
+
+    public void handleMessage(String event, JSONObject message) {
+        if ("Viewport:UpdateAndDraw".equals(event)) {
+            Log.e(LOGTAG, "### Java side Viewport:UpdateAndDraw()!");
+            mUpdateViewportOnEndDraw = true;
+
+            // Redraw everything.
+            Rect rect = new Rect(0, 0, mBufferSize.width, mBufferSize.height);
+            //GeckoAppShell.sendEventToGecko(GeckoEvent.createDrawEvent(rect));
+            LOKitShell.sendEvent(LOEvent.draw(rect));
+        } else if ("Viewport:UpdateLater".equals(event)) {
+            Log.e(LOGTAG, "### Java side Viewport:UpdateLater()!");
+            mUpdateViewportOnEndDraw = true;
+        }
+    }
+
+    @Override
+    public void geometryChanged() {
+        /* Let Gecko know if the screensize has changed */
+        sendResizeEventIfNecessary();
+        render();
+    }
+
+    public int getWidth() {
+        return mBufferSize.width;
+    }
+
+    public int getHeight() {
+        return mBufferSize.height;
+    }
+
+    public ViewportMetrics getGeckoViewportMetrics() {
+        // Return a copy, as we modify this inside the Gecko thread
+        if (mGeckoViewport != null)
+            return new ViewportMetrics(mGeckoViewport);
+        return null;
+    }
+
+    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);
+    }
+}
+
diff --git a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java
index 9385d81..97cbfb4 100644
--- a/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java
+++ b/android/experimental/LOAndroid2/app/src/main/java/org/mozilla/gecko/gfx/GeckoSoftwareLayerClient.java
@@ -38,130 +38,70 @@
 
 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.LayerController;
+import org.mozilla.gecko.gfx.LayerRenderer;
+import org.mozilla.gecko.gfx.MultiTileLayer;
+import org.mozilla.gecko.gfx.PointUtils;
+import org.mozilla.gecko.gfx.WidgetTileLayer;
+//import org.mozilla.gecko.GeckoAppShell;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.PointF;
+import android.graphics.Point;
 import android.graphics.Rect;
-import android.util.DisplayMetrics;
+import android.graphics.RectF;
 import android.util.Log;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.libreoffice.LOEvent;
-import org.libreoffice.LOKitShell;
-import org.libreoffice.LibreOfficeMainActivity;
-import org.mozilla.gecko.GeckoEventListener;
-import org.mozilla.gecko.util.FloatUtils;
-
 import java.nio.ByteBuffer;
-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;
 
 /**
  * Transfers a software-rendered Gecko to an ImageLayer so that it can be rendered by our
  * compositor.
- * <p/>
+ *
  * TODO: Throttle down Gecko's priority when we pan and zoom.
  */
-public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventListener {
+public class GeckoSoftwareLayerClient extends GeckoLayerClient {
     private static final String LOGTAG = "GeckoSoftwareLayerClient";
-    private static final IntSize TILE_SIZE = new IntSize(256, 256);
-    private static final long MIN_VIEWPORT_CHANGE_DELAY = 350L;
-    private static Pattern sColorPattern;
-    private Context mContext;
+
     private int mFormat;
-    private IntSize mScreenSize;
-    private IntSize mBufferSize;
+    private IntSize mViewportSize;
     private ByteBuffer mBuffer;
-    private Layer mTileLayer;
-    /* The viewport rect that Gecko is currently displaying. */
-    private ViewportMetrics mGeckoViewport;
+
     private CairoImage mCairoImage;
-    private long mLastViewportChangeTime;
-    private boolean mPendingViewportAdjust;
-    private boolean mViewportSizeChanged;
+
+    private static final IntSize TILE_SIZE = new IntSize(256, 256);
+
     // Whether or not the last paint we got used direct texturing
     private boolean mHasDirectTexture;
-    // mUpdateViewportOnEndDraw is used to indicate that we received a
-    // viewport update notification while drawing. therefore, when the
-    // draw finishes, we need to update the entire viewport rather than
-    // just the page size. this boolean should always be accessed from
-    // inside a transaction, so no synchronization is needed.
-    private boolean mUpdateViewportOnEndDraw;
 
     public GeckoSoftwareLayerClient(Context context) {
-        mContext = context;
+        super(context);
 
-        mScreenSize = new IntSize(0, 0);
-        mBufferSize = new IntSize(0, 0);
         mFormat = CairoImage.FORMAT_RGB16_565;
 
         mCairoImage = new CairoImage() {
             @Override
-            public ByteBuffer getBuffer() {
-                return mBuffer;
-            }
-
+            public ByteBuffer getBuffer() { return mBuffer; }
             @Override
-            public IntSize getSize() {
-                return mBufferSize;
-            }
-
+            public IntSize getSize() { return mBufferSize; }
             @Override
-            public int getFormat() {
-                return mFormat;
-            }
+            public int getFormat() { return mFormat; }
         };
-
-        mTileLayer = new MultiTileLayer(mCairoImage, TILE_SIZE);
-    }
-
-    // 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);
-    }
-
-    public int getWidth() {
-        return mBufferSize.width;
-    }
-
-    public int getHeight() {
-        return mBufferSize.height;
     }
 
     protected void finalize() throws Throwable {
         try {
             if (mBuffer != null)
-                /*GeckoAppShell*/ LOKitShell.freeDirectBuffer(mBuffer);
+                LOKitShell.freeDirectBuffer(mBuffer);
             mBuffer = null;
         } finally {
             super.finalize();
         }
     }
 
-    /**
-     * Attaches the root layer to the layer controller so that Gecko appears.
-     */
-    @Override
     public void setLayerController(LayerController layerController) {
         super.setLayerController(layerController);
 
@@ -172,142 +112,100 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
 
         //GeckoAppShell.registerGeckoEventListener("Viewport:UpdateAndDraw", this);
         //GeckoAppShell.registerGeckoEventListener("Viewport:UpdateLater", this);
+        //GeckoAppShell.registerGeckoEventListener("Checkerboard:Toggle", this);
 
-        // This needs to happen before a call to sendResizeEventIfNecessary
-        // happens, but only needs to be called once. As that is only called by
-        // the layer controller or this, here is a safe place to do so.
-        if (mTileLayer instanceof MultiTileLayer) {
-            //GeckoEvent event = new GeckoEvent(GeckoEvent.TILE_SIZE, TILE_SIZE);
-            //GeckoAppShell.sendEventToGecko(event);
-            LOKitShell.sendEvent(LOEvent.tileSize(TILE_SIZE));
-        }
-
-        sendResizeEventIfNecessary();
+        // XXX: Review pcwalton. This signature changed on m-c, should force = false here?
+        sendResizeEventIfNecessary(false);
     }
 
-    private void setHasDirectTexture(boolean hasDirectTexture) {
-        if (hasDirectTexture == mHasDirectTexture)
-            return;
+    @Override
+    protected boolean handleDirectTextureChange(boolean hasDirectTexture) {
+        if (mTileLayer != null && hasDirectTexture == mHasDirectTexture)
+            return false;
 
         mHasDirectTexture = hasDirectTexture;
 
-        IntSize tileSize;
         if (mHasDirectTexture) {
+            Log.i(LOGTAG, "Creating WidgetTileLayer");
             mTileLayer = new WidgetTileLayer(mCairoImage);
-            tileSize = new IntSize(0, 0);
         } else {
+            Log.i(LOGTAG, "Creating MultiTileLayer");
             mTileLayer = new MultiTileLayer(mCairoImage, TILE_SIZE);
-            tileSize = TILE_SIZE;
         }
 
         getLayerController().setRoot(mTileLayer);
 
-        //GeckoEvent event = new GeckoEvent(GeckoEvent.TILE_SIZE, tileSize);
-        //GeckoAppShell.sendEventToGecko(event);
-        LOKitShell.sendEvent(LOEvent.tileSize(tileSize));
-
         // Force a resize event to be sent because the results of this
         // are different depending on what tile system we're using
         sendResizeEventIfNecessary(true);
-    }
-
-    public void beginDrawing(int width, int height) {
-        beginTransaction(mTileLayer);
-
-        if (mBufferSize.width != width || mBufferSize.height != height) {
-            mBufferSize = new IntSize(width, height);
-
-            // Reallocate the buffer if necessary
 
-            // * 2 because it's a 16-bit buffer (so 2 bytes per pixel).
-            int size = mBufferSize.getArea() * 2;
-            if (mBuffer == null || mBuffer.capacity() != size) {
-                // Free the old buffer
-                if (mBuffer != null) {
-                    /*GeckoAppShell*/
-                    LOKitShell.freeDirectBuffer(mBuffer);
-                    mBuffer = null;
-                }
-
-                mBuffer = /*GeckoAppShell*/LOKitShell.allocateDirectBuffer(size);
-            }
-        }
+        return true;
     }
 
-    private void updateViewport(String viewportDescription, final boolean onlyUpdatePageSize) {
-        try {
-            JSONObject viewportObject = new JSONObject(viewportDescription);
-
-            // save and restore the viewport size stored in java; never let the
-            // 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();
-            mGeckoViewport = new ViewportMetrics(viewportObject);
-            mGeckoViewport.setSize(viewportSize);
-
-            LayerController controller = getLayerController();
-            PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin();
-            mTileLayer.setOrigin(PointUtils.round(displayportOrigin));
-            mTileLayer.setResolution(mGeckoViewport.getZoomFactor());
-
-            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());
-            } else {
-                Log.d(LOGTAG, "Received viewport update from gecko");
-                controller.setViewportMetrics(mGeckoViewport);
-                controller.abortPanZoomAnimation();
+    @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 (mHasDirectTexture) {
+            if (tileWidth != 0 || tileHeight != 0) {
+                Log.e(LOGTAG, "Aborting draw, incorrect tile size of " + tileWidth + "x" +
+                        tileHeight);
+                return false;
             }
-
-            // Update the background color, if it's present.
-            String backgroundColorString = viewportObject.optString("backgroundColor");
-            if (backgroundColorString != null) {
-                controller.setCheckerboardColor(parseColorFromGecko(backgroundColorString));
+        } else {
+            if (tileWidth != TILE_SIZE.width || tileHeight != TILE_SIZE.height) {
+                Log.e(LOGTAG, "Aborting draw, incorrect tile size of " + tileWidth + "x" +
+                        tileHeight);
+                return false;
             }
-        } catch (JSONException e) {
-            Log.e(LOGTAG, "Bad viewport description: " + viewportDescription);
-            throw new RuntimeException(e);
         }
-    }
 
-    public ByteBuffer getBuffer() {
-        return mBuffer;
+        return true;
     }
 
-    /*
-         * TODO: Would be cleaner if this took an android.graphics.Rect instead, but that would require
-         * a little more JNI magic.
-         */
-    public void endDrawing(int x, int y, int width, int height, String metadata, boolean hasDirectTexture) {
-        synchronized (getLayerController()) {
-            try {
-                updateViewport(metadata, !mUpdateViewportOnEndDraw);
-                mUpdateViewportOnEndDraw = false;
-                Rect rect = new Rect(x, y, x + width, y + height);
+    @Override
+    public Rect beginDrawing(int width, int height, int tileWidth, int tileHeight,
+                             String metadata, boolean hasDirectTexture) {
+        Rect bufferRect = super.beginDrawing(width, height, tileWidth, tileHeight,
+                metadata, hasDirectTexture);
+        if (bufferRect == null) {
+            return bufferRect;
+        }
 
-                setHasDirectTexture(hasDirectTexture);
+        // If the window size has changed, reallocate the buffer to match.
+        if (mBufferSize.width != width || mBufferSize.height != height) {
+            mBufferSize = new IntSize(width, height);
 
-                if (!mHasDirectTexture)
-                    ((MultiTileLayer) mTileLayer).invalidate(rect);
-            } finally {
-                endTransaction(mTileLayer);
+            // Reallocate the buffer if necessary
+            if (mTileLayer instanceof MultiTileLayer) {
+                int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat) / 8;
+                int size = mBufferSize.getArea() * bpp;
+                if (mBuffer == null || mBuffer.capacity() != size) {
+                    // Free the old buffer
+                    if (mBuffer != null) {
+                        LOKitShell.freeDirectBuffer(mBuffer);
+                        mBuffer = null;
+                    }
+
+                    mBuffer = LOKitShell.allocateDirectBuffer(size);
+                }
             }
         }
+
+        return bufferRect;
     }
 
-    public ViewportMetrics getGeckoViewportMetrics() {
-        // Return a copy, as we modify this inside the Gecko thread
-        if (mGeckoViewport != null)
-            return new ViewportMetrics(mGeckoViewport);
-        return null;
+    @Override
+    protected void updateLayerAfterDraw(Rect updatedRect) {
+        if (!(mTileLayer instanceof MultiTileLayer)) {
+            return;
+        }
+
+        ((MultiTileLayer)mTileLayer).invalidate(updatedRect);
     }
 
-    public void copyPixelsFromMultiTileLayer(Bitmap target) {
-        Canvas canvas = new Canvas(target);
+    private void copyPixelsFromMultiTileLayer(Bitmap target) {
+        Canvas c = new Canvas(target);
         ByteBuffer tileBuffer = mBuffer.slice();
         int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat) / 8;
 
@@ -323,7 +221,7 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
                 tile.copyPixelsFromBuffer(tileBuffer.asIntBuffer());
 
                 // Copy the tile to the master Bitmap and recycle it
-                canvas.drawBitmap(tile, x, y, null);
+                c.drawBitmap(tile, x, y, null);
                 tile.recycle();
 
                 // Progress the buffer to the next tile
@@ -333,7 +231,16 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
         }
     }
 
+    @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);
@@ -341,21 +248,17 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
             if (mBuffer == null || mBufferSize.width <= 0 || mBufferSize.height <= 0)
                 return null;
             try {
-                Bitmap bitmap = null;
+                Bitmap b = null;
 
                 if (mTileLayer instanceof MultiTileLayer) {
-                    bitmap = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height,
+                    b = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height,
                             CairoUtils.cairoFormatTobitmapConfig(mFormat));
-                    copyPixelsFromMultiTileLayer(bitmap);
-                } else if (mTileLayer instanceof SingleTileLayer) {
-                    bitmap = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height,
-                            CairoUtils.cairoFormatTobitmapConfig(mFormat));
-                    bitmap.copyPixelsFromBuffer(mBuffer.asIntBuffer());
+                    copyPixelsFromMultiTileLayer(b);
                 } else {
                     Log.w(LOGTAG, "getBitmap() called on a layer (" + mTileLayer + ") we don't know how to get a bitmap from");
                 }
 
-                return bitmap;
+                return b;
             } catch (OutOfMemoryError oom) {
                 Log.w(LOGTAG, "Unable to create bitmap", oom);
                 return null;
@@ -365,9 +268,7 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
         }
     }
 
-    /**
-     * Returns the back buffer. This function is for Gecko to use.
-     */
+    /** Returns the back buffer. This function is for Gecko to use. */
     public ByteBuffer lockBuffer() {
         return mBuffer;
     }
@@ -381,125 +282,41 @@ public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventL
     }
 
     @Override
-    public void geometryChanged() {
-        /* Let Gecko know if the screensize has changed */
-        sendResizeEventIfNecessary();
-        render();
-    }
-
-    private void sendResizeEventIfNecessary() {
-        sendResizeEventIfNecessary(false);
-    }
-
-    /* Informs Gecko that the screen size has changed. */
-    private void sendResizeEventIfNecessary(boolean force) {
-        DisplayMetrics metrics = new DisplayMetrics();
-        //GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
-        LibreOfficeMainActivity.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
-
-        // Return immediately if the screen size hasn't changed or the viewport
-        // size is zero (which indicates that the rendering surface hasn't been
-        // allocated yet).
-        boolean screenSizeChanged = (metrics.widthPixels != mScreenSize.width ||
-                metrics.heightPixels != mScreenSize.height);
-        boolean viewportSizeValid = (getLayerController() != null &&
-                getLayerController().getViewportSize().isPositive());
-        if (!(force || (screenSizeChanged && viewportSizeValid))) {
-            return;
-        }
-
-        mScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
-        IntSize bufferSize;
-
-        // Round up depending on layer implementation to remove texture wastage
-        if (mTileLayer instanceof MultiTileLayer) {
-            // Round to the next multiple of the tile size, respecting maximum texture size
-            bufferSize = new IntSize(((mScreenSize.width + LayerController.MIN_BUFFER.width - 1) / TILE_SIZE.width + 1) * TILE_SIZE.width,
-                    ((mScreenSize.height + LayerController.MIN_BUFFER.height - 1) / TILE_SIZE.height + 1) * TILE_SIZE.height);
-
-        } else {
-            int maxSize = getLayerController().getView().getMaxTextureSize();
-
-            // XXX Integrate gralloc/tiling work to circumvent this
-            if (mScreenSize.width > maxSize || mScreenSize.height > maxSize)

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list