Bug 695448 - Implement a Java compositor, and use it to scroll. r=?
authorPatrick Walton <pwalton@mozilla.com>
Wed, 09 Nov 2011 17:39:29 -0800
changeset 81790 eaf778e88070a0292cf3c35139686eac4fe307ca
parent 81789 a2f776f67fa1b42875c4f2c0ba773fc8320693d6
child 81791 821f8e1cd0ed8b524a4f39aa80d15358bc83df8f
push idunknown
push userunknown
push dateunknown
bugs695448
milestone10.0a1
Bug 695448 - Implement a Java compositor, and use it to scroll. r=?
embedding/android/GeckoApp.java
embedding/android/GeckoAppShell.java
embedding/android/GeckoEvent.java
embedding/android/GeckoGestureDetector.java
embedding/android/GeckoInputConnection.java
embedding/android/GeckoSurfaceView.java
embedding/android/Makefile.in
embedding/android/gfx/BufferedCairoImage.java
embedding/android/gfx/CairoImage.java
embedding/android/gfx/CairoUtils.java
embedding/android/gfx/FloatPoint.java
embedding/android/gfx/FloatRect.java
embedding/android/gfx/GeckoSoftwareLayerClient.java
embedding/android/gfx/InputConnectionHandler.java
embedding/android/gfx/IntPoint.java
embedding/android/gfx/IntRect.java
embedding/android/gfx/IntSize.java
embedding/android/gfx/Layer.java
embedding/android/gfx/LayerClient.java
embedding/android/gfx/LayerController.java
embedding/android/gfx/LayerRenderer.java
embedding/android/gfx/LayerView.java
embedding/android/gfx/NinePatchTileLayer.java
embedding/android/gfx/PlaceholderLayerClient.java
embedding/android/gfx/SingleTileLayer.java
embedding/android/gfx/TextLayer.java
embedding/android/gfx/TextureReaper.java
embedding/android/gfx/TileLayer.java
embedding/android/resources/drawable/checkerboard.png
embedding/android/resources/drawable/shadow.png
embedding/android/ui/PanZoomController.java
embedding/android/ui/ViewportController.java
gfx/thebes/GLContextProviderEGL.cpp
mobile/chrome/content/browser.js
other-licenses/android/APKOpen.cpp
widget/src/android/AndroidBridge.cpp
widget/src/android/AndroidBridge.h
widget/src/android/AndroidJNI.cpp
widget/src/android/AndroidJavaWrappers.cpp
widget/src/android/AndroidJavaWrappers.h
widget/src/android/nsAppShell.cpp
widget/src/android/nsWindow.cpp
widget/src/android/nsWindow.h
--- a/embedding/android/GeckoApp.java
+++ b/embedding/android/GeckoApp.java
@@ -35,16 +35,24 @@
  * 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;
 
+import org.mozilla.gecko.gfx.GeckoSoftwareLayerClient;
+import org.mozilla.gecko.gfx.IntRect;
+import org.mozilla.gecko.gfx.IntSize;
+import org.mozilla.gecko.gfx.LayerController;
+import org.mozilla.gecko.gfx.LayerView;
+import org.mozilla.gecko.gfx.PlaceholderLayerClient;
+import org.mozilla.gecko.Tab.HistoryEntry;
+
 import java.io.*;
 import java.util.*;
 import java.util.zip.*;
 import java.net.URL;
 import java.nio.*;
 import java.nio.channels.FileChannel;
 import java.util.concurrent.*;
 import java.lang.reflect.*;
@@ -59,55 +67,59 @@ import android.view.inputmethod.*;
 import android.view.ViewGroup.LayoutParams;
 import android.content.*;
 import android.content.res.*;
 import android.graphics.*;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.BitmapDrawable;
 import android.widget.*;
 import android.hardware.*;
+import android.location.*;
 
 import android.util.*;
 import android.net.*;
 import android.database.*;
 import android.database.sqlite.*;
 import android.provider.*;
 import android.content.pm.*;
 import android.content.pm.PackageManager.*;
 import android.content.SharedPreferences.*;
 import dalvik.system.*;
 
 abstract public class GeckoApp
-    extends Activity implements GeckoEventListener 
+    extends Activity implements GeckoEventListener, SensorEventListener, LocationListener
 {
     private static final String LOG_NAME = "GeckoApp";
 
     public static final String ACTION_ALERT_CLICK = "org.mozilla.gecko.ACTION_ALERT_CLICK";
     public static final String ACTION_ALERT_CLEAR = "org.mozilla.gecko.ACTION_ALERT_CLEAR";
     public static final String ACTION_WEBAPP      = "org.mozilla.gecko.WEBAPP";
     public static final String ACTION_DEBUG       = "org.mozilla.gecko.DEBUG";
     public static final String ACTION_BOOKMARK    = "org.mozilla.gecko.BOOKMARK";
 
     private LinearLayout mMainLayout;
     private RelativeLayout mGeckoLayout;
-    public static GeckoSurfaceView surfaceView;
     public static SurfaceView cameraView;
     public static GeckoApp mAppContext;
     public static boolean mFullScreen = false;
     public static File sGREDir = null;
     public static Menu sMenu;
     public Handler mMainHandler;
     private IntentFilter mConnectivityFilter;
     private BroadcastReceiver mConnectivityReceiver;
     public static BrowserToolbar mBrowserToolbar;
     public static DoorHangerPopup mDoorHangerPopup;
     public Favicons mFavicons;
     private static boolean sIsGeckoReady = false;
     private IntentFilter mBatteryFilter;
     private BroadcastReceiver mBatteryReceiver;
+    private Geocoder mGeocoder;
+    private Address  mLastGeoAddress;
+    private static LayerController mLayerController;
+    private static GeckoSoftwareLayerClient mSoftwareLayerClient;
     boolean mUserDefinedProfile = false;
 
     public interface OnTabsChangedListener {
         public void onTabsChanged();
     }
     
     private static ArrayList<OnTabsChangedListener> mTabsChangedListeners;
 
@@ -119,26 +131,26 @@ abstract public class GeckoApp
             Log.i("GeckoJSMenu", "menu item clicked");
             GeckoAppShell.sendEventToGecko(new GeckoEvent("Menu:Clicked", Integer.toString(id)));
             return true;
         }
     }
 
     static Vector<ExtraMenuItem> sExtraMenuItems = new Vector<ExtraMenuItem>();
 
-    enum LaunchState {Launching, WaitButton,
-                      Launched, GeckoRunning, GeckoExiting};
+    public enum LaunchState {Launching, WaitButton,
+                             Launched, GeckoRunning, GeckoExiting};
     private static LaunchState sLaunchState = LaunchState.Launching;
     private static boolean sTryCatchAttached = false;
 
     private static final int FILE_PICKER_REQUEST = 1;
     private static final int AWESOMEBAR_REQUEST = 2;
     private static final int CAMERA_CAPTURE_REQUEST = 3;
 
-    static boolean checkLaunchState(LaunchState checkState) {
+    public static boolean checkLaunchState(LaunchState checkState) {
         synchronized(sLaunchState) {
             return sLaunchState == checkState;
         }
     }
 
     static void setLaunchState(LaunchState setState) {
         synchronized(sLaunchState) {
             sLaunchState = setState;
@@ -534,38 +546,53 @@ abstract public class GeckoApp
            case R.id.addons:
                GeckoAppShell.sendEventToGecko(new GeckoEvent("about:addons"));
                return true;
            default:
                return super.onOptionsItemSelected(item);
         }
     }
 
+    public String getStartupBitmapFilePath() {
+        File file = new File(Environment.getExternalStorageDirectory(),
+                             "lastScreen.png");
+        return file.toString();
+    }
+
     private void rememberLastScreen(boolean sync) {
         if (mUserDefinedProfile)
             return;
 
-        if (surfaceView == null)
-            return;
         Tab tab = Tabs.getInstance().getSelectedTab();
         if (tab == null)
             return;
 
-        Tab.HistoryEntry he = tab.getLastHistoryEntry();
-        if (he != null) {
-            SharedPreferences prefs = getSharedPreferences("GeckoApp", MODE_PRIVATE);
-            Editor editor = prefs.edit();
-            
-            editor.putString("last-uri", he.mUri);
-            editor.putString("last-title", he.mTitle);
+        HistoryEntry lastHistoryEntry = tab.getLastHistoryEntry();
+        if (lastHistoryEntry == null)
+            return;
+
+        SharedPreferences prefs = getSharedPreferences("GeckoApp", 0);
+        Editor editor = prefs.edit();
+
+        String uri = lastHistoryEntry.mUri;
+        String title = lastHistoryEntry.mTitle;
 
-            Log.i(LOG_NAME, "Saving:: " + he.mUri + " " + he.mTitle);
-            editor.commit();
-            surfaceView.saveLast(sync);
-        }
+        editor.putString("last-uri", uri);
+        editor.putString("last-title", title);
+
+        Log.i(LOG_NAME, "Saving:: " + uri + " " + title);
+        editor.commit();
+
+        GeckoEvent event = new GeckoEvent();
+        event.mType = GeckoEvent.SAVE_STATE;
+        event.mCharacters = getStartupBitmapFilePath();
+        if (sync)
+            GeckoAppShell.sendEventToGeckoSync(event);
+        else
+            GeckoAppShell.sendEventToGecko(event);
     }
 
     private void loadFavicon(final Tab tab) {
         mFavicons.loadFavicon(tab.getURL(), tab.getFaviconURL(),
                 new Favicons.OnFaviconLoadedListener() {
 
             public void onFaviconLoaded(String pageUrl, Drawable favicon) {
                 // Leave favicon UI untouched if we failed to load the image
@@ -709,16 +736,17 @@ abstract public class GeckoApp
             } else if (event.equals("Toast:Show")) {
                 final String msg = message.getString("message");
                 final String duration = message.getString("duration");
                 handleShowToast(msg, duration);
             } else if (event.equals("DOMContentLoaded")) {
                 final int tabId = message.getInt("tabID");
                 final String uri = message.getString("uri");
                 final String title = message.getString("title");
+                final JSONObject jsonPageSize = message.getJSONObject("pageSize");
                 final CharSequence titleText = title;
                 handleContentLoaded(tabId, uri, title);
                 Log.i(LOG_NAME, "URI - " + uri + ", title - " + title);
             } else if (event.equals("DOMTitleChanged")) {
                 final int tabId = message.getInt("tabID");
                 final String title = message.getString("title");
                 final CharSequence titleText = title;
                 handleTitleChanged(tabId, title);
@@ -782,16 +810,22 @@ abstract public class GeckoApp
             } else if (event.equals("Gecko:Ready")) {
                 sIsGeckoReady = true;
                 mMainHandler.post(new Runnable() {
                     public void run() {
                         if (sMenu != null)
                             sMenu.findItem(R.id.preferences).setEnabled(true);
                     }
                 });
+            } else if (event.equals("PanZoom:Ack")) {
+                final IntRect rect = new IntRect(message.getJSONObject("rect"));
+                mSoftwareLayerClient.jsPanZoomCompleted(rect);
+            } else if (event.equals("PanZoom:Resize")) {
+                final IntSize size = new IntSize(message.getJSONObject("size"));
+                mSoftwareLayerClient.setPageSize(size);
             } else if (event.equals("ToggleChrome:Hide")) {
                 mMainHandler.post(new Runnable() {
                     public void run() {
                         mBrowserToolbar.setVisibility(View.GONE);
                     }
                 });
             } else if (event.equals("ToggleChrome:Show")) {
                 mMainHandler.post(new Runnable() {
@@ -915,17 +949,16 @@ abstract public class GeckoApp
             return;
 
         tab.setLoading(false);
 
         mMainHandler.post(new Runnable() {
             public void run() {
                 if (Tabs.getInstance().isSelectedTab(tab))
                     mBrowserToolbar.setProgressVisibility(false);
-                surfaceView.hideStartupBitmap();
                 onTabsChanged();
             }
         });
     }
 
     void handleShowToast(final String message, final String duration) {
         mMainHandler.post(new Runnable() {
             public void run() {
@@ -1080,27 +1113,43 @@ abstract public class GeckoApp
 
         tabs.setContentResolver(getContentResolver()); 
 
         if (cameraView == null) {
             cameraView = new SurfaceView(this);
             cameraView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
         }
 
-        if (surfaceView == null) {
-            surfaceView = new GeckoSurfaceView(this);
-            mGeckoLayout.addView(surfaceView);
-        } else if (mGeckoLayout.getChildCount() == 0) {
-           //surfaceView still holds to the old one during rotation. re-add it to new activity
-           ((ViewGroup) surfaceView.getParent()).removeAllViews();
-           mGeckoLayout.addView(surfaceView);
+        if (mLayerController == null) {
+            /*
+             * Create a layer client so that Gecko will have a buffer to draw into, but don't hook
+             * it up to the layer controller yet.
+             */
+            mSoftwareLayerClient = new GeckoSoftwareLayerClient(this);
+
+            /*
+             * Hook a placeholder layer client up to the layer controller so that the user can pan
+             * and zoom a cached screenshot of the previous page. This call will return null if
+             * there is no cached screenshot; in that case, we have no choice but to display a
+             * checkerboard.
+             *
+             * TODO: Fall back to a built-in screenshot of the Fennec Start page for a nice first-
+             * run experience, perhaps?
+             */
+            PlaceholderLayerClient placeholderClient = mUserDefinedProfile ?
+              null : PlaceholderLayerClient.createInstance(this);
+            if (placeholderClient != null) {
+                mLayerController = new LayerController(this, placeholderClient);
+                placeholderClient.init();
+            } else {
+                mLayerController = new LayerController(this, null);
+            }
+
+            mGeckoLayout.addView(mLayerController.getView());
         }
-        
-        if (!mUserDefinedProfile)
-            surfaceView.loadStartupBitmap();
 
         Log.w(LOGTAG, "zerdatime " + new Date().getTime() + " - UI almost up");
 
         if (sGREDir == null)
             sGREDir = new File(this.getApplicationInfo().dataDir);
 
         mMainHandler = new Handler();
 
@@ -1135,16 +1184,18 @@ abstract public class GeckoApp
         GeckoAppShell.registerGeckoEventListener("Tab:Closed", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Tab:Selected", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Doorhanger:Add", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Menu:Add", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Menu:Remove", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Preferences:Data", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Gecko:Ready", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Toast:Show", GeckoApp.mAppContext);
+        GeckoAppShell.registerGeckoEventListener("PanZoom:Ack", GeckoApp.mAppContext);
+        GeckoAppShell.registerGeckoEventListener("PanZoom:Resize", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("ToggleChrome:Hide", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("ToggleChrome:Show", GeckoApp.mAppContext);
 
         mConnectivityFilter = new IntentFilter();
         mConnectivityFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         mConnectivityReceiver = new GeckoConnectivityReceiver();
 
         mBatteryFilter = new IntentFilter();
@@ -1383,17 +1434,17 @@ abstract public class GeckoApp
     }
 
     public void doRestart() {
         try {
             String action = "org.mozilla.gecko.restart";
             Intent intent = new Intent(action);
             intent.setClassName(getPackageName(),
                                 getPackageName() + ".Restarter");
-            addEnvToIntent(intent);
+            /* TODO: addEnvToIntent(intent); */
             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                             Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
             Log.i(LOG_NAME, intent.toString());
             GeckoAppShell.killAnyZombies();
             startActivity(intent);
         } catch (Exception e) {
             Log.i(LOG_NAME, "error doing restart", e);
         }
@@ -1655,9 +1706,94 @@ abstract public class GeckoApp
         mBrowserToolbar.setTitle(url);
         Log.d(LOG_NAME, type.name());
         if (type == AwesomeBar.Type.ADD) {
             GeckoAppShell.sendEventToGecko(new GeckoEvent("Tab:Add", url));
         } else {
             GeckoAppShell.sendEventToGecko(new GeckoEvent("Tab:Load", url));
         }
     }
+
+    public GeckoSoftwareLayerClient getSoftwareLayerClient() { return mSoftwareLayerClient; }
+    public LayerController getLayerController() { return mLayerController; }
+
+    // accelerometer
+    public void onAccuracyChanged(Sensor sensor, int accuracy)
+    {
+    }
+
+    public void onSensorChanged(SensorEvent event)
+    {
+        Log.w(LOGTAG, "onSensorChanged "+event);
+        GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
+    }
+
+    private class GeocoderTask extends AsyncTask<Location, Void, Void> {
+        protected Void doInBackground(Location... location) {
+            try {
+                List<Address> addresses = mGeocoder.getFromLocation(location[0].getLatitude(),
+                                                                    location[0].getLongitude(), 1);
+                // grab the first address.  in the future,
+                // may want to expose multiple, or filter
+                // for best.
+                mLastGeoAddress = addresses.get(0);
+                GeckoAppShell.sendEventToGecko(new GeckoEvent(location[0], mLastGeoAddress));
+            } catch (Exception e) {
+                Log.w(LOGTAG, "GeocoderTask "+e);
+            }
+            return null;
+        }
+    }
+
+    // geolocation
+    public void onLocationChanged(Location location)
+    {
+        Log.w(LOGTAG, "onLocationChanged "+location);
+        if (mGeocoder == null)
+            mGeocoder = new Geocoder(mLayerController.getView().getContext(), Locale.getDefault());
+
+        if (mLastGeoAddress == null) {
+            new GeocoderTask().execute(location);
+        }
+        else {
+            float[] results = new float[1];
+            Location.distanceBetween(location.getLatitude(),
+                                     location.getLongitude(),
+                                     mLastGeoAddress.getLatitude(),
+                                     mLastGeoAddress.getLongitude(),
+                                     results);
+            // pfm value.  don't want to slam the
+            // geocoder with very similar values, so
+            // only call after about 100m
+            if (results[0] > 100)
+                new GeocoderTask().execute(location);
+        }
+
+        GeckoAppShell.sendEventToGecko(new GeckoEvent(location, mLastGeoAddress));
+    }
+
+    public void onProviderDisabled(String provider)
+    {
+    }
+
+    public void onProviderEnabled(String provider)
+    {
+    }
+
+    public void onStatusChanged(String provider, int status, Bundle extras)
+    {
+    }
+
+    public void connectGeckoLayerClient() {
+        new Timer("Gecko Wait").schedule(new TimerTask() {
+            public void run() {
+                GeckoApp.mAppContext.runOnUiThread(new Runnable() {
+                    public void run() {
+                        LayerController layerController = getLayerController();
+                        layerController.setLayerClient(mSoftwareLayerClient);
+                        mSoftwareLayerClient.init();    /* Attaches the new root layer. */
+                        GeckoAppShell.scheduleRedraw();
+                    }
+                });
+            }
+        }, 3000);
+    }
 }
--- a/embedding/android/GeckoAppShell.java
+++ b/embedding/android/GeckoAppShell.java
@@ -33,16 +33,22 @@
  * 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;
 
+import org.mozilla.gecko.gfx.FloatPoint;
+import org.mozilla.gecko.gfx.GeckoSoftwareLayerClient;
+import org.mozilla.gecko.gfx.IntPoint;
+import org.mozilla.gecko.gfx.LayerController;
+import org.mozilla.gecko.gfx.LayerView;
+
 import java.io.*;
 import java.lang.reflect.*;
 import java.nio.*;
 import java.nio.channels.*;
 import java.text.*;
 import java.util.*;
 import java.util.zip.*;
 import java.util.concurrent.*;
@@ -85,25 +91,21 @@ public class GeckoAppShell
     private GeckoAppShell() { }
 
     static private LinkedList<GeckoEvent> gPendingEvents =
         new LinkedList<GeckoEvent>();
 
     static private boolean gRestartScheduled = false;
     static private PromptService gPromptService = null;
 
-    static private final Timer mIMETimer = new Timer();
+    static private GeckoInputConnection mInputConnection = null;
+
     static private final HashMap<Integer, AlertNotification>
         mAlertNotifications = new HashMap<Integer, AlertNotification>();
 
-    static private final int NOTIFY_IME_RESETINPUTSTATE = 0;
-    static private final int NOTIFY_IME_SETOPENSTATE = 1;
-    static private final int NOTIFY_IME_CANCELCOMPOSITION = 2;
-    static private final int NOTIFY_IME_FOCUSCHANGE = 3;
-
     /* Keep in sync with constants found here:
       http://mxr.mozilla.org/mozilla-central/source/uriloader/base/nsIWebProgressListener.idl
     */
     static public final int WPL_STATE_START = 0x00000001;
     static public final int WPL_STATE_STOP = 0x00000010;
     static public final int WPL_STATE_IS_DOCUMENT = 0x00020000;
 
     static private File sCacheFile = null;
@@ -113,17 +115,18 @@ public class GeckoAppShell
 
     /* The Android-side API: API methods that Android calls */
 
     // Initialization methods
     public static native void nativeInit();
     public static native void nativeRun(String args);
 
     // helper methods
-    public static native void setSurfaceView(GeckoSurfaceView sv);
+    //    public static native void setSurfaceView(GeckoSurfaceView sv);
+    public static native void setSoftwareLayerClient(GeckoSoftwareLayerClient client);
     public static native void putenv(String map);
     public static native void onResume();
     public static native void onLowMemory();
     public static native void callObserver(String observerKey, String topic, String data);
     public static native void removeObserver(String observerKey);
     public static native void loadLibs(String apkName, boolean shouldExtract);
     public static native void onChangeNetworkLinkStatus(String status);
     public static native void reportJavaCrash(String stack);
@@ -409,43 +412,86 @@ public class GeckoAppShell
             GeckoAppShell.putenv("LOCALE_GROUPING=" + (char)df.getGroupingSize());
         }
     }
 
     public static void runGecko(String apkPath, String args, String url) {
         // run gecko -- it will spawn its own thread
         GeckoAppShell.nativeInit();
 
-        // Tell Gecko where the target surface view is for rendering
-        GeckoAppShell.setSurfaceView(GeckoApp.surfaceView);
+        // Tell Gecko where the target byte buffer is for rendering
+        GeckoAppShell.setSoftwareLayerClient(GeckoApp.mAppContext.getSoftwareLayerClient());
 
         // First argument is the .apk path
         String combinedArgs = apkPath + " -greomni " + apkPath;
         if (args != null)
             combinedArgs += " " + args;
         if (url != null)
             combinedArgs += " -remote " + url;
 
+        /* TODO: Is this complexity necessary? */
+        new Timer("Gecko Setup").schedule(new TimerTask() {
+            public void run() {
+                GeckoApp.mAppContext.runOnUiThread(new Runnable() {
+                    public void run() {
+                        geckoLoaded();
+                    }
+                });
+            }
+        }, 0);
+
         // and go
         GeckoAppShell.nativeRun(combinedArgs);
     }
 
+    // Called on the UI thread after Gecko loads.
+    private static void geckoLoaded() {
+        GeckoApp.mAppContext.connectGeckoLayerClient();
+
+        final LayerController layerController = GeckoApp.mAppContext.getLayerController();
+        LayerView v = layerController.getView();
+        mInputConnection = new GeckoInputConnection(v);
+        v.setInputConnectionHandler(mInputConnection);
+
+        layerController.setOnTouchListener(new View.OnTouchListener() {
+            public boolean onTouch(View view, MotionEvent event) {
+                float origX = event.getX();
+                float origY = event.getY();
+                /* Transform the point to the layer offset. */
+                FloatPoint eventPoint = new FloatPoint(origX, origY);
+                FloatPoint geckoPoint = layerController.convertViewPointToLayerPoint(eventPoint);
+                event.setLocation((int)Math.round(geckoPoint.x), (int)Math.round(geckoPoint.y));
+
+                GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
+
+                /* Restore the view coordinates in case the caller further processes this event */
+                event.setLocation(origX, origY);
+                return true;
+            }
+        });
+
+        GeckoEvent event = new GeckoEvent(GeckoEvent.SIZE_CHANGED,
+                                          LayerController.TILE_WIDTH, LayerController.TILE_HEIGHT,
+                                          LayerController.TILE_WIDTH, LayerController.TILE_HEIGHT);
+        GeckoAppShell.sendEventToGecko(event);
+    }
+
     private static GeckoEvent mLastDrawEvent;
 
     private static void sendPendingEventsToGecko() {
         try {
             while (!gPendingEvents.isEmpty()) {
                 GeckoEvent e = gPendingEvents.removeFirst();
                 notifyGeckoOfEvent(e);
             }
         } catch (NoSuchElementException e) {}
     }
 
     public static void sendEventToGecko(GeckoEvent e) {
-        if (GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning)) {
+        if (GeckoApp.mAppContext.checkLaunchState(GeckoApp.LaunchState.GeckoRunning)) {
             notifyGeckoOfEvent(e);
         } else {
             gPendingEvents.addLast(e);
         }
     }
 
     public static void sendEventToGeckoSync(GeckoEvent e) {
         sendEventToGecko(e);
@@ -455,155 +501,34 @@ public class GeckoAppShell
     // Tell the Gecko event loop that an event is available.
     public static native void notifyGeckoOfEvent(GeckoEvent event);
 
     /*
      *  The Gecko-side API: API methods that Gecko calls
      */
     public static void scheduleRedraw() {
         // Redraw everything
-        scheduleRedraw(0, -1, -1, -1, -1);
-    }
-
-    public static void scheduleRedraw(int nativeWindow, int x, int y, int w, int h) {
-        GeckoEvent e;
-
-        if (x == -1) {
-            e = new GeckoEvent(GeckoEvent.DRAW, null);
-        } else {
-            e = new GeckoEvent(GeckoEvent.DRAW, new Rect(x, y, w, h));
-        }
-
-        e.mNativeWindow = nativeWindow;
-
-        sendEventToGecko(e);
+        Rect rect = new Rect(0, 0, LayerController.TILE_WIDTH, LayerController.TILE_HEIGHT);
+        GeckoEvent event = new GeckoEvent(GeckoEvent.DRAW, rect);
+        event.mNativeWindow = 0;
+        sendEventToGecko(event);
     }
 
-    /* Delay updating IME states (see bug 573800) */
-    private static final class IMEStateUpdater extends TimerTask
-    {
-        static private IMEStateUpdater instance;
-        private boolean mEnable, mReset;
-
-        static private IMEStateUpdater getInstance() {
-            if (instance == null) {
-                instance = new IMEStateUpdater();
-                mIMETimer.schedule(instance, 200);
-            }
-            return instance;
-        }
-
-        static public synchronized void enableIME() {
-            getInstance().mEnable = true;
-        }
-
-        static public synchronized void resetIME() {
-            getInstance().mReset = true;
-        }
-
-        public void run() {
-            synchronized(IMEStateUpdater.class) {
-                instance = null;
-            }
-
-            InputMethodManager imm = (InputMethodManager)
-                GeckoApp.surfaceView.getContext().getSystemService(
-                    Context.INPUT_METHOD_SERVICE);
-            if (imm == null)
-                return;
-
-            if (mReset)
-                imm.restartInput(GeckoApp.surfaceView);
-
-            if (!mEnable)
-                return;
-
-            int state = GeckoApp.surfaceView.mIMEState;
-            if (state != GeckoSurfaceView.IME_STATE_DISABLED &&
-                state != GeckoSurfaceView.IME_STATE_PLUGIN)
-                imm.showSoftInput(GeckoApp.surfaceView, 0);
-            else
-                imm.hideSoftInputFromWindow(
-                    GeckoApp.surfaceView.getWindowToken(), 0);
-        }
-    }
 
     public static void notifyIME(int type, int state) {
-        if (GeckoApp.surfaceView == null)
-            return;
-
-        switch (type) {
-        case NOTIFY_IME_RESETINPUTSTATE:
-            // Composition event is already fired from widget.
-            // So reset IME flags.
-            GeckoApp.surfaceView.inputConnection.reset();
-            
-            // Don't use IMEStateUpdater for reset.
-            // Because IME may not work showSoftInput()
-            // after calling restartInput() immediately.
-            // So we have to call showSoftInput() delay.
-            InputMethodManager imm = (InputMethodManager) 
-                GeckoApp.surfaceView.getContext().getSystemService(
-                    Context.INPUT_METHOD_SERVICE);
-            if (imm == null) {
-                // no way to reset IME status directly
-                IMEStateUpdater.resetIME();
-            } else {
-                imm.restartInput(GeckoApp.surfaceView);
-            }
-
-            // keep current enabled state
-            IMEStateUpdater.enableIME();
-            break;
-
-        case NOTIFY_IME_CANCELCOMPOSITION:
-            IMEStateUpdater.resetIME();
-            break;
-
-        case NOTIFY_IME_FOCUSCHANGE:
-            IMEStateUpdater.resetIME();
-            break;
-        }
+        mInputConnection.notifyIME(type, state);
     }
 
     public static void notifyIMEEnabled(int state, String typeHint,
-                                        String actionHint, boolean landscapeFS)
-    {
-        if (GeckoApp.surfaceView == null)
-            return;
-
-        /* When IME is 'disabled', IME processing is disabled.
-           In addition, the IME UI is hidden */
-        GeckoApp.surfaceView.mIMEState = state;
-        GeckoApp.surfaceView.mIMETypeHint = typeHint;
-        GeckoApp.surfaceView.mIMEActionHint = actionHint;
-        GeckoApp.surfaceView.mIMELandscapeFS = landscapeFS;
-        IMEStateUpdater.enableIME();
+                                        String actionHint, boolean landscapeFS) {
+        mInputConnection.notifyIMEEnabled(state, typeHint, actionHint, landscapeFS);
     }
 
     public static void notifyIMEChange(String text, int start, int end, int newEnd) {
-        if (GeckoApp.surfaceView == null ||
-            GeckoApp.surfaceView.inputConnection == null)
-            return;
-
-        InputMethodManager imm = (InputMethodManager)
-            GeckoApp.surfaceView.getContext().getSystemService(
-                Context.INPUT_METHOD_SERVICE);
-        if (imm == null)
-            return;
-
-        // Log.d("GeckoAppJava", String.format("IME: notifyIMEChange: t=%s s=%d ne=%d oe=%d",
-        //                                      text, start, newEnd, end));
-
-        if (newEnd < 0)
-            GeckoApp.surfaceView.inputConnection.notifySelectionChange(
-                imm, start, end);
-        else
-            GeckoApp.surfaceView.inputConnection.notifyTextChange(
-                imm, text, start, end, newEnd);
+        mInputConnection.notifyIMEChange(text, start, end, newEnd);
     }
 
     private static CountDownLatch sGeckoPendingAcks = null;
 
     // Block the current thread until the Gecko event loop is caught up
     synchronized public static void geckoEventSync() {
         sGeckoPendingAcks = new CountDownLatch(1);
         GeckoAppShell.sendEventToGecko(
@@ -622,87 +547,83 @@ public class GeckoAppShell
         if (tmp != null)
             tmp.countDown();
     }
 
     static Sensor gAccelerometerSensor = null;
     static Sensor gOrientationSensor = null;
 
     public static void enableDeviceMotion(boolean enable) {
-        SensorManager sm = (SensorManager)
-            GeckoApp.surfaceView.getContext().getSystemService(Context.SENSOR_SERVICE);
+        LayerView v = GeckoApp.mAppContext.getLayerController().getView();
+        SensorManager sm = (SensorManager) v.getContext().getSystemService(Context.SENSOR_SERVICE);
 
         if (gAccelerometerSensor == null || gOrientationSensor == null) {
             gAccelerometerSensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
             gOrientationSensor   = sm.getDefaultSensor(Sensor.TYPE_ORIENTATION);
         }
 
         if (enable) {
             if (gAccelerometerSensor != null)
-                sm.registerListener(GeckoApp.surfaceView, gAccelerometerSensor, SensorManager.SENSOR_DELAY_GAME);
+                sm.registerListener(GeckoApp.mAppContext, gAccelerometerSensor, SensorManager.SENSOR_DELAY_GAME);
             if (gOrientationSensor != null)
-                sm.registerListener(GeckoApp.surfaceView, gOrientationSensor,   SensorManager.SENSOR_DELAY_GAME);
+                sm.registerListener(GeckoApp.mAppContext, gOrientationSensor,   SensorManager.SENSOR_DELAY_GAME);
         } else {
             if (gAccelerometerSensor != null)
-                sm.unregisterListener(GeckoApp.surfaceView, gAccelerometerSensor);
+                sm.unregisterListener(GeckoApp.mAppContext, gAccelerometerSensor);
             if (gOrientationSensor != null)
-                sm.unregisterListener(GeckoApp.surfaceView, gOrientationSensor);
+                sm.unregisterListener(GeckoApp.mAppContext, gOrientationSensor);
         }
     }
 
     public static void enableLocation(final boolean enable) {
         getMainHandler().post(new Runnable() { 
                 public void run() {
-                    GeckoSurfaceView view = GeckoApp.surfaceView;
+                    LayerView v = GeckoApp.mAppContext.getLayerController().getView();
+
                     LocationManager lm = (LocationManager)
-                        view.getContext().getSystemService(Context.LOCATION_SERVICE);
+                        GeckoApp.mAppContext.getSystemService(Context.LOCATION_SERVICE);
 
                     if (enable) {
                         Criteria crit = new Criteria();
                         crit.setAccuracy(Criteria.ACCURACY_FINE);
                         String provider = lm.getBestProvider(crit, true);
                         if (provider == null)
                             return;
 
                         Looper l = Looper.getMainLooper();
                         Location loc = lm.getLastKnownLocation(provider);
                         if (loc != null) {
-                            view.onLocationChanged(loc);
+                            GeckoApp.mAppContext.onLocationChanged(loc);
                         }
-                        lm.requestLocationUpdates(provider, 100, (float).5, view, l);
+                        lm.requestLocationUpdates(provider, 100, (float).5, GeckoApp.mAppContext, l);
                     } else {
-                        lm.removeUpdates(view);
+                        lm.removeUpdates(GeckoApp.mAppContext);
                     }
                 }
             });
     }
 
     public static void moveTaskToBack() {
         GeckoApp.mAppContext.moveTaskToBack(true);
     }
 
     public static void returnIMEQueryResult(String result, int selectionStart, int selectionLength) {
-        GeckoApp.surfaceView.inputConnection.mSelectionStart = selectionStart;
-        GeckoApp.surfaceView.inputConnection.mSelectionLength = selectionLength;
-        try {
-            GeckoApp.surfaceView.inputConnection.mQueryResult.put(result);
-        } catch (InterruptedException e) {
-        }
+        mInputConnection.returnIMEQueryResult(result, selectionStart, selectionLength);
     }
 
     static void onAppShellReady()
     {
         // mLaunchState can only be Launched at this point
-        GeckoApp.setLaunchState(GeckoApp.LaunchState.GeckoRunning);
+        GeckoApp.mAppContext.setLaunchState(GeckoApp.LaunchState.GeckoRunning);
         sendPendingEventsToGecko();
     }
 
     static void onXreExit() {
         // mLaunchState can only be Launched or GeckoRunning at this point
-        GeckoApp.setLaunchState(GeckoApp.LaunchState.GeckoExiting);
+        GeckoApp.mAppContext.setLaunchState(GeckoApp.LaunchState.GeckoExiting);
         Log.i("GeckoAppJava", "XRE exited");
         if (gRestartScheduled) {
             GeckoApp.mAppContext.doRestart();
         } else {
             Log.i("GeckoAppJava", "we're done, good bye");
             GeckoApp.mAppContext.finish();
         }
         getHandler().postDelayed(new Runnable() {
@@ -756,18 +677,17 @@ public class GeckoAppShell
         // aURL may contain the whole URL or just the protocol
         Uri uri = aURL.indexOf(':') >= 0 ? Uri.parse(aURL) : new Uri.Builder().scheme(aURL).build();
         Intent intent = getIntentForActionString(aAction);
         intent.setData(uri);
         return getHandlersForIntent(intent);
     }
 
     static String[] getHandlersForIntent(Intent intent) {
-        PackageManager pm =
-            GeckoApp.surfaceView.getContext().getPackageManager();
+        PackageManager pm = GeckoApp.mAppContext.getPackageManager();
         List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
         int numAttr = 4;
         String[] ret = new String[list.size() * numAttr];
         for (int i = 0; i < list.size(); i++) {
             ResolveInfo resolveInfo = list.get(i);
             ret[i * numAttr] = resolveInfo.loadLabel(pm).toString();
             if (resolveInfo.isDefault)
                 ret[i * numAttr + 1] = "default";
@@ -856,17 +776,17 @@ public class GeckoAppShell
             }
             intent.setData(uri);
         }
         if (aPackageName.length() > 0 && aClassName.length() > 0)
             intent.setClassName(aPackageName, aClassName);
 
         intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
         try {
-            GeckoApp.surfaceView.getContext().startActivity(intent);
+            GeckoApp.mAppContext.startActivity(intent);
             return true;
         } catch(ActivityNotFoundException e) {
             return false;
         }
     }
 
     static SynchronousQueue<String> sClipboardQueue =
         new SynchronousQueue<String>();
@@ -875,17 +795,17 @@ public class GeckoAppShell
     // On some devices, access to the clipboard service needs to happen
     // on a thread with a looper, so dispatch this to our looper thread
     // Note: the main looper won't work because it may be blocked on the
     // gecko thread, which is most likely this thread
     static String getClipboardText() {
         getHandler().post(new Runnable() { 
             @SuppressWarnings("deprecation")
             public void run() {
-                Context context = GeckoApp.surfaceView.getContext();
+                Context context = GeckoApp.mAppContext;
                 String text = null;
                 if (android.os.Build.VERSION.SDK_INT >= 11) {
                     android.content.ClipboardManager cm = (android.content.ClipboardManager)
                         context.getSystemService(Context.CLIPBOARD_SERVICE);
                     if (cm.hasPrimaryClip()) {
                         ClipData clip = cm.getPrimaryClip();
                         if (clip != null) {
                             ClipData.Item item = clip.getItemAt(0);
@@ -908,17 +828,17 @@ public class GeckoAppShell
         } catch (InterruptedException ie) {}
         return null;
     }
 
     static void setClipboardText(final String text) {
         getHandler().post(new Runnable() { 
             @SuppressWarnings("deprecation")
             public void run() {
-                Context context = GeckoApp.surfaceView.getContext();
+                Context context = GeckoApp.mAppContext;
                 if (android.os.Build.VERSION.SDK_INT >= 11) {
                     android.content.ClipboardManager cm = (android.content.ClipboardManager)
                         context.getSystemService(Context.CLIPBOARD_SERVICE);
                     cm.setPrimaryClip(ClipData.newPlainText("Text", text));
                 } else {
                     android.text.ClipboardManager cm = (android.text.ClipboardManager)
                         context.getSystemService(Context.CLIPBOARD_SERVICE);
                     cm.setText(text);
@@ -1052,31 +972,29 @@ public class GeckoAppShell
     }
 
     public static String showFilePicker(String aFilters) {
         return GeckoApp.mAppContext.
             showFilePicker(getMimeTypeFromExtensions(aFilters));
     }
 
     public static void performHapticFeedback(boolean aIsLongPress) {
-        GeckoApp.surfaceView.
-            performHapticFeedback(aIsLongPress ?
-                                  HapticFeedbackConstants.LONG_PRESS :
-                                  HapticFeedbackConstants.VIRTUAL_KEY);
+        // TODO
     }
 
     public static void showInputMethodPicker() {
-        InputMethodManager imm = (InputMethodManager) GeckoApp.surfaceView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+        InputMethodManager imm = (InputMethodManager)
+            GeckoApp.mAppContext.getSystemService(Context.INPUT_METHOD_SERVICE);
         imm.showInputMethodPicker();
     }
 
     public static void setKeepScreenOn(final boolean on) {
         GeckoApp.mAppContext.runOnUiThread(new Runnable() {
             public void run() {
-                GeckoApp.surfaceView.setKeepScreenOn(on);
+                // TODO
             }
         });
     }
 
     public static boolean isNetworkLinkUp() {
         ConnectivityManager cm = (ConnectivityManager)
             GeckoApp.mAppContext.getSystemService(Context.CONNECTIVITY_SERVICE);
         NetworkInfo info = cm.getActiveNetworkInfo();
@@ -1240,29 +1158,29 @@ public class GeckoAppShell
         while (!checkForGeckoProcs() &&  --countdown > 0) {
             try {
                 Thread.currentThread().sleep(100);
             } catch (InterruptedException ie) {}
         }
     }
 
     public static void scanMedia(String aFile, String aMimeType) {
-        Context context = GeckoApp.surfaceView.getContext();
+        Context context = GeckoApp.mAppContext;
         GeckoMediaScannerClient client = new GeckoMediaScannerClient(context, aFile, aMimeType);
     }
 
     public static byte[] getIconForExtension(String aExt, int iconSize) {
         try {
             if (iconSize <= 0)
                 iconSize = 16;
 
             if (aExt != null && aExt.length() > 1 && aExt.charAt(0) == '.')
                 aExt = aExt.substring(1);
 
-            PackageManager pm = GeckoApp.surfaceView.getContext().getPackageManager();
+            PackageManager pm = GeckoApp.mAppContext.getPackageManager();
             Drawable icon = getDrawableForExtension(pm, aExt);
             if (icon == null) {
                 // Use a generic icon
                 icon = pm.getDefaultActivityIcon();
             }
 
             Bitmap bitmap = ((BitmapDrawable)icon).getBitmap();
             if (bitmap.getWidth() != iconSize || bitmap.getHeight() != iconSize)
@@ -1657,18 +1575,21 @@ public class GeckoAppShell
 
     public static void emitGeckoAccessibilityEvent (int eventType, String role, String text, String description, boolean enabled, boolean checked, boolean password) {
         AccessibilityManager accessibilityManager =
             (AccessibilityManager) GeckoApp.mAppContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
 
         if (!accessibilityManager.isEnabled())
             return;
 
+        LayerController layerController = GeckoApp.mAppContext.getLayerController();
+        LayerView layerView = layerController.getView();
+
         AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
-        event.setClassName(GeckoApp.surfaceView.getClass().getName() + "$" + role);
+        event.setClassName(layerView.getClass().getName() + "$" + role);
         event.setPackageName(GeckoApp.mAppContext.getPackageName());
         event.setEnabled(enabled);
         event.setChecked(checked);
         event.setPassword(password);
         event.setContentDescription(description);
         event.getText().add(text);
 
         accessibilityManager.sendAccessibilityEvent(event);
--- a/embedding/android/GeckoEvent.java
+++ b/embedding/android/GeckoEvent.java
@@ -198,24 +198,24 @@ public class GeckoEvent {
 
     public GeckoEvent(int offset, int count,
                       int rangeType, int rangeStyles,
                       int rangeForeColor, int rangeBackColor) {
         InitIMERange(IME_ADD_RANGE, offset, count, rangeType, rangeStyles,
                      rangeForeColor, rangeBackColor);
     }
 
-    public GeckoEvent(int etype, Rect dirty) {
+    public GeckoEvent(int etype, Rect rect) {
         if (etype != DRAW) {
             mType = INVALID;
             return;
         }
 
         mType = etype;
-        mRect = dirty;
+        mRect = rect;
     }
 
     public GeckoEvent(int etype, int w, int h, int screenw, int screenh) {
         if (etype != SIZE_CHANGED) {
             mType = INVALID;
             return;
         }
 
deleted file mode 100644
--- a/embedding/android/GeckoGestureDetector.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Android code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2011
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Wes Johnston <wjohnston@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;
-
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-import android.content.Context;
-import android.view.View;
-import org.json.JSONArray;
-import org.json.JSONObject;
-import android.util.Log;
-
-class GeckoGestureDetector implements GestureDetector.OnGestureListener {
-    private GestureDetector mDetector;
-    private static final String LOG_FILE_NAME = "GeckoGestureDetector";
-    public GeckoGestureDetector(Context aContext) {
-        mDetector = new GestureDetector(aContext, this);
-    }
-
-    public boolean onTouchEvent(MotionEvent event) {
-        return mDetector.onTouchEvent(event);
-    }
-
-    @Override
-    public boolean onDown(MotionEvent e) {
-    	return true;
-    }
-
-    @Override
-    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-    	return true;
-    }
-
-    @Override
-    public void onLongPress(MotionEvent motionEvent) {
-        JSONObject ret = new JSONObject();
-        try {
-            ret.put("x", motionEvent.getX());
-            ret.put("y", motionEvent.getY());
-        } catch(Exception ex) {
-            Log.w(LOG_FILE_NAME, "Error building return: " + ex);
-        }
-
-        GeckoEvent e = new GeckoEvent("Gesture:LongPress", ret.toString());
-        GeckoAppShell.sendEventToGecko(e);
-    }
-
-    @Override
-    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
-    	return true;
-    }
-
-    @Override
-    public void onShowPress(MotionEvent e) {
-    }
-    
-    @Override
-    public boolean onSingleTapUp(MotionEvent e) {
-    	return true;
-    }
-}
--- a/embedding/android/GeckoInputConnection.java
+++ b/embedding/android/GeckoInputConnection.java
@@ -37,30 +37,38 @@
 
 package org.mozilla.gecko;
 
 import java.io.*;
 import java.util.*;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.*;
 
+import org.mozilla.gecko.gfx.InputConnectionHandler;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
 import android.os.*;
 import android.app.*;
 import android.text.*;
 import android.text.style.*;
 import android.view.*;
 import android.view.inputmethod.*;
 import android.content.*;
 import android.R;
 
+import android.text.method.TextKeyListener;
+import android.text.method.KeyListener;
+
 import android.util.*;
 
+
 public class GeckoInputConnection
     extends BaseInputConnection
-    implements TextWatcher
+    implements TextWatcher, InputConnectionHandler
 {
     private class ChangeNotification {
         public String mText;
         public int mStart;
         public int mEnd;
         public int mNewEnd;
 
         ChangeNotification(String text, int start, int oldEnd, int newEnd) {
@@ -76,45 +84,51 @@ public class GeckoInputConnection
             mEnd = end;
             mNewEnd = 0;
         }
     }
 
     public GeckoInputConnection (View targetView) {
         super(targetView, true);
         mQueryResult = new SynchronousQueue<String>();
+
+        mEditableFactory = Editable.Factory.getInstance();
+        initEditable("");
+        mIMEState = IME_STATE_DISABLED;
+        mIMETypeHint = "";
+        mIMEActionHint = "";
     }
 
     @Override
     public boolean beginBatchEdit() {
-        //Log.d("GeckoAppJava", "IME: beginBatchEdit");
+        Log.d("GeckoAppJava", "IME: beginBatchEdit");
         mBatchMode = true;
         return true;
     }
 
     @Override
     public boolean commitCompletion(CompletionInfo text) {
-        //Log.d("GeckoAppJava", "IME: commitCompletion");
+        Log.d("GeckoAppJava", "IME: commitCompletion");
 
         return commitText(text.getText(), 1);
     }
 
     @Override
     public boolean commitText(CharSequence text, int newCursorPosition) {
-        //Log.d("GeckoAppJava", "IME: commitText");
+        Log.d("GeckoAppJava", "IME: commitText");
 
         setComposingText(text, newCursorPosition);
         finishComposingText();
 
         return true;
     }
 
     @Override
     public boolean deleteSurroundingText(int leftLength, int rightLength) {
-        //Log.d("GeckoAppJava", "IME: deleteSurroundingText");
+        Log.d("GeckoAppJava", "IME: deleteSurroundingText");
         if (leftLength == 0 && rightLength == 0)
             return true;
 
         /* deleteSurroundingText is supposed to ignore the composing text,
             so we cancel any pending composition, delete the text, and then
             restart the composition */
 
         if (mComposing) {
@@ -163,39 +177,39 @@ public class GeckoInputConnection
         // They will be re-enabled on the next setComposingText
         disableChangeNotifications();
 
         return true;
     }
 
     @Override
     public boolean endBatchEdit() {
-        //Log.d("GeckoAppJava", "IME: endBatchEdit");
+        Log.d("GeckoAppJava", "IME: endBatchEdit");
 
         mBatchMode = false;
 
         if (!mBatchChanges.isEmpty()) {
             InputMethodManager imm = (InputMethodManager)
-                GeckoApp.surfaceView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+                GeckoApp.mAppContext.getSystemService(Context.INPUT_METHOD_SERVICE);
             if (imm != null) {
                 for (ChangeNotification n : mBatchChanges) {
                     if (n.mText != null)
                         notifyTextChange(imm, n.mText, n.mStart, n.mEnd, n.mNewEnd);
                     else
                         notifySelectionChange(imm, n.mStart, n.mEnd);
                 }
             }
             mBatchChanges.clear();
         }
         return true;
     }
 
     @Override
     public boolean finishComposingText() {
-        //Log.d("GeckoAppJava", "IME: finishComposingText");
+        Log.d("GeckoAppJava", "IME: finishComposingText");
 
         if (mComposing) {
             // Set style to none
             GeckoAppShell.sendEventToGecko(
                 new GeckoEvent(0, mComposingText.length(),
                                GeckoEvent.IME_RANGE_RAWINPUT, 0, 0, 0,
                                mComposingText));
             GeckoAppShell.sendEventToGecko(
@@ -210,32 +224,32 @@ public class GeckoInputConnection
                                    mCompositionStart + mCompositionSelStart, 0));
             }
         }
         return true;
     }
 
     @Override
     public int getCursorCapsMode(int reqModes) {
-        //Log.d("GeckoAppJava", "IME: getCursorCapsMode");
+        Log.d("GeckoAppJava", "IME: getCursorCapsMode");
 
         return 0;
     }
 
     @Override
     public Editable getEditable() {
         Log.w("GeckoAppJava", "IME: getEditable called from " +
             Thread.currentThread().getStackTrace()[0].toString());
 
         return null;
     }
 
     @Override
     public boolean performContextMenuAction(int id) {
-        //Log.d("GeckoAppJava", "IME: performContextMenuAction");
+        Log.d("GeckoAppJava", "IME: performContextMenuAction");
 
         // First we need to ask Gecko to tell us the full contents of the
         // text field we're about to operate on.
         String text;
         GeckoAppShell.sendEventToGecko(
             new GeckoEvent(GeckoEvent.IME_GET_TEXT, 0, Integer.MAX_VALUE));
         try {
             text = mQueryResult.take();
@@ -285,17 +299,17 @@ public class GeckoInputConnection
         if (req == null)
             return null;
 
         // Bail out here if gecko isn't running, otherwise we deadlock
         // below when waiting for the reply to IME_GET_SELECTION.
         if (!GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning))
             return null;
 
-        //Log.d("GeckoAppJava", "IME: getExtractedText");
+        Log.d("GeckoAppJava", "IME: getExtractedText");
 
         ExtractedText extract = new ExtractedText();
         extract.flags = 0;
         extract.partialStartOffset = -1;
         extract.partialEndOffset = -1;
 
         GeckoAppShell.sendEventToGecko(
             new GeckoEvent(GeckoEvent.IME_GET_SELECTION, 0, 0));
@@ -337,17 +351,17 @@ public class GeckoInputConnection
         } catch (InterruptedException e) {
             Log.e("GeckoAppJava", "IME: getExtractedText interrupted", e);
             return null;
         }
     }
 
     @Override
     public CharSequence getTextAfterCursor(int length, int flags) {
-        //Log.d("GeckoAppJava", "IME: getTextAfterCursor");
+        Log.d("GeckoAppJava", "IME: getTextAfterCursor");
 
         GeckoAppShell.sendEventToGecko(
             new GeckoEvent(GeckoEvent.IME_GET_SELECTION, 0, 0));
         try {
             mQueryResult.take();
         } catch (InterruptedException e) {
             Log.e("GeckoAppJava", "IME: getTextBefore/AfterCursor interrupted", e);
             return null;
@@ -374,24 +388,24 @@ public class GeckoInputConnection
         } catch (InterruptedException e) {
             Log.e("GeckoAppJava", "IME: getTextBefore/AfterCursor: Interrupted!", e);
             return null;
         }
     }
 
     @Override
     public CharSequence getTextBeforeCursor(int length, int flags) {
-        //Log.d("GeckoAppJava", "IME: getTextBeforeCursor");
+        Log.d("GeckoAppJava", "IME: getTextBeforeCursor");
 
         return getTextAfterCursor(-length, flags);
     }
 
     @Override
     public boolean setComposingText(CharSequence text, int newCursorPosition) {
-        //Log.d("GeckoAppJava", "IME: setComposingText");
+        Log.d("GeckoAppJava", "IME: setComposingText");
 
         enableChangeNotifications();
 
         // Set new composing text
         mComposingText = text != null ? text.toString() : "";
 
         if (!mComposing) {
             // Get current selection
@@ -515,17 +529,17 @@ public class GeckoInputConnection
                            GeckoEvent.IME_RANGE_CARETPOSITION, 0, 0, 0,
                            mComposingText));
 
         return true;
     }
 
     @Override
     public boolean setComposingRegion(int start, int end) {
-        //Log.d("GeckoAppJava", "IME: setComposingRegion(start=" + start + ", end=" + end + ")");
+        Log.d("GeckoAppJava", "IME: setComposingRegion(start=" + start + ", end=" + end + ")");
         if (start < 0 || end < start)
             return true;
 
         CharSequence text = null;
         if (start == mCompositionStart && end - start == mComposingText.length()) {
             // Use mComposingText to avoid extra call to Gecko
             text = mComposingText;
         }
@@ -549,17 +563,17 @@ public class GeckoInputConnection
         // Call setComposingText with the same text to start composition and let Gecko know about new composing region
         setComposingText(text, 1);
 
         return true;
     }
 
     @Override
     public boolean setSelection(int start, int end) {
-        //Log.d("GeckoAppJava", "IME: setSelection");
+        Log.d("GeckoAppJava", "IME: setSelection");
 
         if (mComposing) {
             /* Translate to fake selection positions */
             start -= mCompositionStart;
             end -= mCompositionStart;
 
             if (start < 0)
                 start = 0;
@@ -597,32 +611,34 @@ public class GeckoInputConnection
         }
 
         commitText(null, 1);
         return true;
     }
 
     public void notifyTextChange(InputMethodManager imm, String text,
                                  int start, int oldEnd, int newEnd) {
-        // Log.d("GeckoAppShell", String.format("IME: notifyTextChange: text=%s s=%d ne=%d oe=%d",
-        //                                      text, start, newEnd, oldEnd));
+        Log.d("GeckoAppShell", String.format("IME: notifyTextChange: text=%s s=%d ne=%d oe=%d",
+                                              text, start, newEnd, oldEnd));
         if (!mChangeNotificationsEnabled)
             return;
 
         if (mBatchMode) {
             mBatchChanges.add(new ChangeNotification(text, start, oldEnd, newEnd));
             return;
         }
 
         mNumPendingChanges = Math.max(mNumPendingChanges - 1, 0);
 
         // If there are pending changes, that means this text is not the most up-to-date version
         // and we'll step on ourselves if we change the editable right now.
-        if (mNumPendingChanges == 0 && !text.contentEquals(GeckoApp.surfaceView.mEditable))
-            GeckoApp.surfaceView.setEditable(text);
+        View v = GeckoApp.mAppContext.getLayerController().getView();
+
+        if (mNumPendingChanges == 0 && !text.contentEquals(mEditable))
+            setEditable(text);
 
         if (mUpdateRequest == null)
             return;
 
         mUpdateExtract.flags = 0;
 
         // We update from (0, oldEnd) to (0, newEnd) because some Android IMEs
         // assume that updates start at zero, according to jchen.
@@ -631,66 +647,66 @@ public class GeckoInputConnection
 
         // Faster to not query for selection
         mUpdateExtract.selectionStart = newEnd;
         mUpdateExtract.selectionEnd = newEnd;
 
         mUpdateExtract.text = text.substring(0, newEnd);
         mUpdateExtract.startOffset = 0;
 
-        imm.updateExtractedText(GeckoApp.surfaceView,
-            mUpdateRequest.token, mUpdateExtract);
+        imm.updateExtractedText(v, mUpdateRequest.token, mUpdateExtract);
     }
 
     public void notifySelectionChange(InputMethodManager imm,
                                       int start, int end) {
-        // Log.d("GeckoAppJava", String.format("IME: notifySelectionChange: s=%d e=%d", start, end));
+        Log.d("GeckoAppJava", String.format("IME: notifySelectionChange: s=%d e=%d", start, end));
 
         if (!mChangeNotificationsEnabled)
             return;
 
         if (mBatchMode) {
             mBatchChanges.add(new ChangeNotification(start, end));
             return;
         }
 
+        View v = GeckoApp.mAppContext.getLayerController().getView();
         if (mComposing)
-            imm.updateSelection(GeckoApp.surfaceView,
-                mCompositionStart + mCompositionSelStart,
-                mCompositionStart + mCompositionSelStart + mCompositionSelLen,
-                mCompositionStart,
-                mCompositionStart + mComposingText.length());
+            imm.updateSelection(v,
+                                mCompositionStart + mCompositionSelStart,
+                                mCompositionStart + mCompositionSelStart + mCompositionSelLen,
+                                mCompositionStart,
+                                mCompositionStart + mComposingText.length());
         else
-            imm.updateSelection(GeckoApp.surfaceView, start, end, -1, -1);
+            imm.updateSelection(v, start, end, -1, -1);
 
         // We only change the selection if we are relatively sure that the text we have is
         // up-to-date.  Bail out if we are stil expecting changes.
         if (mNumPendingChanges > 0)
             return;
 
-        int maxLen = GeckoApp.surfaceView.mEditable.length();
-        Selection.setSelection(GeckoApp.surfaceView.mEditable, 
+        int maxLen = mEditable.length();
+        Selection.setSelection(mEditable,
                                Math.min(start, maxLen),
                                Math.min(end, maxLen));
     }
 
     public void reset() {
         mComposing = false;
         mComposingText = "";
         mUpdateRequest = null;
         mNumPendingChanges = 0;
         mBatchMode = false;
         mBatchChanges.clear();
     }
 
     // TextWatcher
     public void onTextChanged(CharSequence s, int start, int before, int count)
     {
-        // Log.d("GeckoAppShell", String.format("IME: onTextChanged: t=%s s=%d b=%d l=%d",
-        //                                      s, start, before, count));
+         Log.d("GeckoAppShell", String.format("IME: onTextChanged: t=%s s=%d b=%d l=%d",
+                                              s, start, before, count));
 
         mNumPendingChanges++;
         GeckoAppShell.sendEventToGecko(
             new GeckoEvent(GeckoEvent.IME_SET_SELECTION, start, before));
 
         if (count == 0) {
             GeckoAppShell.sendEventToGecko(
                 new GeckoEvent(GeckoEvent.IME_DELETE_TEXT, 0, 0));
@@ -727,31 +743,370 @@ public class GeckoInputConnection
     private void disableChangeNotifications() {
         mChangeNotificationsEnabled = false;
     }
 
     private void enableChangeNotifications() {
         mChangeNotificationsEnabled = true;
     }
 
+
+    public InputConnection onCreateInputConnection(EditorInfo outAttrs)
+    {
+        Log.d("GeckoAppJava", "IME: handleCreateInputConnection called");
+
+        outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
+        outAttrs.imeOptions = EditorInfo.IME_ACTION_NONE;
+        outAttrs.actionLabel = null;
+        mKeyListener = TextKeyListener.getInstance();
+
+        if (mIMEState == IME_STATE_PASSWORD)
+            outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_PASSWORD;
+        else if (mIMETypeHint.equalsIgnoreCase("url"))
+            outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_URI;
+        else if (mIMETypeHint.equalsIgnoreCase("email"))
+            outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
+        else if (mIMETypeHint.equalsIgnoreCase("search"))
+            outAttrs.imeOptions = EditorInfo.IME_ACTION_SEARCH;
+        else if (mIMETypeHint.equalsIgnoreCase("tel"))
+            outAttrs.inputType = InputType.TYPE_CLASS_PHONE;
+        else if (mIMETypeHint.equalsIgnoreCase("number") ||
+                 mIMETypeHint.equalsIgnoreCase("range"))
+            outAttrs.inputType = InputType.TYPE_CLASS_NUMBER;
+        else if (mIMETypeHint.equalsIgnoreCase("datetime") ||
+                 mIMETypeHint.equalsIgnoreCase("datetime-local"))
+            outAttrs.inputType = InputType.TYPE_CLASS_DATETIME |
+                                 InputType.TYPE_DATETIME_VARIATION_NORMAL;
+        else if (mIMETypeHint.equalsIgnoreCase("date"))
+            outAttrs.inputType = InputType.TYPE_CLASS_DATETIME |
+                                 InputType.TYPE_DATETIME_VARIATION_DATE;
+        else if (mIMETypeHint.equalsIgnoreCase("time"))
+            outAttrs.inputType = InputType.TYPE_CLASS_DATETIME |
+                                 InputType.TYPE_DATETIME_VARIATION_TIME;
+
+        if (mIMEActionHint.equalsIgnoreCase("go"))
+            outAttrs.imeOptions = EditorInfo.IME_ACTION_GO;
+        else if (mIMEActionHint.equalsIgnoreCase("done"))
+            outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
+        else if (mIMEActionHint.equalsIgnoreCase("next"))
+            outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT;
+        else if (mIMEActionHint.equalsIgnoreCase("search"))
+            outAttrs.imeOptions = EditorInfo.IME_ACTION_SEARCH;
+        else if (mIMEActionHint.equalsIgnoreCase("send"))
+            outAttrs.imeOptions = EditorInfo.IME_ACTION_SEND;
+        else if (mIMEActionHint != null && mIMEActionHint.length() != 0)
+            outAttrs.actionLabel = mIMEActionHint;
+
+        if (mIMELandscapeFS == false)
+            outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI;
+
+        reset();
+        return this;
+    }
+
+    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+        switch (event.getAction()) {
+            case KeyEvent.ACTION_DOWN:
+                return processKeyDown(keyCode, event, true);
+            case KeyEvent.ACTION_UP:
+                return processKeyUp(keyCode, event, true);
+            case KeyEvent.ACTION_MULTIPLE:
+                return onKeyMultiple(keyCode, event.getRepeatCount(), event);
+        }
+        return false;
+    }
+
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return processKeyDown(keyCode, event, false);
+    }
+
+    private boolean processKeyDown(int keyCode, KeyEvent event, boolean isPreIme) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_MENU:
+            case KeyEvent.KEYCODE_BACK:
+            case KeyEvent.KEYCODE_VOLUME_UP:
+            case KeyEvent.KEYCODE_VOLUME_DOWN:
+            case KeyEvent.KEYCODE_SEARCH:
+                return false;
+            case KeyEvent.KEYCODE_DEL:
+                // See comments in GeckoInputConnection.onKeyDel
+                if (onKeyDel()) {
+                    return true;
+                }
+                break;
+            case KeyEvent.KEYCODE_ENTER:
+                if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 &&
+                    mIMEActionHint.equalsIgnoreCase("next"))
+                    event = new KeyEvent(event.getAction(), KeyEvent.KEYCODE_TAB);
+                break;
+            default:
+                break;
+        }
+
+        if (isPreIme && mIMEState != IME_STATE_DISABLED &&
+            (event.getMetaState() & KeyEvent.META_ALT_ON) == 0)
+            // Let active IME process pre-IME key events
+            return false;
+
+        View v = GeckoApp.mAppContext.getLayerController().getView();
+
+        // KeyListener returns true if it handled the event for us.
+        if (mIMEState == IME_STATE_DISABLED ||
+            keyCode == KeyEvent.KEYCODE_ENTER ||
+            keyCode == KeyEvent.KEYCODE_DEL ||
+            (event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) != 0 ||
+            !mKeyListener.onKeyDown(v, mEditable, keyCode, event))
+            GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
+        return true;
+    }
+
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        return processKeyUp(keyCode, event, false);
+    }
+
+    private boolean processKeyUp(int keyCode, KeyEvent event, boolean isPreIme) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_BACK:
+            case KeyEvent.KEYCODE_SEARCH:
+            case KeyEvent.KEYCODE_MENU:
+                return false;
+            default:
+                break;
+        }
+
+        if (isPreIme && mIMEState != IME_STATE_DISABLED &&
+            (event.getMetaState() & KeyEvent.META_ALT_ON) == 0)
+            // Let active IME process pre-IME key events
+            return false;
+        View v = GeckoApp.mAppContext.getLayerController().getView();
+
+        if (mIMEState == IME_STATE_DISABLED ||
+            keyCode == KeyEvent.KEYCODE_ENTER ||
+            keyCode == KeyEvent.KEYCODE_DEL ||
+            (event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) != 0 ||
+            !mKeyListener.onKeyUp(v, mEditable, keyCode, event))
+            GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
+        return true;
+    }
+
+    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+        GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
+        return true;
+    }
+
+    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+        View v = GeckoApp.mAppContext.getLayerController().getView();
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_MENU:
+                InputMethodManager imm = (InputMethodManager)
+                    v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+                imm.toggleSoftInputFromWindow(v.getWindowToken(),
+                                              imm.SHOW_FORCED, 0);
+                return true;
+            default:
+                break;
+        }
+        return false;
+    }
+
+
+    public void notifyIME(int type, int state) {
+
+        View v = GeckoApp.mAppContext.getLayerController().getView();
+
+        Log.d("GeckoAppJava", "notifyIME");
+
+        if (v == null)
+            return;
+
+        Log.d("GeckoAppJava", "notifyIME v!= null");
+
+        switch (type) {
+        case NOTIFY_IME_RESETINPUTSTATE:
+
+        Log.d("GeckoAppJava", "notifyIME = reset");
+            // Composition event is already fired from widget.
+            // So reset IME flags.
+            reset();
+
+            // Don't use IMEStateUpdater for reset.
+            // Because IME may not work showSoftInput()
+            // after calling restartInput() immediately.
+            // So we have to call showSoftInput() delay.
+            InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+            if (imm != null) {
+                // no way to reset IME status directly
+                IMEStateUpdater.resetIME();
+            } else {
+                imm.restartInput(v);
+            }
+
+            // keep current enabled state
+            IMEStateUpdater.enableIME();
+            break;
+
+        case NOTIFY_IME_CANCELCOMPOSITION:
+        Log.d("GeckoAppJava", "notifyIME = cancel");
+            IMEStateUpdater.resetIME();
+            break;
+
+        case NOTIFY_IME_FOCUSCHANGE:
+        Log.d("GeckoAppJava", "notifyIME = focus");
+            IMEStateUpdater.resetIME();
+            break;
+        }
+    }
+
+    public void notifyIMEEnabled(int state, String typeHint,
+                                        String actionHint, boolean landscapeFS)
+    {
+        View v = GeckoApp.mAppContext.getLayerController().getView();
+
+        if (v == null)
+            return;
+
+        /* When IME is 'disabled', IME processing is disabled.
+           In addition, the IME UI is hidden */
+        mIMEState = state;
+        mIMETypeHint = typeHint;
+        mIMEActionHint = actionHint;
+        mIMELandscapeFS = landscapeFS;
+        IMEStateUpdater.enableIME();
+    }
+
+
+    public void notifyIMEChange(String text, int start, int end, int newEnd) {
+        View v = GeckoApp.mAppContext.getLayerController().getView();
+
+        if (v == null)
+            return;
+
+        InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+        if (imm == null)
+            return;
+
+        Log.d("GeckoAppJava", String.format("IME: notifyIMEChange: t=%s s=%d ne=%d oe=%d",
+                                            text, start, newEnd, end));
+
+        if (newEnd < 0)
+            notifySelectionChange(imm, start, end);
+        else
+            notifyTextChange(imm, text, start, end, newEnd);
+    }
+
+
+    public void returnIMEQueryResult(String result, int selectionStart, int selectionLength) {
+        mSelectionStart = selectionStart;
+        mSelectionLength = selectionLength;
+        try {
+            mQueryResult.put(result);
+        } catch (InterruptedException e) {}
+    }
+
+    static private final Timer mIMETimer = new Timer();
+
+    static private final int NOTIFY_IME_RESETINPUTSTATE = 0;
+    static private final int NOTIFY_IME_SETOPENSTATE = 1;
+    static private final int NOTIFY_IME_CANCELCOMPOSITION = 2;
+    static private final int NOTIFY_IME_FOCUSCHANGE = 3;
+
+
+    /* Delay updating IME states (see bug 573800) */
+    private static final class IMEStateUpdater extends TimerTask
+    {
+        static private IMEStateUpdater instance;
+        private boolean mEnable, mReset;
+
+        static private IMEStateUpdater getInstance() {
+            if (instance == null) {
+                instance = new IMEStateUpdater();
+                mIMETimer.schedule(instance, 200);
+            }
+            return instance;
+        }
+
+        static public synchronized void enableIME() {
+            getInstance().mEnable = true;
+        }
+
+        static public synchronized void resetIME() {
+            getInstance().mReset = true;
+        }
+
+        public void run() {
+            Log.d("GeckoAppJava", "IME: run()");
+            synchronized(IMEStateUpdater.class) {
+                instance = null;
+            }
+
+            View v = GeckoApp.mAppContext.getLayerController().getView();
+            Log.d("GeckoAppJava", "IME: v="+v);
+
+            InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+            if (imm == null)
+                return;
+
+            if (mReset)
+                imm.restartInput(v);
+
+            if (!mEnable)
+                return;
+
+            if (mIMEState != IME_STATE_DISABLED &&
+                mIMEState != IME_STATE_PLUGIN)
+                imm.showSoftInput(v, 0);
+            else
+                imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
+        }
+    }
+
+    public void setEditable(String contents)
+    {
+        mEditable.removeSpan(this);
+        mEditable.replace(0, mEditable.length(), contents);
+        mEditable.setSpan(this, 0, contents.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        Selection.setSelection(mEditable, contents.length());
+    }
+
+    public void initEditable(String contents)
+    {
+        mEditable = mEditableFactory.newEditable(contents);
+        mEditable.setSpan(this, 0, contents.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        Selection.setSelection(mEditable, contents.length());
+    }
+
     // Is a composition active?
     boolean mComposing;
     // Composition text when a composition is active
     String mComposingText = "";
     // Start index of the composition within the text body
     int mCompositionStart;
     /* During a composition, we should not alter the real selection,
         therefore we keep our own offsets to emulate selection */
     // Start of fake selection, relative to start of composition
     int mCompositionSelStart;
     // Length of fake selection
     int mCompositionSelLen;
     // Number of in flight changes
     int mNumPendingChanges;
 
+    // IME stuff
+    public static final int IME_STATE_DISABLED = 0;
+    public static final int IME_STATE_ENABLED = 1;
+    public static final int IME_STATE_PASSWORD = 2;
+    public static final int IME_STATE_PLUGIN = 3;
+
+    KeyListener mKeyListener;
+    Editable mEditable;
+    Editable.Factory mEditableFactory;
+    static int mIMEState;
+    static String mIMETypeHint;
+    static String mIMEActionHint;
+    static boolean mIMELandscapeFS;
+
     private boolean mBatchMode;
     private boolean mChangeNotificationsEnabled = true;
 
     private CopyOnWriteArrayList<ChangeNotification> mBatchChanges =
         new CopyOnWriteArrayList<ChangeNotification>();
 
     ExtractedTextRequest mUpdateRequest;
     final ExtractedText mUpdateExtract = new ExtractedText();
deleted file mode 100644
--- a/embedding/android/GeckoSurfaceView.java
+++ /dev/null
@@ -1,827 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Android code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Vladimir Vukicevic <vladimir@pobox.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;
-
-import java.io.*;
-import java.util.*;
-import java.util.concurrent.*;
-import java.util.concurrent.locks.*;
-import java.util.concurrent.atomic.*;
-import java.util.zip.*;
-import java.nio.*;
-
-import android.os.*;
-import android.app.*;
-import android.text.*;
-import android.text.method.*;
-import android.view.*;
-import android.view.inputmethod.*;
-import android.content.*;
-import android.graphics.*;
-import android.widget.*;
-import android.hardware.*;
-import android.location.*;
-import android.graphics.drawable.*;
-import android.content.res.*;
-import android.util.*;
-
-/*
- * GeckoSurfaceView implements a GL surface view,
- * similar to GLSurfaceView.  However, since we
- * already have a thread for Gecko, we don't really want
- * a separate renderer thread that GLSurfaceView provides.
- */
-class GeckoSurfaceView
-    extends SurfaceView
-    implements SurfaceHolder.Callback, SensorEventListener, LocationListener
-{
-    private static final String LOG_FILE_NAME = "GeckoSurfaceView";
-
-    public GeckoSurfaceView(Context context) {
-        super(context, null, android.R.style.Theme_Light_NoTitleBar);
-
-        getHolder().addCallback(this);
-        inputConnection = new GeckoInputConnection(this);
-        gestureScanner = new GeckoGestureDetector(context);
-        setFocusable(true);
-        setFocusableInTouchMode(true);
-
-        DisplayMetrics metrics = new DisplayMetrics();
-        GeckoApp.mAppContext.getWindowManager().
-            getDefaultDisplay().getMetrics(metrics);
-        mWidth = metrics.widthPixels;
-        mHeight = metrics.heightPixels;
-        mBufferWidth = 0;
-        mBufferHeight = 0;
-
-        mSurfaceLock = new ReentrantLock();
-
-        mEditableFactory = Editable.Factory.getInstance();
-        initEditable("");
-        mIMEState = IME_STATE_DISABLED;
-        mIMETypeHint = "";
-        mIMEActionHint = "";
-    }
-
-    protected void finalize() throws Throwable {
-        super.finalize();
-    }
-
-    /*
-     * Called on main thread
-     */
-
-    public String getStartupBitmapFilePath() {
-        File file = new File(Environment.getExternalStorageDirectory(),
-                             "lastScreen.png");
-        return file.toString();
-    }
-
-    public void hideStartupBitmap() {
-        Log.e(LOG_FILE_NAME, "!!! hideStartupBitmap !!!");
-        if (mShowingLoadScreen == false)
-            return;
-
-        mStartupBitmap = null;
-        mShowingLoadScreen = false;
-
-        surfaceCreated(getHolder());
-        surfaceChanged(getHolder(), mFormat, mWidth, mHeight);
-    }
-
-    public void showStartupBitmap() {
-        Log.e(LOG_FILE_NAME, "!!! showStartupBitmap !!!");
-        mShowingLoadScreen = true;
-    }
-
-    public void loadStartupBitmap() {
-        // This is blocking on the main thread and that is
-        // okay.  we want to get this image in as soon as
-        // possible so that we can paint it to the screen.
-        String filePath = getStartupBitmapFilePath();
-        mStartupBitmap = BitmapFactory.decodeFile(filePath);
-    }
-
-    public void drawStartupBitmap(SurfaceHolder holder, int width, int height) {
-        Log.e(LOG_FILE_NAME, "!!! drawStartupBitmap !!!");
-
-        Canvas c = holder.lockCanvas();
-        if (c == null) {
-            Log.e(LOG_FILE_NAME, "!!! NO CANVAS !!!");
-            return;
-        }
-
-        if (mStartupBitmap == null) {
-            Resources res = getResources();
-            Drawable drawable = res.getDrawable(R.drawable.start);
-            drawable.setBounds(0, 0, width, height);
-            drawable.draw(c);
-
-            Paint paint = new Paint();
-            c.drawText("Place holder. Missing screenshot.", 10.0f, 20.0f, paint);
-        } else {
-            Drawable drawable = new BitmapDrawable(mStartupBitmap);
-            drawable.setBounds(0, 0, width, height);
-            drawable.draw(c);
-        }
-        holder.unlockCanvasAndPost(c);
-    }
-
-    public void draw(SurfaceHolder holder, ByteBuffer buffer) {
-        Log.e(LOG_FILE_NAME, "!!! draw1 !!!");
-
-        if (buffer == null || buffer.capacity() != (mWidth * mHeight * 2))
-            return;
-
-        synchronized (mSoftwareBuffer) {
-            if (buffer != mSoftwareBuffer || mSoftwareBufferCopy == null)
-                return;
-
-            Canvas c = holder.lockCanvas();
-            if (c == null)
-                return;
-            mSoftwareBufferCopy.copyPixelsFromBuffer(buffer);
-            c.drawBitmap(mSoftwareBufferCopy, 0, 0, null);
-            holder.unlockCanvasAndPost(c);
-        }
-    }
-
-    public void draw(SurfaceHolder holder, Bitmap bitmap) {
-        Log.e(LOG_FILE_NAME, "!!! draw2 !!!");
-
-        if (bitmap == null ||
-            bitmap.getWidth() != mWidth || bitmap.getHeight() != mHeight)
-            return;
-
-        synchronized (mSoftwareBitmap) {
-            if (bitmap != mSoftwareBitmap)
-                return;
-
-            Canvas c = holder.lockCanvas();
-            if (c == null)
-                return;
-            c.drawBitmap(bitmap, 0, 0, null);
-            holder.unlockCanvasAndPost(c);
-        }
-    }
-
-    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-        Log.i(LOG_FILE_NAME, "!!! surfaceChanged: fmt: " + format + " dim: " + width + " " + height);
-
-        mFormat = format;
-        mWidth = width;
-        mHeight = height;
-
-        if (mShowingLoadScreen) {
-            drawStartupBitmap(holder, width, height);
-            return;
-        }
-
-        // On pre-Honeycomb, force exactly one frame of the previous size
-        // to render because the surface change is only seen by GLES after we
-        // have swapped the back buffer (i.e. the buffer size only changes 
-        // after the next swap buffer). We need to make sure Gecko's view 
-        // resizes when Android's buffer resizes.
-        // In Honeycomb, the buffer size changes immediately, so rendering a
-        // frame of the previous size is unnecessary (and wrong).
-        if (mDrawMode == DRAW_GLES_2 && 
-            (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)) {
-            // When we get a surfaceChange event, we have 0 to n paint events 
-            // waiting in the Gecko event queue. We will make the first
-            // succeed and the abort the others.
-            mDrawSingleFrame = true;
-            if (!mInDrawing) { 
-                // Queue at least one paint event in case none are queued.
-                GeckoAppShell.scheduleRedraw();
-            }
-            GeckoAppShell.geckoEventSync();
-            mDrawSingleFrame = false;
-            mAbortDraw = false;
-        }
-
-        mSurfaceLock.lock();
-
-        if (mInDrawing) {
-            Log.w(LOG_FILE_NAME, "!! surfaceChanged while mInDrawing is true!");
-        }
-
-        boolean invalidSize;
-
-        if (width == 0 || height == 0) {
-            mSoftwareBitmap = null;
-            mSoftwareBuffer = null;
-            mSoftwareBufferCopy = null;
-            invalidSize = true;
-        } else {
-            invalidSize = false;
-        }
-
-        boolean doSyncDraw =
-            mDrawMode == DRAW_2D &&
-            !invalidSize &&
-            GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning);
-        mSyncDraw = doSyncDraw;
-
-        mSurfaceValid = true;
-
-        Log.i(LOG_FILE_NAME, "!! surfaceChanged: fmt: " + format + " dim: " + width + " " + height);
-
-        try {
-            DisplayMetrics metrics = new DisplayMetrics();
-            GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
-
-            GeckoEvent e = new GeckoEvent(GeckoEvent.SIZE_CHANGED, width, height,
-                                          metrics.widthPixels, metrics.heightPixels);
-            GeckoAppShell.sendEventToGecko(e);
-        } finally {
-            mSurfaceLock.unlock();
-        }
-
-        if (doSyncDraw) {
-            GeckoAppShell.scheduleRedraw();
-
-            Object syncDrawObject = null;
-            try {
-                syncDrawObject = mSyncDraws.take();
-            } catch (InterruptedException ie) {
-                Log.e(LOG_FILE_NAME, "!! Threw exception while getting sync draw bitmap/buffer: ", ie);
-            }
-            if (syncDrawObject != null) {
-                if (syncDrawObject instanceof Bitmap)
-                    draw(holder, (Bitmap)syncDrawObject);
-                else
-                    draw(holder, (ByteBuffer)syncDrawObject);
-            } else {
-                Log.e(LOG_FILE_NAME, "!! Synchronised draw object is null");
-            }
-        } else if (!mShowingLoadScreen) {
-            // Make sure a frame is drawn before we return
-            // otherwise we see artifacts or a black screen
-            GeckoAppShell.scheduleRedraw();
-            GeckoAppShell.geckoEventSync();
-        }
-
-        // if the surface changed size and we have the soft keyboard up, make sure
-        // the focused input field is still in view or it might get hidden behind the
-        // keyboard and be really hard to use
-        if (mIMEState == IME_STATE_ENABLED) {
-            GeckoAppShell.sendEventToGecko(new GeckoEvent("ScrollTo:FocusedInput", null));
-        }
-    }
-
-    public void surfaceCreated(SurfaceHolder holder) {
-        // Delay sending this event if we are painting the
-        // load screen.  The native access paint path will
-        // paint directly to the screen and we will see a
-        // black screen while content is initally being
-        // drawn.
-        if (!mShowingLoadScreen) {
-            Log.i(LOG_FILE_NAME, "!! surface created");
-            GeckoEvent e = new GeckoEvent(GeckoEvent.SURFACE_CREATED);
-            GeckoAppShell.sendEventToGecko(e);
-        }
-    }
-
-    public void saveLast(boolean sync) {
-        Log.i(LOG_FILE_NAME, "!! save last");
-        GeckoEvent event = new GeckoEvent();
-        event.mType = GeckoEvent.SAVE_STATE;
-        event.mCharacters = getStartupBitmapFilePath();
-        if (sync)
-            GeckoAppShell.sendEventToGeckoSync(event);
-        else
-            GeckoAppShell.sendEventToGecko(event);
-    }
-
-    public void surfaceDestroyed(SurfaceHolder holder) {
-        Log.i(LOG_FILE_NAME, "!! surface destroyed");
-        mSurfaceValid = false;
-        mSoftwareBuffer = null;
-        mSoftwareBufferCopy = null;
-        mSoftwareBitmap = null;
-        GeckoEvent e = new GeckoEvent(GeckoEvent.SURFACE_DESTROYED);
-        if (mDrawMode == DRAW_GLES_2) {
-            // Ensure GL cleanup occurs before we return.
-            GeckoAppShell.sendEventToGeckoSync(e);
-        } else {
-            GeckoAppShell.sendEventToGecko(e);
-        }
-    }
-
-    public Bitmap getSoftwareDrawBitmap() {
-        if (mSoftwareBitmap == null ||
-            mSoftwareBitmap.getHeight() != mHeight ||
-            mSoftwareBitmap.getWidth() != mWidth) {
-            mSoftwareBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.RGB_565);
-        }
-
-        mDrawMode = DRAW_2D;
-        return mSoftwareBitmap;
-    }
-
-    public ByteBuffer getSoftwareDrawBuffer() {
-        // We store pixels in 565 format, so two bytes per pixel (explaining
-        // the * 2 in the following check/allocation)
-        if (mSoftwareBuffer == null ||
-            mSoftwareBuffer.capacity() != (mWidth * mHeight * 2)) {
-            mSoftwareBuffer = ByteBuffer.allocateDirect(mWidth * mHeight * 2);
-        }
-
-        if (mSoftwareBufferCopy == null ||
-            mSoftwareBufferCopy.getHeight() != mHeight ||
-            mSoftwareBufferCopy.getWidth() != mWidth) {
-            mSoftwareBufferCopy = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.RGB_565);
-        }
-
-        mDrawMode = DRAW_2D;
-        return mSoftwareBuffer;
-    }
-
-    public Surface getSurface() {
-        return getHolder().getSurface();
-    }
-
-    /*
-     * Called on Gecko thread
-     */
-
-    public static final int DRAW_ERROR = 0;
-    public static final int DRAW_GLES_2 = 1;
-    public static final int DRAW_2D = 2;
-    // Drawing is disable when the surface buffer
-    // has changed size but we haven't yet processed the
-    // resize event.
-    public static final int DRAW_DISABLED = 3;
-
-    public int beginDrawing() {
-
-        if (mInDrawing) {
-            Log.e(LOG_FILE_NAME, "!! Recursive beginDrawing call!");
-            return DRAW_ERROR;
-        }
-
-        // Once we drawn our first frame after resize we can ignore
-        // the other draw events until we handle the resize events.
-        if (mAbortDraw) {
-            return DRAW_DISABLED;
-        }
-
-        /* Grab the lock, which we'll hold while we're drawing.
-         * It gets released in endDrawing(), and is also used in surfaceChanged
-         * to make sure that we don't change our surface details while
-         * we're in the middle of drawing (and especially in the middle of
-         * executing beginDrawing/endDrawing).
-         *
-         * We might not need to hold this lock in between
-         * beginDrawing/endDrawing, and might just be able to make
-         * surfaceChanged, beginDrawing, and endDrawing synchronized,
-         * but this way is safer for now.
-         */
-        mSurfaceLock.lock();
-
-        if (!mSurfaceValid) {
-            Log.e(LOG_FILE_NAME, "!! Surface not valid");
-            mSurfaceLock.unlock();
-            return DRAW_ERROR;
-        }
-
-        mInDrawing = true;
-        mDrawMode = DRAW_GLES_2;
-        return DRAW_GLES_2;
-    }
-
-    public void endDrawing() {
-        if (!mInDrawing) {
-            Log.e(LOG_FILE_NAME, "!! endDrawing without beginDrawing!");
-            return;
-        }
-
-       if (mDrawSingleFrame)
-            mAbortDraw = true;
-
-        try {
-            if (!mSurfaceValid) {
-                Log.e(LOG_FILE_NAME, "!! endDrawing with false mSurfaceValid");
-                return;
-            }
-        } finally {
-            mInDrawing = false;
-
-            if (!mSurfaceLock.isHeldByCurrentThread())
-                Log.e(LOG_FILE_NAME, "!! endDrawing while mSurfaceLock not held by current thread!");
-
-            mSurfaceLock.unlock();
-        }
-    }
-
-    /* How this works:
-     * Whenever we want to draw, we want to be sure that we do not lock
-     * the canvas unless we're sure we can draw. Locking the canvas clears
-     * the canvas to black in most cases, causing a black flash.
-     * At the same time, the surface can resize/disappear at any moment
-     * unless the canvas is locked.
-     * Draws originate from a different thread so the surface could change
-     * at any moment while we try to draw until we lock the canvas.
-     *
-     * Also, never try to lock the canvas while holding the surface lock
-     * unless you're in SurfaceChanged, in which case the canvas was already
-     * locked. Surface lock -> Canvas lock will lead to AB-BA deadlocks.
-     */
-    public void draw2D(Bitmap bitmap, int width, int height) {
-        // mSurfaceLock ensures that we get mSyncDraw/mSoftwareBitmap/etc.
-        // set correctly before determining whether we should do a sync draw
-        Log.e(LOG_FILE_NAME, "!!! draw2d1 !!!");
-        mSurfaceLock.lock();
-        try {
-            if (mSyncDraw) {
-                if (bitmap != mSoftwareBitmap || width != mWidth || height != mHeight)
-                    return;
-                mSyncDraw = false;
-                try {
-                    mSyncDraws.put(bitmap);
-                } catch (InterruptedException ie) {
-                    Log.e(LOG_FILE_NAME, "!! Threw exception while getting sync draws queue: ", ie);
-                }
-                return;
-            }
-        } finally {
-            mSurfaceLock.unlock();
-        }
-
-        draw(getHolder(), bitmap);
-    }
-
-    public void draw2D(ByteBuffer buffer, int stride) {
-        Log.e(LOG_FILE_NAME, "!!! draw2d2 !!!");
-        mSurfaceLock.lock();
-        try {
-            if (mSyncDraw) {
-                if (buffer != mSoftwareBuffer || stride != (mWidth * 2))
-                    return;
-                mSyncDraw = false;
-                try {
-                    mSyncDraws.put(buffer);
-                } catch (InterruptedException ie) {
-                    Log.e(LOG_FILE_NAME, "!! Threw exception while getting sync bitmaps queue: ", ie);
-                }
-                return;
-            }
-        } finally {
-            mSurfaceLock.unlock();
-        }
-
-        draw(getHolder(), buffer);
-    }
-
-    @Override
-    public boolean onCheckIsTextEditor () {
-        return false;
-    }
-
-    @Override
-    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
-        outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
-        outAttrs.imeOptions = EditorInfo.IME_ACTION_NONE;
-        outAttrs.actionLabel = null;
-        mKeyListener = TextKeyListener.getInstance();
-
-        if (mIMEState == IME_STATE_PASSWORD)
-            outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_PASSWORD;
-        else if (mIMETypeHint.equalsIgnoreCase("url"))
-            outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_URI;
-        else if (mIMETypeHint.equalsIgnoreCase("email"))
-            outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
-        else if (mIMETypeHint.equalsIgnoreCase("search"))
-            outAttrs.imeOptions = EditorInfo.IME_ACTION_SEARCH;
-        else if (mIMETypeHint.equalsIgnoreCase("tel"))
-            outAttrs.inputType = InputType.TYPE_CLASS_PHONE;
-        else if (mIMETypeHint.equalsIgnoreCase("number") ||
-                 mIMETypeHint.equalsIgnoreCase("range"))
-            outAttrs.inputType = InputType.TYPE_CLASS_NUMBER;
-        else if (mIMETypeHint.equalsIgnoreCase("datetime") ||
-                 mIMETypeHint.equalsIgnoreCase("datetime-local"))
-            outAttrs.inputType = InputType.TYPE_CLASS_DATETIME |
-                                 InputType.TYPE_DATETIME_VARIATION_NORMAL;
-        else if (mIMETypeHint.equalsIgnoreCase("date"))
-            outAttrs.inputType = InputType.TYPE_CLASS_DATETIME |
-                                 InputType.TYPE_DATETIME_VARIATION_DATE;
-        else if (mIMETypeHint.equalsIgnoreCase("time"))
-            outAttrs.inputType = InputType.TYPE_CLASS_DATETIME |
-                                 InputType.TYPE_DATETIME_VARIATION_TIME;
-
-        if (mIMEActionHint.equalsIgnoreCase("go"))
-            outAttrs.imeOptions = EditorInfo.IME_ACTION_GO;
-        else if (mIMEActionHint.equalsIgnoreCase("done"))
-            outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
-        else if (mIMEActionHint.equalsIgnoreCase("next"))
-            outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT;
-        else if (mIMEActionHint.equalsIgnoreCase("search"))
-            outAttrs.imeOptions = EditorInfo.IME_ACTION_SEARCH;
-        else if (mIMEActionHint.equalsIgnoreCase("send"))
-            outAttrs.imeOptions = EditorInfo.IME_ACTION_SEND;
-        else if (mIMEActionHint != null && mIMEActionHint.length() != 0)
-            outAttrs.actionLabel = mIMEActionHint;
-
-        if (mIMELandscapeFS == false)
-            outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI;
-
-        inputConnection.reset();
-        return inputConnection;
-    }
-
-    public void setEditable(String contents)
-    {
-        mEditable.removeSpan(inputConnection);
-        mEditable.replace(0, mEditable.length(), contents);
-        mEditable.setSpan(inputConnection, 0, contents.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-        Selection.setSelection(mEditable, contents.length());
-    }
-
-    public void initEditable(String contents)
-    {
-        mEditable = mEditableFactory.newEditable(contents);
-        mEditable.setSpan(inputConnection, 0, contents.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-        Selection.setSelection(mEditable, contents.length());
-    }
-
-    // accelerometer
-    public void onAccuracyChanged(Sensor sensor, int accuracy)
-    {
-    }
-
-    public void onSensorChanged(SensorEvent event)
-    {
-        GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
-    }
-
-    private class GeocoderTask extends AsyncTask<Location, Void, Void> {
-        protected Void doInBackground(Location... location) {
-            try {
-                List<Address> addresses = mGeocoder.getFromLocation(location[0].getLatitude(),
-                                                                    location[0].getLongitude(), 1);
-                // grab the first address.  in the future,
-                // may want to expose multiple, or filter
-                // for best.
-                mLastGeoAddress = addresses.get(0);
-                GeckoAppShell.sendEventToGecko(new GeckoEvent(location[0], mLastGeoAddress));
-            } catch (Exception e) {
-                Log.w(LOG_FILE_NAME, "GeocoderTask "+e);
-            }
-            return null;
-        }
-    }
-
-    // geolocation
-    public void onLocationChanged(Location location)
-    {
-        if (mGeocoder == null)
-            mGeocoder = new Geocoder(getContext(), Locale.getDefault());
-
-        if (mLastGeoAddress == null) {
-            new GeocoderTask().execute(location);
-        }
-        else {
-            float[] results = new float[1];
-            Location.distanceBetween(location.getLatitude(),
-                                     location.getLongitude(),
-                                     mLastGeoAddress.getLatitude(),
-                                     mLastGeoAddress.getLongitude(),
-                                     results);
-            // pfm value.  don't want to slam the
-            // geocoder with very similar values, so
-            // only call after about 100m
-            if (results[0] > 100)
-                new GeocoderTask().execute(location);
-        }
-
-        GeckoAppShell.sendEventToGecko(new GeckoEvent(location, mLastGeoAddress));
-    }
-
-    public void onProviderDisabled(String provider)
-    {
-    }
-
-    public void onProviderEnabled(String provider)
-    {
-    }
-
-    public void onStatusChanged(String provider, int status, Bundle extras)
-    {
-    }
-
-    // event stuff
-    public boolean onTouchEvent(MotionEvent event) {
-        requestFocus(FOCUS_UP, null);
-        GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
-        return gestureScanner.onTouchEvent(event);
-    }
-
-    @Override
-    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
-        if (event.isSystem())
-            return super.onKeyPreIme(keyCode, event);
-
-        switch (event.getAction()) {
-            case KeyEvent.ACTION_DOWN:
-                return processKeyDown(keyCode, event, true);
-            case KeyEvent.ACTION_UP:
-                return processKeyUp(keyCode, event, true);
-            case KeyEvent.ACTION_MULTIPLE:
-                return onKeyMultiple(keyCode, event.getRepeatCount(), event);
-        }
-        return super.onKeyPreIme(keyCode, event);
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        return processKeyDown(keyCode, event, false);
-    }
-
-    private boolean processKeyDown(int keyCode, KeyEvent event, boolean isPreIme) {
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_MENU:
-            case KeyEvent.KEYCODE_BACK:
-            case KeyEvent.KEYCODE_VOLUME_UP:
-            case KeyEvent.KEYCODE_VOLUME_DOWN:
-            case KeyEvent.KEYCODE_SEARCH:
-                return false;
-            case KeyEvent.KEYCODE_DEL:
-                // See comments in GeckoInputConnection.onKeyDel
-                if (inputConnection != null &&
-                    inputConnection.onKeyDel()) {
-                    return true;
-                }
-                break;
-            case KeyEvent.KEYCODE_ENTER:
-                if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 &&
-                    mIMEActionHint.equalsIgnoreCase("next"))
-                    event = new KeyEvent(event.getAction(), KeyEvent.KEYCODE_TAB);
-                break;
-            default:
-                break;
-        }
-
-        if (isPreIme && mIMEState != IME_STATE_DISABLED &&
-            (event.getMetaState() & KeyEvent.META_ALT_ON) == 0)
-            // Let active IME process pre-IME key events
-            return false;
-
-        // KeyListener returns true if it handled the event for us.
-        if (mIMEState == IME_STATE_DISABLED ||
-            keyCode == KeyEvent.KEYCODE_ENTER ||
-            keyCode == KeyEvent.KEYCODE_DEL ||
-            (event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) != 0 ||
-            !mKeyListener.onKeyDown(this, mEditable, keyCode, event))
-            GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
-        return true;
-    }
-
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        return processKeyUp(keyCode, event, false);
-    }
-
-    private boolean processKeyUp(int keyCode, KeyEvent event, boolean isPreIme) {
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_BACK:
-            case KeyEvent.KEYCODE_SEARCH:
-            case KeyEvent.KEYCODE_MENU:
-                return false;
-            default:
-                break;
-        }
-
-        if (isPreIme && mIMEState != IME_STATE_DISABLED &&
-            (event.getMetaState() & KeyEvent.META_ALT_ON) == 0)
-            // Let active IME process pre-IME key events
-            return false;
-
-        if (mIMEState == IME_STATE_DISABLED ||
-            keyCode == KeyEvent.KEYCODE_ENTER ||
-            keyCode == KeyEvent.KEYCODE_DEL ||
-            (event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) != 0 ||
-            !mKeyListener.onKeyUp(this, mEditable, keyCode, event))
-            GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
-        return true;
-    }
-
-    @Override
-    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
-        GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
-        return true;
-    }
-
-    @Override
-    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_MENU:
-                InputMethodManager imm = (InputMethodManager)
-                    getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-                imm.toggleSoftInputFromWindow(getWindowToken(),
-                                              imm.SHOW_FORCED, 0);
-                return true;
-            default:
-                break;
-        }
-        return false;
-    }
-
-    // Is this surface valid for drawing into?
-    boolean mSurfaceValid;
-
-    // Are we actively between beginDrawing/endDrawing?
-    boolean mInDrawing;
-
-    // Used to finish the current buffer before changing the surface size
-    boolean mDrawSingleFrame = false;
-    boolean mAbortDraw = false;
-
-    // Are we waiting for a buffer to draw in surfaceChanged?
-    boolean mSyncDraw;
-
-    // True if gecko requests a buffer
-    int mDrawMode;
-
-    static boolean mShowingLoadScreen = true;
-
-    // let's not change stuff around while we're in the middle of
-    // starting drawing, ending drawing, or changing surface
-    // characteristics
-    ReentrantLock mSurfaceLock;
-
-    // Surface format, from surfaceChanged.  Largely
-    // useless.
-    int mFormat;
-
-    // the dimensions of the surface
-    int mWidth;
-    int mHeight;
-
-    // the dimensions of the buffer we're using for drawing,
-    // that is the software buffer or the EGLSurface
-    int mBufferWidth;
-    int mBufferHeight;
-
-    // IME stuff
-    public static final int IME_STATE_DISABLED = 0;
-    public static final int IME_STATE_ENABLED = 1;
-    public static final int IME_STATE_PASSWORD = 2;
-    public static final int IME_STATE_PLUGIN = 3;
-
-    GeckoInputConnection inputConnection;
-    GeckoGestureDetector gestureScanner;
-    KeyListener mKeyListener;
-    Editable mEditable;
-    Editable.Factory mEditableFactory;
-    int mIMEState;
-    String mIMETypeHint;
-    String mIMEActionHint;
-    boolean mIMELandscapeFS;
-
-    // Software rendering
-    Bitmap mSoftwareBitmap;
-    ByteBuffer mSoftwareBuffer;
-    Bitmap mSoftwareBufferCopy;
-    Bitmap mStartupBitmap;
-
-    Geocoder mGeocoder;
-    Address  mLastGeoAddress;
-
-    final SynchronousQueue<Object> mSyncDraws = new SynchronousQueue<Object>();
-}
-
--- a/embedding/android/Makefile.in
+++ b/embedding/android/Makefile.in
@@ -56,25 +56,46 @@ JAVAFILES = \
   Favicons.java \
   GeckoApp.java \
   GeckoAppShell.java \
   GeckoConnectivityReceiver.java \
   GeckoEvent.java \
   GeckoEventListener.java \
   GeckoInputConnection.java \
   GeckoPreferences.java \
-  GeckoSurfaceView.java \
-  GeckoGestureDetector.java \
   GlobalHistory.java \
   PromptService.java \
   SurfaceInfo.java \
   Tab.java \
   Tabs.java \
   TabsTray.java \
   GeckoBatteryManager.java \
+  gfx/BufferedCairoImage.java \
+  gfx/CairoImage.java \
+  gfx/CairoUtils.java \
+  gfx/FloatPoint.java \
+  gfx/FloatRect.java \
+  gfx/GeckoSoftwareLayerClient.java \
+  gfx/InputConnectionHandler.java \
+  gfx/IntPoint.java \
+  gfx/IntRect.java \
+  gfx/IntSize.java \
+  gfx/Layer.java \
+  gfx/LayerClient.java \
+  gfx/LayerController.java \
+  gfx/LayerRenderer.java \
+  gfx/LayerView.java \
+  gfx/NinePatchTileLayer.java \
+  gfx/PlaceholderLayerClient.java \
+  gfx/SingleTileLayer.java \
+  gfx/TextureReaper.java \
+  gfx/TextLayer.java \
+  gfx/TileLayer.java \
+  ui/PanZoomController.java \
+  ui/ViewportController.java \
   $(NULL)
 
 PROCESSEDJAVAFILES = \
   App.java \
   LauncherShortcuts.java \
   NotificationHandler.java \
   Restarter.java \
   $(NULL)
@@ -284,16 +305,18 @@ MOZ_ANDROID_DRAWABLES += embedding/andro
                          embedding/android/resources/drawable/tab_close.png                   \
                          embedding/android/resources/drawable/tabs_button.xml                 \
                          embedding/android/resources/drawable/tabs_normal.png                 \
                          embedding/android/resources/drawable/tabs_pressed.png                \
                          embedding/android/resources/drawable/tabs_off.png                    \
                          embedding/android/resources/drawable/tabs_plus.png                   \
                          embedding/android/resources/drawable/tabs_menu.png                   \
                          embedding/android/resources/drawable/tabs_tray_bg.9.png              \
+                         embedding/android/resources/drawable/checkerboard.png                \
+                         embedding/android/resources/drawable/shadow.png                      \
                          $(NULL)
 
 
 MOZ_ANDROID_DRAWABLES += $(shell if test -e $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/android-resources.mn; then cat $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/android-resources.mn | tr '\n' ' ';  fi)
 
 include $(topsrcdir)/config/rules.mk
 
 ifneq ($(AB_CD),en-US)
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/BufferedCairoImage.java
@@ -0,0 +1,73 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.gfx.CairoImage;
+import org.mozilla.gecko.gfx.CairoUtils;
+import android.graphics.Bitmap;
+import java.nio.ByteBuffer;
+
+/** A Cairo image that simply saves a buffer of pixel data. */
+public class BufferedCairoImage extends CairoImage {
+    private ByteBuffer mBuffer;
+    private int mWidth, mHeight, mFormat;
+
+    /** Creates a buffered Cairo image from a byte buffer. */
+    public BufferedCairoImage(ByteBuffer inBuffer, int inWidth, int inHeight, int inFormat) {
+        mBuffer = inBuffer; mWidth = inWidth; mHeight = inHeight; mFormat = inFormat;
+    }
+
+    /** Creates a buffered Cairo image from an Android bitmap. */
+    public BufferedCairoImage(Bitmap bitmap) {
+        mFormat = CairoUtils.bitmapConfigToCairoFormat(bitmap.getConfig());
+        mWidth = bitmap.getWidth();
+        mHeight = bitmap.getHeight();
+        mBuffer = ByteBuffer.allocateDirect(mWidth * mHeight * 4);
+        bitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
+    }
+
+    @Override
+    public ByteBuffer lockBuffer() { return mBuffer; }
+    @Override
+    public int getWidth() { return mWidth; }
+    @Override
+    public int getHeight() { return mHeight; }
+    @Override
+    public int getFormat() { return mFormat; }
+}
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/CairoImage.java
@@ -0,0 +1,60 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import java.nio.ByteBuffer;
+
+/*
+ * A bitmap with pixel data in one of the formats that Cairo understands.
+ */
+public abstract class CairoImage {
+    public abstract ByteBuffer lockBuffer();
+    public void unlockBuffer() { /* By default, a no-op. */ }
+
+    public abstract int getWidth();
+    public abstract int getHeight();
+    public abstract int getFormat();
+
+    public static final int FORMAT_INVALID = -1;
+    public static final int FORMAT_ARGB32 = 0;
+    public static final int FORMAT_RGB24 = 1;
+    public static final int FORMAT_A8 = 2;
+    public static final int FORMAT_A1 = 3;
+    public static final int FORMAT_RGB16_565 = 4;
+}
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/CairoUtils.java
@@ -0,0 +1,120 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.gfx.CairoImage;
+import android.graphics.Bitmap;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * Utility methods useful when displaying Cairo bitmaps using OpenGL ES.
+ */
+public class CairoUtils {
+    private CairoUtils() { /* Don't call me. */ }
+
+    public static int cairoFormatToGLInternalFormat(int cairoFormat) {
+        switch (cairoFormat) {
+        case CairoImage.FORMAT_ARGB32:
+            return GL10.GL_RGBA;
+        case CairoImage.FORMAT_RGB24:
+        case CairoImage.FORMAT_RGB16_565:
+            return GL10.GL_RGB;
+        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");
+        }
+    }
+
+    public static int cairoFormatToGLFormat(int cairoFormat) {
+        switch (cairoFormat) {
+        case CairoImage.FORMAT_ARGB32:
+            return GL10.GL_RGBA;
+        case CairoImage.FORMAT_RGB24:
+        case CairoImage.FORMAT_RGB16_565:
+            return GL10.GL_RGB;
+        case CairoImage.FORMAT_A8:
+        case CairoImage.FORMAT_A1:
+            return GL10.GL_ALPHA;
+        default:
+            throw new RuntimeException("Unknown Cairo format");
+        }
+    }
+
+    public static int cairoFormatToGLType(int cairoFormat) {
+        switch (cairoFormat) {
+        case CairoImage.FORMAT_ARGB32:
+        case CairoImage.FORMAT_RGB24:
+        case CairoImage.FORMAT_A8:
+            return GL10.GL_UNSIGNED_BYTE;
+        case CairoImage.FORMAT_A1:
+            throw new RuntimeException("Cairo FORMAT_A1 unsupported in Android OpenGL");
+        case CairoImage.FORMAT_RGB16_565:
+            return GL10.GL_UNSIGNED_SHORT_5_6_5;
+        default:
+            throw new RuntimeException("Unknown Cairo format");
+        }
+    }
+
+    public static int bitsPerPixelForCairoFormat(int cairoFormat) {
+        switch (cairoFormat) {
+        case CairoImage.FORMAT_A1:          return 1;
+        case CairoImage.FORMAT_A8:          return 8;
+        case CairoImage.FORMAT_RGB16_565:   return 16;
+        case CairoImage.FORMAT_RGB24:       return 24;
+        case CairoImage.FORMAT_ARGB32:      return 32;
+        default:
+            throw new RuntimeException("Unknown Cairo format");
+        }
+    }
+
+    public static int bitmapConfigToCairoFormat(Bitmap.Config config) {
+        if (config == null)
+            return CairoImage.FORMAT_ARGB32;    /* Droid Pro fix. */
+
+        switch (config) {
+        case ALPHA_8:   return CairoImage.FORMAT_A8;
+        case ARGB_4444: throw new RuntimeException("ARGB_444 unsupported");
+        case ARGB_8888: return CairoImage.FORMAT_ARGB32;
+        case RGB_565:   return CairoImage.FORMAT_RGB16_565;
+        default:        throw new RuntimeException("Unknown Skia bitmap config");
+        }
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/FloatPoint.java
@@ -0,0 +1,71 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.gfx.IntPoint;
+
+public class FloatPoint {
+    public final float x, y;
+
+    public FloatPoint(float inX, float inY) {
+        x = inX; y = inY;
+    }
+
+    public FloatPoint(IntPoint intPoint) {
+        x = intPoint.x; y = intPoint.y;
+    }
+
+    @Override
+    public String toString() {
+        return "(" + x + ", " + y + ")";
+    }
+
+    public FloatPoint add(FloatPoint other) {
+        return new FloatPoint(x + other.x, y + other.y);
+    }
+
+    public FloatPoint subtract(FloatPoint other) {
+        return new FloatPoint(x - other.x, y - other.y);
+    }
+
+    public FloatPoint scale(float factor) {
+        return new FloatPoint(x * factor, y * factor);
+    }
+}
+
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/FloatRect.java
@@ -0,0 +1,98 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.gfx.FloatPoint;
+import org.mozilla.gecko.gfx.IntRect;
+
+public class FloatRect {
+    public final float x, y, width, height;
+
+    public FloatRect(float inX, float inY, float inWidth, float inHeight) {
+        x = inX; y = inY; width = inWidth; height = inHeight;
+    }
+
+    public FloatRect(IntRect intRect) {
+        x = intRect.x; y = intRect.y; width = intRect.width; height = intRect.height;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof FloatRect))
+            return false;
+        FloatRect otherRect = (FloatRect)other;
+        return x == otherRect.x && y == otherRect.y &&
+            width == otherRect.width && height == otherRect.height;
+    }
+
+    public float getRight() { return x + width; }
+    public float getBottom() { return y + height; }
+
+    public FloatPoint getOrigin() { return new FloatPoint(x, y); }
+    public FloatPoint getCenter() { return new FloatPoint(x + width / 2, y + height / 2); }
+
+    /** Returns the intersection of this rectangle with another rectangle. */
+    public FloatRect intersect(FloatRect other) {
+        float left = Math.max(x, other.x);
+        float top = Math.max(y, other.y);
+        float right = Math.min(getRight(), other.getRight());
+        float bottom = Math.min(getBottom(), other.getBottom());
+        return new FloatRect(left, top, Math.max(right - left, 0), Math.max(bottom - top, 0));
+    }
+
+    /** Returns true if and only if the given rectangle is fully enclosed within this one. */
+    public boolean contains(FloatRect other) {
+        return x <= other.x && y <= other.y &&
+               getRight() >= other.getRight() &&
+               getBottom() >= other.getBottom();
+    }
+
+    /** Contracts a rectangle by the given number of units in each direction, from the center. */
+    public FloatRect contract(float lessWidth, float lessHeight) {
+        float halfWidth = width / 2.0f - lessWidth, halfHeight = height / 2.0f - lessHeight;
+        FloatPoint center = getCenter();
+        return new FloatRect(center.x - halfWidth, center.y - halfHeight,
+                             halfWidth * 2.0f, halfHeight * 2.0f);
+    }
+
+    /** Scales all four dimensions of this rectangle by the given factor. */
+    public FloatRect scaleAll(float factor) {
+        return new FloatRect(x * factor, y * factor, width * factor, height * factor);
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/GeckoSoftwareLayerClient.java
@@ -0,0 +1,273 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.gfx.CairoImage;
+import org.mozilla.gecko.gfx.FloatRect;
+import org.mozilla.gecko.gfx.IntRect;
+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.SingleTileLayer;
+import org.mozilla.gecko.ui.ViewportController;
+import org.mozilla.gecko.GeckoApp;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoEvent;
+import android.content.Context;
+import android.graphics.Point;
+import android.util.Log;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Semaphore;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * Transfers a software-rendered Gecko to an ImageLayer so that it can be rendered by our
+ * compositor.
+ *
+ * TODO: Throttle down Gecko's priority when we pan and zoom.
+ */
+public class GeckoSoftwareLayerClient extends LayerClient {
+    private Context mContext;
+    private int mWidth, mHeight, mFormat;
+    private ByteBuffer mBuffer;
+    private Semaphore mBufferSemaphore;
+    private SingleTileLayer mTileLayer;
+    private ViewportController mViewportController;
+
+    private FloatRect mGeckoVisibleRect;
+    /* The viewport rect that Gecko is currently displaying. */
+
+    private IntRect mJSPanningToRect;
+    /* The rect that we just told chrome JavaScript to pan to. */
+
+    private boolean mWaitingForJSPanZoom;
+    /* This will be set to true if we are waiting on the chrome JavaScript to finish panning or
+     * zooming before we can render. */
+
+    private CairoImage mCairoImage;
+
+    /* The initial page width and height that we use before a page is loaded. */
+    private static final int PAGE_WIDTH = 980;      /* Matches MobileSafari. */
+    private static final int PAGE_HEIGHT = 1500;
+
+    public GeckoSoftwareLayerClient(Context context) {
+        mContext = context;
+
+        mViewportController = new ViewportController(new IntSize(PAGE_WIDTH, PAGE_HEIGHT),
+                                                     new FloatRect(0, 0, 1, 1));
+
+        mWidth = LayerController.TILE_WIDTH;
+        mHeight = LayerController.TILE_HEIGHT;
+        mFormat = CairoImage.FORMAT_RGB16_565;
+
+        mBuffer = ByteBuffer.allocateDirect(mWidth * mHeight * 2);
+        mBufferSemaphore = new Semaphore(1);
+
+        mWaitingForJSPanZoom = false;
+
+        mCairoImage = new CairoImage() {
+            @Override
+            public ByteBuffer lockBuffer() {
+                try {
+                    mBufferSemaphore.acquire();
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                return mBuffer;
+            }
+            @Override
+            public void unlockBuffer() {
+                mBufferSemaphore.release();
+            }
+            @Override
+            public int getWidth() { return mWidth; }
+            @Override
+            public int getHeight() { return mHeight; }
+            @Override
+            public int getFormat() { return mFormat; }
+        };
+
+        mTileLayer = new SingleTileLayer();
+    }
+
+    /** Attaches the root layer to the layer controller so that Gecko appears. */
+    @Override
+    public void init() {
+        getLayerController().setRoot(mTileLayer);
+    }
+
+    public void beginDrawing() {
+        /* no-op */
+    }
+
+    /*
+     * 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) {
+        LayerController controller = getLayerController();
+        //controller.unzoom();  /* FIXME */
+        controller.notifyViewOfGeometryChange();
+
+        mViewportController.setVisibleRect(mGeckoVisibleRect);
+
+        if (mGeckoVisibleRect != null) {
+            FloatRect layerRect = mViewportController.untransformVisibleRect(mGeckoVisibleRect,
+                                                                             getPageSize());
+            mTileLayer.origin = layerRect.getOrigin();
+        }
+
+        repaint(new IntRect(x, y, width, height));
+    }
+
+    private void repaint(IntRect rect) {
+        mTileLayer.paintSubimage(mCairoImage, rect);
+    }
+
+    /** Called whenever the chrome JS finishes panning or zooming to some location. */
+    public void jsPanZoomCompleted(IntRect rect) {
+        mGeckoVisibleRect = new FloatRect(rect);
+        if (mWaitingForJSPanZoom)
+            render();
+    }
+
+    /**
+     * Acquires a lock on the back buffer and returns it, blocking until it's unlocked. This
+     * function is for Gecko to use.
+     */
+    public ByteBuffer lockBuffer() {
+        try {
+            mBufferSemaphore.acquire();
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        return mBuffer;
+    }
+
+    /**
+     * Releases the lock on the back buffer. After this call, it is forbidden for Gecko to touch
+     * the buffer. This function is, again, for Gecko to use.
+     */
+    public void unlockBuffer() {
+        mBufferSemaphore.release();
+    }
+
+    /** Called whenever the page changes size. */
+    public void setPageSize(IntSize pageSize) {
+        mViewportController.setPageSize(pageSize);
+        getLayerController().setPageSize(pageSize);
+    }
+
+    @Override
+    public void geometryChanged() {
+        mViewportController.setVisibleRect(getTransformedVisibleRect());
+        render();
+    }
+
+    @Override
+    public IntSize getPageSize() { return mViewportController.getPageSize(); }
+
+    @Override
+    public void render() {
+        LayerController layerController = getLayerController();
+        FloatRect visibleRect = layerController.getVisibleRect();
+        FloatRect tileRect = mViewportController.widenRect(visibleRect);
+        tileRect = mViewportController.clampRect(tileRect);
+
+        IntSize pageSize = layerController.getPageSize();
+        FloatRect viewportRect = mViewportController.transformVisibleRect(tileRect, pageSize);
+
+        /* Prevent null pointer exceptions at the start. */
+        if (mGeckoVisibleRect == null)
+            mGeckoVisibleRect = viewportRect;
+
+        if (!getLayerController().getRedrawHint())
+            return;
+
+        /* If Gecko's visible rect is the same as our visible rect, then we can actually kick off a
+         * draw event. */
+        if (mGeckoVisibleRect.equals(viewportRect)) {
+            mWaitingForJSPanZoom = false;
+            mJSPanningToRect = null;
+            GeckoAppShell.scheduleRedraw();
+            return;
+        }
+
+        /* Otherwise, we need to get Gecko's visible rect equal to our visible rect before we can
+         * safely draw. If we're just waiting for chrome JavaScript to catch up, we do nothing.
+         * This check avoids bombarding the chrome JavaScript with messages. */
+        IntRect panToRect = new IntRect((int)Math.round(viewportRect.x),
+                                        (int)Math.round(viewportRect.y),
+                                        LayerController.TILE_WIDTH,
+                                        LayerController.TILE_HEIGHT);
+
+        if (mWaitingForJSPanZoom && mJSPanningToRect != null &&
+                mJSPanningToRect.equals(panToRect)) {
+            return;
+        }
+
+        /* We send Gecko a message telling it to move its visible rect to the appropriate spot and
+         * set a flag to remind us to try the redraw again. */
+
+        GeckoAppShell.sendEventToGecko(new GeckoEvent("PanZoom:PanZoom",
+            "{\"x\": " + panToRect.x + ", \"y\": " + panToRect.y +
+            ", \"width\": " + panToRect.width + ", \"height\": " + panToRect.height +
+            ", \"zoomFactor\": " + getZoomFactor() + "}"));
+
+        mJSPanningToRect = panToRect;
+        mWaitingForJSPanZoom = true;
+    }
+
+    /* Returns the dimensions of the box in page coordinates that the user is viewing. */
+    private FloatRect getTransformedVisibleRect() {
+        LayerController layerController = getLayerController();
+        return mViewportController.transformVisibleRect(layerController.getVisibleRect(),
+                                                        layerController.getPageSize());
+    }
+
+    private float getZoomFactor() {
+        return 1.0f;    // FIXME
+        /*LayerController layerController = getLayerController();
+        return mViewportController.getZoomFactor(layerController.getVisibleRect(),
+                                                 layerController.getPageSize(),
+                                                 layerController.getScreenSize());*/
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/InputConnectionHandler.java
@@ -0,0 +1,15 @@
+package org.mozilla.gecko.gfx;
+
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.KeyEvent;
+
+public interface InputConnectionHandler
+{
+    InputConnection onCreateInputConnection(EditorInfo outAttrs);
+    boolean onKeyPreIme(int keyCode, KeyEvent event);
+    boolean onKeyDown(int keyCode, KeyEvent event);
+    boolean onKeyLongPress(int keyCode, KeyEvent event);
+    boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event);
+    boolean onKeyUp(int keyCode, KeyEvent event);
+}
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/IntPoint.java
@@ -0,0 +1,60 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+public class IntPoint {
+    public final int x, y;
+
+    public IntPoint(int inX, int inY) { x = inX; y = inY; }
+
+    @Override
+    public String toString() { return "(" + x + ", " + y + ")"; }
+
+    /** Returns the result of adding the given point to this point. */
+    public IntPoint add(IntPoint other) { return new IntPoint(x + other.x, y + other.y); }
+
+    /** Returns the result of subtracting the given point from this point. */
+    public IntPoint subtract(IntPoint other) { return new IntPoint(x - other.x, y - other.y); }
+
+    /** Returns the result of multiplying both components by the given scalar. */
+    public IntPoint scale(float scale) {
+        return new IntPoint((int)Math.round((float)x * scale), (int)Math.round((float)y * scale));
+    }
+}
+
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/IntRect.java
@@ -0,0 +1,94 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.gfx.IntPoint;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class IntRect implements Cloneable {
+    public final int x, y, width, height;
+
+    public IntRect(int inX, int inY, int inWidth, int inHeight) {
+        x = inX; y = inY; width = inWidth; height = inHeight;
+    }
+
+    public IntRect(JSONObject json) {
+        try {
+            x = json.getInt("x");
+            y = json.getInt("y");
+            width = json.getInt("width");
+            height = json.getInt("height");
+        } catch (JSONException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Object clone() { return new IntRect(x, y, width, height); }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof IntRect))
+            return false;
+        IntRect otherRect = (IntRect)other;
+        return x == otherRect.x && y == otherRect.y && width == otherRect.width &&
+            height == otherRect.height;
+    }
+
+    @Override
+    public String toString() { return "(" + x + "," + y + "," + width + "," + height + ")"; }
+
+    public IntPoint getOrigin() { return new IntPoint(x, y); }
+    public IntPoint getCenter() { return new IntPoint(x + width / 2, y + height / 2); }
+
+    public int getRight() { return x + width; }
+    public int getBottom() { return y + height; }
+
+    /** Contracts a rectangle by the given number of units in each direction, from the center. */
+    public IntRect contract(int lessWidth, int lessHeight) {
+        float halfWidth = width / 2.0f - lessWidth, halfHeight = height / 2.0f - lessHeight;
+        IntPoint center = getCenter();
+        return new IntRect((int)Math.round((float)center.x - halfWidth),
+                           (int)Math.round((float)center.y - halfHeight),
+                           (int)Math.round(halfWidth * 2.0f),
+                           (int)Math.round(halfHeight * 2.0f));
+    }
+}
+
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/IntSize.java
@@ -0,0 +1,65 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class IntSize {
+    public final int width, height;
+
+    public IntSize(int inWidth, int inHeight) { width = inWidth; height = inHeight; }
+
+    public IntSize(JSONObject json) {
+        try {
+            width = json.getInt("width");
+            height = json.getInt("height");
+        } catch (JSONException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String toString() { return "(" + width + "," + height + ")"; }
+
+    public IntSize scale(float factor) {
+        return new IntSize((int)Math.round(width * factor),
+                           (int)Math.round(height * factor));
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/Layer.java
@@ -0,0 +1,65 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.gfx.FloatPoint;
+import javax.microedition.khronos.opengles.GL10;
+
+public abstract class Layer {
+    public FloatPoint origin;
+
+    public Layer() {
+        origin = new FloatPoint(0.0f, 0.0f);
+    }
+
+    /** Draws the layer. Automatically applies the translation. */
+    public final void draw(GL10 gl) {
+        gl.glPushMatrix();
+        gl.glTranslatef(origin.x, origin.y, 0.0f);
+        onDraw(gl);
+        gl.glPopMatrix();
+    }
+
+    /**
+     * Subclasses implement this method to perform drawing.
+     *
+     * Invariant: The current matrix mode must be GL_MODELVIEW both before and after this call.
+     */
+    protected abstract void onDraw(GL10 gl);
+}
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/LayerClient.java
@@ -0,0 +1,64 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.gfx.IntRect;
+import org.mozilla.gecko.gfx.IntSize;
+import org.mozilla.gecko.gfx.LayerController;
+
+/**
+ * A layer client provides tiles and manages other information used by the layer controller.
+ */
+public abstract class LayerClient {
+    private LayerController mLayerController;
+
+    public abstract void geometryChanged();
+    public abstract IntSize getPageSize();
+
+    /** Called whenever the page changes size. */
+    public abstract void setPageSize(IntSize pageSize);
+
+    public abstract void init();
+    protected abstract void render();
+
+    public LayerController getLayerController() { return mLayerController; }
+    public void setLayerController(LayerController layerController) {
+        mLayerController = layerController;
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/LayerController.java
@@ -0,0 +1,288 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.gfx.FloatPoint;
+import org.mozilla.gecko.gfx.FloatRect;
+import org.mozilla.gecko.gfx.IntRect;
+import org.mozilla.gecko.gfx.IntSize;
+import org.mozilla.gecko.gfx.Layer;
+import org.mozilla.gecko.gfx.LayerClient;
+import org.mozilla.gecko.gfx.LayerView;
+import org.mozilla.gecko.ui.PanZoomController;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.GestureDetector;
+import android.view.ScaleGestureDetector;
+import android.view.View.OnTouchListener;
+import java.util.ArrayList;
+
+/**
+ * The layer controller manages a tile that represents the visible page. It does panning and
+ * zooming natively by delegating to a panning/zooming controller. Touch events can be dispatched
+ * to a higher-level view.
+ */
+public class LayerController {
+    private Layer mRootLayer;                   /* The root layer. */
+    private LayerView mView;                    /* The main rendering view. */
+    private Context mContext;                   /* The current context. */
+    private FloatRect mVisibleRect;             /* The current visible region. */
+    private IntSize mScreenSize;                /* The screen size of the viewport. */
+    private IntSize mPageSize;                  /* The current page size. */
+
+    private PanZoomController mPanZoomController;
+    /*
+     * The panning and zooming controller, which interprets pan and zoom gestures for us and
+     * updates our visible rect appropriately.
+     */
+
+    private OnTouchListener mOnTouchListener;   /* The touch listener. */
+    private LayerClient mLayerClient;           /* The layer client. */
+
+    public static final int TILE_WIDTH = 1024;
+    public static final int TILE_HEIGHT = 2048;
+    /* NB: These must be powers of two due to the OpenGL ES 1.x restriction on NPOT textures. */
+
+    private static final int DANGER_ZONE_X = 150;
+    private static final int DANGER_ZONE_Y = 300;
+    /* If the visible rect is within the danger zone (measured in pixels from each edge of a tile),
+     * we start aggressively redrawing to minimize checkerboarding. */
+
+    public LayerController(Context context, LayerClient layerClient) {
+        mContext = context;
+
+        mVisibleRect = new FloatRect(0.0f, 0.0f, 1.0f, 1.0f);
+        /* Gets filled in when the surface changes. */
+
+        mScreenSize = new IntSize(1, 1);
+
+        if (layerClient != null)
+            setLayerClient(layerClient);
+        else
+            mPageSize = new IntSize(LayerController.TILE_WIDTH, LayerController.TILE_HEIGHT);
+
+        mPanZoomController = new PanZoomController(this);
+        mView = new LayerView(context, this);
+    }
+
+    public void setRoot(Layer layer) { mRootLayer = layer; }
+
+    public void setLayerClient(LayerClient layerClient) {
+        mLayerClient = layerClient;
+        mPageSize = layerClient.getPageSize();
+        layerClient.setLayerController(this);
+    }
+
+    public Layer getRoot()              { return mRootLayer; }
+    public LayerView getView()          { return mView; }
+    public Context getContext()         { return mContext; }
+    public FloatRect getVisibleRect()   { return mVisibleRect; }
+    public IntSize getScreenSize()      { return mScreenSize; }
+    public IntSize getPageSize()        { return mPageSize; }
+
+    public Bitmap getCheckerboardPattern()  { return getDrawable("checkerboard"); }
+    public Bitmap getShadowPattern()        { return getDrawable("shadow"); }
+
+    public GestureDetector.OnGestureListener getGestureListener()                   { return mPanZoomController; }
+    public ScaleGestureDetector.OnScaleGestureListener getScaleGestureListener()    { return mPanZoomController; }
+
+    private Bitmap getDrawable(String name) {
+        Resources resources = mContext.getResources();
+        int resourceID = resources.getIdentifier(name, "drawable", mContext.getPackageName());
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inScaled = false;
+        return BitmapFactory.decodeResource(mContext.getResources(), resourceID, options);
+    }
+
+    /*
+     * Note that the zoom factor of the layer controller differs from the zoom factor of the layer
+     * client (i.e. the page).
+     */
+    public float getZoomFactor() { return (float)mScreenSize.width / mVisibleRect.width; }
+
+    /**
+     * The view calls this to indicate that the screen changed size.
+     *
+     * TODO: Refactor this to use an interface. Expose that interface only to the view and not
+     * to the layer client. That way, the layer client won't be tempted to call this, which might
+     * result in an infinite loop.
+     */
+    public void setScreenSize(int width, int height) {
+        float zoomFactor = getZoomFactor();     /* Must come first. */
+
+        mScreenSize = new IntSize(width, height);
+        setVisibleRect(mVisibleRect.x, mVisibleRect.y, width / zoomFactor, height / zoomFactor);
+
+        notifyLayerClientOfGeometryChange();
+    }
+
+    public void setNeedsDisplay() {
+        // TODO
+    }
+
+    public void scrollTo(float x, float y) {
+        setVisibleRect(x, y, mVisibleRect.width, mVisibleRect.height);
+    }
+
+    public void setVisibleRect(float x, float y, float width, float height) {
+        mVisibleRect = new FloatRect(x, y, width, height);
+        setNeedsDisplay();
+    }
+
+    /**
+     * Sets the zoom factor to 1, adjusting the visible rect accordingly. The Gecko layer client
+     * calls this function after a zoom has completed and Gecko is done rendering the new visible
+     * region.
+     */
+    public void unzoom() {
+        float zoomFactor = getZoomFactor();
+        mVisibleRect = new FloatRect(Math.round(mVisibleRect.x * zoomFactor),
+                                     Math.round(mVisibleRect.y * zoomFactor),
+                                     mScreenSize.width,
+                                     mScreenSize.height);
+        mPageSize = mPageSize.scale(zoomFactor);
+        setNeedsDisplay();
+    }
+
+    public void setPageSize(IntSize size) {
+        mPageSize = size.scale(getZoomFactor());
+        mView.notifyRendererOfPageSizeChange();
+    }
+
+    public boolean post(Runnable action) { return mView.post(action); }
+
+    public void setOnTouchListener(OnTouchListener onTouchListener) {
+        mOnTouchListener = onTouchListener;
+    }
+
+    /**
+     * The view as well as the controller itself use this method to notify the layer client that
+     * the geometry changed.
+     */
+    public void notifyLayerClientOfGeometryChange() {
+        if (mLayerClient != null)
+            mLayerClient.geometryChanged();
+    }
+
+    // Informs the view and the panning and zooming controller that the geometry changed.
+    public void notifyViewOfGeometryChange() {
+        mView.geometryChanged();
+        mPanZoomController.geometryChanged();
+    }
+
+    /**
+     * Returns true if this controller is fine with performing a redraw operation or false if it
+     * would prefer that the action didn't take place.
+     */
+    public boolean getRedrawHint() {
+        return aboutToCheckerboard();
+    }
+
+    private FloatRect getTileRect() {
+        return new FloatRect(mRootLayer.origin.x, mRootLayer.origin.y, TILE_WIDTH, TILE_HEIGHT);
+    }
+
+    // Returns true if a checkerboard is about to be visible.
+    private boolean aboutToCheckerboard() {
+        IntRect pageRect = new IntRect(0, 0, mPageSize.width, mPageSize.height);
+        IntRect adjustedPageRect = pageRect.contract(DANGER_ZONE_X, DANGER_ZONE_Y);
+        FloatRect visiblePageRect = mVisibleRect.intersect(new FloatRect(adjustedPageRect));
+        FloatRect adjustedTileRect = getTileRect().contract(DANGER_ZONE_X, DANGER_ZONE_Y);
+        return !adjustedTileRect.contains(visiblePageRect);
+    }
+
+    /** Returns the given rect, clamped to the boundaries of a tile. */
+    public FloatRect clampRect(FloatRect rect) {
+        float x = clamp(0, rect.x, mPageSize.width - LayerController.TILE_WIDTH);
+        float y = clamp(0, rect.y, mPageSize.height - LayerController.TILE_HEIGHT);
+        return new FloatRect(x, y, rect.width, rect.height);
+    }
+
+    private float clamp(float min, float value, float max) {
+        if (max < min)
+            return min;
+        return (value < min) ? min : (value > max) ? max : value;
+    }
+
+    // Returns the coordinates of a tile, scaled by the given factor, centered on the given rect.
+    private static FloatRect widenRect(FloatRect rect, float scaleFactor) {
+        FloatPoint center = rect.getCenter();
+        float halfTileWidth = TILE_WIDTH * scaleFactor / 2.0f;
+        float halfTileHeight = TILE_HEIGHT * scaleFactor / 2.0f;
+        return new FloatRect(center.x - halfTileWidth, center.y - halfTileHeight,
+                             halfTileWidth, halfTileHeight);
+    }
+
+    /** Returns the coordinates of a tile centered on the given rect. */
+    public static FloatRect widenRect(FloatRect rect) {
+        return widenRect(rect, 1.0f);
+    }
+
+    /**
+     * Converts a point from layer view coordinates to layer coordinates. In other words, given a
+     * point measured in pixels from the top left corner of the layer view, returns the point in
+     * pixels measured from the top left corner of the root layer, in the coordinate system of the
+     * layer itself. This method is used by the viewport controller as part of the process of
+     * translating touch events to Gecko's coordinate system.
+     */
+    public FloatPoint convertViewPointToLayerPoint(FloatPoint viewPoint) {
+        if (mRootLayer == null)
+            return null;
+
+        // Undo the transforms.
+        FloatPoint scaledPoint = viewPoint.scale(1.0f / getZoomFactor());
+        return mVisibleRect.getOrigin().add(scaledPoint).subtract(mRootLayer.origin);
+    }
+
+    /*
+     * Gesture detection. This is handled only at a high level in this class; we dispatch to the
+     * pan/zoom controller to do the dirty work.
+     */
+
+    public boolean onTouchEvent(MotionEvent event) {
+        boolean result = mPanZoomController.onTouchEvent(event);
+        if (mOnTouchListener != null)
+            result = mOnTouchListener.onTouch(mView, event) || result;
+        return result;
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/LayerRenderer.java
@@ -0,0 +1,202 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.gfx.BufferedCairoImage;
+import org.mozilla.gecko.gfx.FloatRect;
+import org.mozilla.gecko.gfx.IntRect;
+import org.mozilla.gecko.gfx.IntSize;
+import org.mozilla.gecko.gfx.LayerController;
+import org.mozilla.gecko.gfx.LayerView;
+import org.mozilla.gecko.gfx.NinePatchTileLayer;
+import org.mozilla.gecko.gfx.SingleTileLayer;
+import org.mozilla.gecko.gfx.TextureReaper;
+import org.mozilla.gecko.gfx.TextLayer;
+import org.mozilla.gecko.gfx.TileLayer;
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.WindowManager;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+import java.nio.ByteBuffer;
+
+/**
+ * The layer renderer implements the rendering logic for a layer view.
+ */
+public class LayerRenderer implements GLSurfaceView.Renderer {
+    private static final float BACKGROUND_COLOR_R = 0.81f;
+    private static final float BACKGROUND_COLOR_G = 0.81f;
+    private static final float BACKGROUND_COLOR_B = 0.81f;
+
+    private LayerView mView;
+    private SingleTileLayer mCheckerboardLayer;
+    private NinePatchTileLayer mShadowLayer;
+    private TextLayer mFPSLayer;
+
+    // FPS display
+    private long mFrameCountTimestamp;
+    private int mFrameCount;            // number of frames since last timestamp
+
+    public LayerRenderer(LayerView view) {
+        mView = view;
+
+        /* FIXME: Layers should not be directly connected to the layer controller. */
+        LayerController controller = view.getController();
+        mCheckerboardLayer = new SingleTileLayer(true);
+        mCheckerboardLayer.paintImage(new BufferedCairoImage(controller.getCheckerboardPattern()));
+        mShadowLayer = new NinePatchTileLayer(controller);
+        mShadowLayer.paintImage(new BufferedCairoImage(controller.getShadowPattern()));
+        mFPSLayer = new TextLayer(new IntSize(64, 32));
+        mFPSLayer.setText("-- FPS");
+
+        mFrameCountTimestamp = System.currentTimeMillis();
+        mFrameCount = 0;
+    }
+
+    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+        gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+        gl.glClearDepthf(1.0f);             /* FIXME: Is this needed? */
+        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
+        gl.glShadeModel(GL10.GL_SMOOTH);    /* FIXME: Is this needed? */
+        gl.glDisable(GL10.GL_DITHER);
+        gl.glEnable(GL10.GL_TEXTURE_2D);
+    }
+
+    public void onDrawFrame(GL10 gl) {
+        checkFPS();
+        TextureReaper.get().reap(gl);
+
+        LayerController controller = mView.getController();
+
+        /* Draw the background. */
+        gl.glClearColor(BACKGROUND_COLOR_R, BACKGROUND_COLOR_G, BACKGROUND_COLOR_B, 1.0f);
+        gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
+
+        /* Draw the drop shadow. */
+        setupPageTransform(gl);
+        mShadowLayer.draw(gl);
+
+        /* Draw the checkerboard. */
+        IntRect pageRect = clampToScreen(getPageRect());
+        IntSize screenSize = controller.getScreenSize();
+        gl.glEnable(GL10.GL_SCISSOR_TEST);
+        gl.glScissor(pageRect.x, screenSize.height - (pageRect.y + pageRect.height),
+                     pageRect.width, pageRect.height);
+
+        gl.glLoadIdentity();
+        mCheckerboardLayer.draw(gl);
+
+        /* Draw the layer the client added to us. */
+        setupPageTransform(gl);
+
+        Layer rootLayer = controller.getRoot();
+        if (rootLayer != null)
+            rootLayer.draw(gl);
+
+        gl.glDisable(GL10.GL_SCISSOR_TEST);
+
+        /* Draw the FPS. */
+        gl.glLoadIdentity();
+        gl.glEnable(GL10.GL_BLEND);
+        mFPSLayer.draw(gl);
+        gl.glDisable(GL10.GL_BLEND);
+    }
+
+    public void pageSizeChanged() {
+        mShadowLayer.recreateVertexBuffers();
+    }
+
+    private void setupPageTransform(GL10 gl) {
+        LayerController controller = mView.getController();
+        FloatRect visibleRect = controller.getVisibleRect();
+        float zoomFactor = controller.getZoomFactor();
+
+        gl.glLoadIdentity();
+        gl.glScalef(zoomFactor, zoomFactor, 1.0f);
+        gl.glTranslatef(-visibleRect.x, -visibleRect.y, 0.0f);
+    }
+
+    private IntRect getPageRect() {
+        LayerController controller = mView.getController();
+        float zoomFactor = controller.getZoomFactor();
+        FloatRect visibleRect = controller.getVisibleRect();
+        IntSize pageSize = controller.getPageSize();
+
+        return new IntRect((int)Math.round(-zoomFactor * visibleRect.x),
+                           (int)Math.round(-zoomFactor * visibleRect.y),
+                           (int)Math.round(zoomFactor * pageSize.width),
+                           (int)Math.round(zoomFactor * pageSize.height));
+    }
+
+    private IntRect clampToScreen(IntRect rect) {
+        LayerController controller = mView.getController();
+        IntSize screenSize = controller.getScreenSize();
+
+        int left = Math.max(0, rect.x);
+        int top = Math.max(0, rect.y);
+        int right = Math.min(screenSize.width, rect.getRight());
+        int bottom = Math.min(screenSize.height, rect.getBottom());
+        return new IntRect(left, top, right - left, bottom - top);
+    }
+
+    public void onSurfaceChanged(GL10 gl, int width, int height) {
+        gl.glViewport(0, 0, width, height);
+        gl.glMatrixMode(GL10.GL_PROJECTION);
+        gl.glLoadIdentity();
+        gl.glOrthof(0.0f, (float)width, (float)height, 0.0f, -10.0f, 10.0f);
+        gl.glMatrixMode(GL10.GL_MODELVIEW);
+        gl.glLoadIdentity();
+
+        mView.setScreenSize(width, height);
+
+        /* TODO: Throw away tile images? */
+    }
+
+    private void checkFPS() {
+        if (System.currentTimeMillis() >= mFrameCountTimestamp + 1000) {
+            mFrameCountTimestamp = System.currentTimeMillis();
+            mFPSLayer.setText(mFrameCount + " FPS");
+            mFrameCount = 0;
+        } else {
+            mFrameCount++;
+        }
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/LayerView.java
@@ -0,0 +1,148 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.gfx.InputConnectionHandler;
+import org.mozilla.gecko.gfx.LayerController;
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.view.GestureDetector;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.ScaleGestureDetector;
+
+/**
+ * A view rendered by the layer compositor.
+ *
+ * This view delegates to LayerRenderer to actually do the drawing. Its role is largely that of a
+ * mediator between the LayerRenderer and the LayerController.
+ */
+public class LayerView extends GLSurfaceView {
+    private Context mContext;
+    private LayerController mController;
+    private InputConnectionHandler mInputConnectionHandler;
+    private LayerRenderer mRenderer;
+    private GestureDetector mGestureDetector;
+    private ScaleGestureDetector mScaleGestureDetector;
+
+    public LayerView(Context context, LayerController controller) {
+        super(context);
+
+        mContext = context;
+        mController = controller;
+        mRenderer = new LayerRenderer(this);
+        setRenderer(mRenderer);
+        mGestureDetector = new GestureDetector(context, controller.getGestureListener());
+        mScaleGestureDetector = new ScaleGestureDetector(context, controller.getScaleGestureListener());
+        mInputConnectionHandler = null;
+
+        setFocusable(true);
+        setFocusableInTouchMode(true);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mGestureDetector.onTouchEvent(event))
+            return true;
+        mScaleGestureDetector.onTouchEvent(event);
+        if (mScaleGestureDetector.isInProgress())
+            return true;
+        return mController.onTouchEvent(event);
+    }
+
+    public LayerController getController() { return mController; }
+    public void geometryChanged() { /* TODO: Schedule a redraw. */ }
+
+    public void notifyRendererOfPageSizeChange() {
+        mRenderer.pageSizeChanged();
+    }
+
+    /** The LayerRenderer calls this to indicate that the window has changed size. */
+    public void setScreenSize(int width, int height) {
+        mController.setScreenSize(width, height);
+    }
+
+    public void setInputConnectionHandler(InputConnectionHandler handler) {
+        mInputConnectionHandler = handler;
+    }
+
+    @Override
+    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+        if (mInputConnectionHandler != null)
+            return mInputConnectionHandler.onCreateInputConnection(outAttrs);
+        return null;
+    }
+
+    @Override
+    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+        if (mInputConnectionHandler != null)
+            return mInputConnectionHandler.onKeyPreIme(keyCode, event);
+        return false;
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (mInputConnectionHandler != null)
+            return mInputConnectionHandler.onKeyDown(keyCode, event);
+        return false;
+    }
+
+    @Override
+    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+        if (mInputConnectionHandler != null)
+            return mInputConnectionHandler.onKeyLongPress(keyCode, event);
+        return false;
+    }
+
+    @Override
+    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+        if (mInputConnectionHandler != null)
+            return mInputConnectionHandler.onKeyMultiple(keyCode, repeatCount, event);
+        return false;
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (mInputConnectionHandler != null)
+            return mInputConnectionHandler.onKeyUp(keyCode, event);
+        return false;
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/NinePatchTileLayer.java
@@ -0,0 +1,176 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.gfx.IntSize;
+import org.mozilla.gecko.gfx.LayerController;
+import org.mozilla.gecko.gfx.TileLayer;
+import javax.microedition.khronos.opengles.GL10;
+import java.nio.FloatBuffer;
+
+/**
+ * Encapsulates the logic needed to draw a nine-patch bitmap using OpenGL ES.
+ *
+ * For more information on nine-patch bitmaps, see the following document:
+ *   http://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch
+ */
+public class NinePatchTileLayer extends TileLayer {
+    private FloatBuffer mSideTexCoordBuffer, mSideVertexBuffer;
+    private FloatBuffer mTopTexCoordBuffer, mTopVertexBuffer;
+    private LayerController mLayerController;
+
+    private static final int PATCH_SIZE = 16;
+    private static final int TEXTURE_SIZE = 48;
+
+    /*
+     * We divide the nine-patch bitmap up into the "sides" and the "tops":
+     *
+     *        Top
+     *         |
+     *         v
+     *   +---+---+---+
+     *   |   |   |   |
+     *   |   +---+   |
+     *   |   |XXX|   | <-- Side
+     *   |   +---+   |
+     *   |   |   |   |
+     *   +---+---+---+
+     */
+
+    private static final float[] SIDE_TEX_COORDS = {
+        0.0f,   0.0f,
+        0.25f,  0.0f,
+        0.0f,   0.25f,
+        0.25f,  0.25f,
+        0.0f,   0.50f,
+        0.25f,  0.50f,
+        0.0f,   0.75f,
+        0.25f,  0.75f,
+    };
+
+    private static final float[] TOP_TEX_COORDS = {
+        0.25f,  0.0f,
+        0.50f,  0.0f,
+        0.25f,  0.25f,
+        0.50f,  0.25f,
+    };
+
+    public NinePatchTileLayer(LayerController layerController) {
+        super(false);
+
+        mLayerController = layerController;
+
+        mSideTexCoordBuffer = createBuffer(SIDE_TEX_COORDS);
+        mTopTexCoordBuffer = createBuffer(TOP_TEX_COORDS);
+
+        recreateVertexBuffers();
+    }
+
+    public void recreateVertexBuffers() {
+        IntSize pageSize = mLayerController.getPageSize();
+
+        float[] sideVertices = {
+            -PATCH_SIZE,    -PATCH_SIZE,                    0.0f,
+            0.0f,           -PATCH_SIZE,                    0.0f,
+            -PATCH_SIZE,    0.0f,                           0.0f,
+            0.0f,           0.0f,                           0.0f,
+            -PATCH_SIZE,    pageSize.height,                0.0f,
+            0.0f,           pageSize.height,                0.0f,
+            -PATCH_SIZE,    PATCH_SIZE + pageSize.height,   0.0f,
+            0.0f,           PATCH_SIZE + pageSize.height,   0.0f
+        };
+
+        float[] topVertices = {
+            0.0f,           -PATCH_SIZE,    0.0f,
+            pageSize.width, -PATCH_SIZE,    0.0f,
+            0.0f,           0.0f,           0.0f,
+            pageSize.width, 0.0f,           0.0f
+        };
+
+        mSideVertexBuffer = createBuffer(sideVertices);
+        mTopVertexBuffer = createBuffer(topVertices);
+    }
+
+    @Override
+    protected void onTileDraw(GL10 gl) {
+        IntSize pageSize = mLayerController.getPageSize();
+
+        gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
+        gl.glEnable(GL10.GL_BLEND);
+
+        gl.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID());
+
+        /* Left side */
+        drawTriangles(gl, mSideVertexBuffer, mSideTexCoordBuffer, 8);
+
+        /* Top */
+        drawTriangles(gl, mTopVertexBuffer, mTopTexCoordBuffer, 4);
+
+        /* Right side */
+        gl.glMatrixMode(GL10.GL_MODELVIEW);
+        gl.glPushMatrix();
+        gl.glTranslatef(pageSize.width + PATCH_SIZE, 0.0f, 0.0f);
+        gl.glMatrixMode(GL10.GL_TEXTURE);
+        gl.glPushMatrix();
+        gl.glTranslatef(0.50f, 0.0f, 0.0f);
+
+        drawTriangles(gl, mSideVertexBuffer, mSideTexCoordBuffer, 8);
+
+        gl.glMatrixMode(GL10.GL_TEXTURE);
+        gl.glPopMatrix();
+        gl.glMatrixMode(GL10.GL_MODELVIEW);     /* Not strictly necessary, but here for clarity. */
+        gl.glPopMatrix();
+
+        /* Bottom */
+        gl.glMatrixMode(GL10.GL_MODELVIEW);
+        gl.glPushMatrix();
+        gl.glTranslatef(0.0f, pageSize.height + PATCH_SIZE, 0.0f);
+        gl.glMatrixMode(GL10.GL_TEXTURE);
+        gl.glPushMatrix();
+        gl.glTranslatef(0.0f, 0.50f, 0.0f);
+
+        drawTriangles(gl, mTopVertexBuffer, mTopTexCoordBuffer, 4);
+
+        gl.glMatrixMode(GL10.GL_TEXTURE);
+        gl.glPopMatrix();
+        gl.glMatrixMode(GL10.GL_MODELVIEW);
+        gl.glPopMatrix();
+
+        gl.glDisable(GL10.GL_BLEND);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/PlaceholderLayerClient.java
@@ -0,0 +1,102 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.gfx.BufferedCairoImage;
+import org.mozilla.gecko.gfx.CairoUtils;
+import org.mozilla.gecko.gfx.IntSize;
+import org.mozilla.gecko.gfx.LayerClient;
+import org.mozilla.gecko.gfx.SingleTileLayer;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Environment;
+import android.util.Log;
+import java.io.File;
+import java.nio.ByteBuffer;
+
+/**
+ * A stand-in for Gecko that renders cached content of the previous page. We use this until Gecko
+ * is up, then we hand off control to it.
+ */
+public class PlaceholderLayerClient extends LayerClient {
+    private Context mContext;
+    private IntSize mPageSize;
+    private int mWidth, mHeight, mFormat;
+    private ByteBuffer mBuffer;
+
+    private PlaceholderLayerClient(Context context, Bitmap bitmap) {
+        mContext = context;
+        mPageSize = new IntSize(995, 1250); /* TODO */
+
+        mWidth = bitmap.getWidth();
+        mHeight = bitmap.getHeight();
+        mFormat = CairoUtils.bitmapConfigToCairoFormat(bitmap.getConfig());
+        mBuffer = ByteBuffer.allocateDirect(mWidth * mHeight * 4);
+        bitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
+    }
+
+    public static PlaceholderLayerClient createInstance(Context context) {
+        File path = new File(Environment.getExternalStorageDirectory(), "lastScreen.png");
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inScaled = false;
+        Bitmap bitmap = BitmapFactory.decodeFile("" + path, options);
+        if (bitmap == null)
+            return null;
+
+        return new PlaceholderLayerClient(context, bitmap);
+    }
+
+    public void init() {
+        SingleTileLayer tileLayer = new SingleTileLayer();
+        getLayerController().setRoot(tileLayer);
+        tileLayer.paintImage(new BufferedCairoImage(mBuffer, mWidth, mHeight, mFormat));
+    }
+
+    @Override
+    public void geometryChanged() { /* no-op */ }
+    @Override
+    public IntSize getPageSize() { return mPageSize; }
+    @Override
+    public void render() { /* no-op */ }
+
+    /** Called whenever the page changes size. */
+    @Override
+    public void setPageSize(IntSize pageSize) { mPageSize = pageSize; }
+}
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/SingleTileLayer.java
@@ -0,0 +1,107 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.gfx.CairoImage;
+import org.mozilla.gecko.gfx.CairoUtils;
+import org.mozilla.gecko.gfx.IntSize;
+import org.mozilla.gecko.gfx.LayerController;
+import org.mozilla.gecko.gfx.TileLayer;
+import android.util.Log;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * Encapsulates the logic needed to draw a single textured tile.
+ */
+public class SingleTileLayer extends TileLayer {
+    private FloatBuffer mTexCoordBuffer, mVertexBuffer;
+
+    private static final float[] VERTICES = {
+        0.0f, 0.0f, 0.0f,
+        1.0f, 0.0f, 0.0f,
+        0.0f, 1.0f, 0.0f,
+        1.0f, 1.0f, 0.0f
+    };
+
+    private static final float[] TEX_COORDS = {
+        0.0f, 0.0f,
+        1.0f, 0.0f,
+        0.0f, 1.0f,
+        1.0f, 1.0f
+    };
+
+    public SingleTileLayer() { this(false); }
+
+    public SingleTileLayer(boolean repeat) {
+        super(repeat);
+
+        mVertexBuffer = createBuffer(VERTICES);
+        mTexCoordBuffer = createBuffer(TEX_COORDS);
+    }
+
+    @Override
+    protected void onTileDraw(GL10 gl) {
+        IntSize size = getSize();
+
+        if (repeats()) {
+            gl.glMatrixMode(GL10.GL_TEXTURE);
+            gl.glPushMatrix();
+            gl.glScalef(LayerController.TILE_WIDTH / size.width,
+                        LayerController.TILE_HEIGHT / size.height,
+                        1.0f);
+
+            gl.glMatrixMode(GL10.GL_MODELVIEW);
+            gl.glScalef(LayerController.TILE_WIDTH, LayerController.TILE_HEIGHT, 1.0f);
+        } else {
+            gl.glScalef(size.width, size.height, 1.0f);
+        }
+
+        gl.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID());
+        drawTriangles(gl, mVertexBuffer, mTexCoordBuffer, 4);
+
+        if (repeats()) {
+            gl.glMatrixMode(GL10.GL_TEXTURE);
+            gl.glPopMatrix();
+            gl.glMatrixMode(GL10.GL_MODELVIEW);
+        }
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/TextLayer.java
@@ -0,0 +1,99 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.gfx.BufferedCairoImage;
+import org.mozilla.gecko.gfx.CairoImage;
+import org.mozilla.gecko.gfx.IntSize;
+import org.mozilla.gecko.gfx.SingleTileLayer;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.util.Log;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+
+/**
+ * Draws text on a layer. This is used for the frame rate meter.
+ */
+public class TextLayer extends SingleTileLayer {
+    private ByteBuffer mBuffer;
+    private BufferedCairoImage mImage;
+    private IntSize mSize;
+    private String mText;
+
+    public TextLayer(IntSize size) {
+        super(false);
+
+        mBuffer = ByteBuffer.allocateDirect(size.width * size.height * 4);
+        mSize = size;
+        mImage = new BufferedCairoImage(mBuffer, size.width, size.height,
+                                        CairoImage.FORMAT_ARGB32);
+        mText = "";
+    }
+
+    public void setText(String text) {
+        mText = text;
+        renderText();
+        paintImage(mImage);
+    }
+
+    private void renderText() {
+        Bitmap bitmap = Bitmap.createBitmap(mSize.width, mSize.height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+
+        Paint textPaint = new Paint();
+        textPaint.setAntiAlias(true);
+        textPaint.setColor(Color.WHITE);
+        textPaint.setFakeBoldText(true);
+        textPaint.setTextSize(18.0f);
+        textPaint.setTypeface(Typeface.DEFAULT_BOLD);
+        float width = textPaint.measureText(mText) + 18.0f;
+
+        Paint backgroundPaint = new Paint();
+        backgroundPaint.setColor(Color.argb(127, 0, 0, 0));
+        canvas.drawRect(0.0f, 0.0f, width, 18.0f + 6.0f, backgroundPaint);
+
+        canvas.drawText(mText, 6.0f, 18.0f, textPaint);
+
+        bitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/TextureReaper.java
@@ -0,0 +1,71 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import javax.microedition.khronos.opengles.GL10;
+import java.util.ArrayList;
+
+/** Manages a list of dead tiles, so we don't leak resources. */
+public class TextureReaper {
+    private static TextureReaper sSharedInstance;
+    private ArrayList<Integer> mDeadTextureIDs;
+
+    private TextureReaper() { mDeadTextureIDs = new ArrayList<Integer>(); }
+
+    public static TextureReaper get() {
+        if (sSharedInstance == null)
+            sSharedInstance = new TextureReaper();
+        return sSharedInstance;
+    }
+
+    public void add(int[] textureIDs) {
+        for (int textureID : textureIDs)
+            mDeadTextureIDs.add(textureID);
+    }
+
+    public void reap(GL10 gl) {
+        int[] deadTextureIDs = new int[mDeadTextureIDs.size()];
+        for (int i = 0; i < deadTextureIDs.length; i++)
+            deadTextureIDs[i] = mDeadTextureIDs.get(i);
+        mDeadTextureIDs.clear();
+
+        gl.glDeleteTextures(deadTextureIDs.length, deadTextureIDs, 0);
+    }
+}
+
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/gfx/TileLayer.java
@@ -0,0 +1,189 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.gfx.CairoImage;
+import org.mozilla.gecko.gfx.IntSize;
+import org.mozilla.gecko.gfx.Layer;
+import org.mozilla.gecko.gfx.TextureReaper;
+import android.util.Log;
+import javax.microedition.khronos.opengles.GL10;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+/**
+ * Base class for tile layers, which encapsulate the logic needed to draw textured tiles in OpenGL
+ * ES.
+ */
+public abstract class TileLayer extends Layer {
+    private CairoImage mImage;
+    private boolean mRepeat;
+    private IntSize mSize;
+    private int[] mTextureIDs;
+
+    private IntRect mTextureUploadRect;
+    /* The rect that needs to be uploaded to the texture. */
+
+    public TileLayer(boolean repeat) {
+        super();
+        mRepeat = repeat;
+        mTextureUploadRect = null;
+    }
+
+    public IntSize getSize() { return mSize; }
+
+    protected boolean repeats() { return mRepeat; }
+    protected int getTextureID() { return mTextureIDs[0]; }
+
+    @Override
+    protected void finalize() throws Throwable {
+        if (mTextureIDs != null)
+            TextureReaper.get().add(mTextureIDs);
+    }
+
+    /**
+     * Subclasses implement this method to perform tile drawing.
+     *
+     * Invariant: The current matrix mode must be GL_MODELVIEW both before and after this call.
+     */
+    protected abstract void onTileDraw(GL10 gl);
+
+    @Override
+    protected void onDraw(GL10 gl) {
+        if (mImage == null)
+            return;
+        if (mTextureUploadRect != null)
+            uploadTexture(gl);
+
+        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
+        gl.glPushMatrix();
+
+        onTileDraw(gl);
+
+        gl.glPopMatrix();
+        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
+    }
+
+    public void paintSubimage(CairoImage image, IntRect rect) {
+        mImage = image;
+        mTextureUploadRect = rect;
+
+        /*
+         * Assert that the image has a power-of-two size. OpenGL ES < 2.0 doesn't support NPOT
+         * textures and OpenGL ES doesn't seem to let us efficiently slice up a NPOT bitmap.
+         */
+        int width = mImage.getWidth(), height = mImage.getHeight();
+        assert (width & (width - 1)) == 0;
+        assert (height & (height - 1)) == 0;
+    }
+
+    public void paintImage(CairoImage image) {
+        paintSubimage(image, new IntRect(0, 0, image.getWidth(), image.getHeight()));
+    }
+
+    private void uploadTexture(GL10 gl) {
+        boolean newTexture = mTextureIDs == null;
+        if (newTexture) {
+            mTextureIDs = new int[1];
+            gl.glGenTextures(mTextureIDs.length, mTextureIDs, 0);
+        }
+
+        int width = mImage.getWidth(), height = mImage.getHeight();
+        mSize = new IntSize(width, height);
+
+        int cairoFormat = mImage.getFormat();
+        int internalFormat = CairoUtils.cairoFormatToGLInternalFormat(cairoFormat);
+        int format = CairoUtils.cairoFormatToGLFormat(cairoFormat);
+        int type = CairoUtils.cairoFormatToGLType(cairoFormat);
+
+        gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIDs[0]);
+        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
+        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
+
+        int repeatMode = mRepeat ? GL10.GL_REPEAT : GL10.GL_CLAMP_TO_EDGE;
+        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, repeatMode);
+        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, repeatMode);
+
+        ByteBuffer buffer = mImage.lockBuffer();
+        try {
+            if (newTexture) {
+                /* The texture is new; we have to upload the whole image. */
+                gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, internalFormat, mSize.width, mSize.height, 0,
+                                format, type, buffer);
+            } else {
+                /*
+                 * The texture is already existing, so upload only the changed rect. We have to
+                 * widen to the full width of the texture because we can't count on the device
+                 * having support for GL_EXT_unpack_subimage, and going line-by-line is too slow.
+                 */
+                Buffer viewBuffer = buffer.slice();
+                int bpp = CairoUtils.bitsPerPixelForCairoFormat(cairoFormat) / 8;
+                viewBuffer.position(mTextureUploadRect.y * width * bpp);
+
+                gl.glTexSubImage2D(gl.GL_TEXTURE_2D,
+                                   0, 0, mTextureUploadRect.y, width, mTextureUploadRect.height,
+                                   format, type, viewBuffer);
+            }
+        } finally {
+            mImage.unlockBuffer();
+        }
+
+        mTextureUploadRect = null;
+    }
+
+    protected static FloatBuffer createBuffer(float[] values) {
+        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(values.length * 4);
+        byteBuffer.order(ByteOrder.nativeOrder());
+
+        FloatBuffer floatBuffer = byteBuffer.asFloatBuffer();
+        floatBuffer.put(values);
+        floatBuffer.position(0);
+        return floatBuffer;
+    }
+
+    protected static void drawTriangles(GL10 gl, FloatBuffer vertexBuffer,
+                                        FloatBuffer texCoordBuffer, int count) {
+        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
+        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, texCoordBuffer);
+        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, count);
+    }
+}
+
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..57cfbe80fdcfaa6866ade23dd363dbb3f0f8c0c2
GIT binary patch
literal 199
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|I14-?iy0WW
zg+Z8+Vb&Z8pdfpRr>`sfZ7yL^X}!xfj385#N?apKobz*YQ}ap~oQqNuOHxx5$}>wc
z6x=<10~GS}6cQDD6O-Fllsf}eYkRslhG?8mwkdwb!)%t2lhYGAr?b(;U{#~Ufgk_>
l*RK&uKR-{FSM^sW1B266js})SVOgLy22WQ%mvv4FO#sOXHcbEk
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3ce69155c6b5181f33caf4be2083a87b74554135
GIT binary patch
literal 881
zc$@)o1CIQOP)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF00001b5ch_0Itp)
z=>Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RW3Kakg5N8=M?*IS;14%?dRCwC$
zTHB7RFbs59V7GsDK7IeH+fvAR!P3a<Txc$=T90HkP)eDJoj49ehWCd11NZP}3NXC8
z2aFYZ+a2%<|JS_2J$Y?TJC|Ti`&0p3;Mj?__8B6sefj`#f2IH~Lfj?J;csNmdWE!)
z2;t8VAVz)&v_nE@B-yq=yNd|p0g;gG0Hz(M1lYg^Xp<1awGM$XgET25**?JJcUlN+
z!d5UAZG&r3geGE)nS)-4^azIMoswfPAte0(<8#_?@L7DX5Rmaa2;$Rj*yisr97wo5
zlU;UW?^%?Pac7SKPIv$loOi-A5q-k(1RkLYz`f1n??iM{#tfgB8k-5AO>5HindsL}
zo<CWLS$F}7-oFEJd0l9haKbBm?^%E!auhv(0sGo*hHyXuR_OH~xW<Wim$L)4oDFiv
zfI-AQ_x#0s|6P=bIQLHw=fQA;|L;Bv#6n;c9#D!4%m@qo&kA19OoSnG5Og90rpf|*
zia|7%P}Qnq{1U(=$jdNbFDM1hBBMyGTtbLu9^evF;RGXoDQ0EhY)ZN$1Y{;SRd|Le
zkWzOMVK}fF3}uC-kS;$&mer@!1YN=-YCJ=T^ywk+^2iauC)V_(*pxyDDaYLfh?TLn
z!1DDOVCLr|Vw~auT94y;eyQ*bi@2xs2!DnLXuZEFi<*0dXW;uR!V|iOfY0pxhgi#(
z;syddl#pmD#?cVKV#bY#t`(l)y5DO75mH{zv>xCiuD?Q2W2}@2UBL?;k+Go3V3^wV
z8AS1OhU(9yupsiYlSjJ=F<1~U3Zz{tLb9NVDJy@!LRcIriQRS~<T$XkBAgOH79FrA
z$F=)Kl(CT5vqjN6oJ&oI-OA$lXDEUkAi4LSEsH-ydyQm=U2B3~2?H%OFe$3BG16@c
zjLiZrlRV}IrCVd0V$HY8b9RSYZ;pnJL+=uF7a%k}zQp#py&zE`St5GjS7cJ&K=cML
zNu{nYeNC?EVyCtME$(4&iW&?CgTY`h7z_r3!C){L4E^vQ?wrv)p5XL$00000NkvXX
Hu0mjflGlVf
new file mode 100644
--- /dev/null
+++ b/embedding/android/ui/PanZoomController.java
@@ -0,0 +1,577 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.ui;
+
+import org.json.JSONObject;
+import org.mozilla.gecko.gfx.FloatPoint;
+import org.mozilla.gecko.gfx.FloatRect;
+import org.mozilla.gecko.gfx.IntPoint;
+import org.mozilla.gecko.gfx.IntRect;
+import org.mozilla.gecko.gfx.IntSize;
+import org.mozilla.gecko.gfx.LayerController;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoEvent;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/*
+ * Handles the kinetic scrolling and zooming physics for a layer controller.
+ *
+ * Many ideas are from Joe Hewitt's Scrollability:
+ *   https://github.com/joehewitt/scrollability/
+ */
+public class PanZoomController
+    extends GestureDetector.SimpleOnGestureListener
+    implements ScaleGestureDetector.OnScaleGestureListener
+{
+    private static final String LOG_NAME = "PanZoomController";
+
+    private LayerController mController;
+
+    private static final float FRICTION = 0.97f;
+    // Animation stops if the velocity is below this value.
+    private static final float STOPPED_THRESHOLD = 4.0f;
+    // The percentage of the surface which can be overscrolled before it must snap back.
+    private static final float SNAP_LIMIT = 0.75f;
+    // The rate of deceleration when the surface has overscrolled.
+    private static final float OVERSCROLL_DECEL_RATE = 0.04f;
+    // The duration of animation when bouncing back.
+    private static final int SNAP_TIME = 150;
+    // The number of subdivisions we should consider when plotting the ease-out transition. Higher
+    // values make the animation more accurate, but slower to plot.
+    private static final int SUBDIVISION_COUNT = 1000;
+
+    private long mLastTimestamp;
+    private Timer mFlingTimer;
+    private Axis mX, mY;
+    /* The span at the first zoom event (in unzoomed page coordinates). */
+    private float mInitialZoomSpan;
+    /* The zoom focus at the first zoom event (in unzoomed page coordinates). */
+    private FloatPoint mInitialZoomFocus;
+
+    private enum PanZoomState {
+        NOTHING,        /* no touch-start events received */
+        FLING,          /* all touches removed, but we're still scrolling page */
+        TOUCHING,       /* one touch-start event received */
+        PANNING,        /* touch-start followed by move */
+        PANNING_HOLD,   /* in panning, but haven't moved.
+                         * similar to TOUCHING but after starting a pan */
+        PINCHING,       /* nth touch-start, where n > 1. this mode allows pan and zoom */
+    }
+
+    private PanZoomState mState;
+
+    public PanZoomController(LayerController controller) {
+        mController = controller;
+        mX = new Axis(); mY = new Axis();
+        mState = PanZoomState.NOTHING;
+
+        populatePositionAndLength();
+    }
+
+    public boolean onTouchEvent(MotionEvent event) {
+        switch (event.getActionMasked()) {
+        case MotionEvent.ACTION_DOWN:   return onTouchStart(event);
+        case MotionEvent.ACTION_MOVE:   return onTouchMove(event);
+        case MotionEvent.ACTION_UP:     return onTouchEnd(event);
+        case MotionEvent.ACTION_CANCEL: return onTouchCancel(event);
+        default:                        return false;
+        }
+    }
+
+    public void geometryChanged() {
+        populatePositionAndLength();
+    }
+
+    /*
+     * Panning/scrolling
+     */
+
+    private boolean onTouchStart(MotionEvent event) {
+        switch (mState) {
+        case FLING:
+            if (mFlingTimer != null) {
+                mFlingTimer.cancel();
+                mFlingTimer = null;
+            }
+            // fall through
+        case NOTHING:
+            mState = PanZoomState.TOUCHING;
+            mX.touchPos = event.getX(0);
+            mY.touchPos = event.getY(0);
+            return false;
+        case TOUCHING:
+        case PANNING:
+        case PANNING_HOLD:
+        case PINCHING:
+            mState = PanZoomState.PINCHING;
+            return false;
+        }
+        Log.e(LOG_NAME, "Unhandled case " + mState + " in onTouchStart");
+        return false;
+    }
+
+    private boolean onTouchMove(MotionEvent event) {
+        switch (mState) {
+        case NOTHING:
+        case FLING:
+            // should never happen
+            Log.e(LOG_NAME, "Received impossible touch move while in " + mState);
+            return false;
+        case TOUCHING:
+            mLastTimestamp = System.currentTimeMillis();
+            // fall through
+        case PANNING_HOLD:
+            mState = PanZoomState.PANNING;
+            // fall through
+        case PANNING:
+            track(event, System.currentTimeMillis());
+            return true;
+        case PINCHING:
+            // scale gesture listener will handle this
+            return false;
+        }
+        Log.e(LOG_NAME, "Unhandled case " + mState + " in onTouchMove");
+        return false;
+    }
+
+    private boolean onTouchEnd(MotionEvent event) {
+        switch (mState) {
+        case NOTHING:
+        case FLING:
+            // should never happen
+            Log.e(LOG_NAME, "Received impossible touch end while in " + mState);
+            return false;
+        case TOUCHING:
+            mState = PanZoomState.NOTHING;
+            // TODO: send click to gecko
+            return false;
+        case PANNING:
+        case PANNING_HOLD:
+            mState = PanZoomState.FLING;
+            fling(System.currentTimeMillis());
+            return true;
+        case PINCHING:
+            int points = event.getPointerCount();
+            if (points == 1) {
+                // last touch up
+                mState = PanZoomState.NOTHING;
+            } else if (points == 2) {
+                int pointRemovedIndex = event.getActionIndex();
+                int pointRemainingIndex = 1 - pointRemovedIndex; // kind of a hack
+                mState = PanZoomState.TOUCHING;
+                mX.touchPos = event.getX(pointRemainingIndex);
+                mY.touchPos = event.getY(pointRemainingIndex);
+            } else {
+                // still pinching, do nothing
+            }
+            return true;
+        }
+        Log.e(LOG_NAME, "Unhandled case " + mState + " in onTouchEnd");
+        return false;
+    }
+
+    private boolean onTouchCancel(MotionEvent event) {
+        mState = PanZoomState.NOTHING;
+        return false;
+    }
+
+    private void track(MotionEvent event, long timestamp) {
+        long timeStep = timestamp - mLastTimestamp;
+        mLastTimestamp = timestamp;
+
+        float zoomFactor = mController.getZoomFactor();
+        mX.velocity = (mX.touchPos - event.getX(0)) / zoomFactor;
+        mY.velocity = (mY.touchPos - event.getY(0)) / zoomFactor;
+        mX.touchPos = event.getX(0); mY.touchPos = event.getY(0);
+
+        float absVelocity = (float)Math.sqrt(mX.velocity * mX.velocity +
+                                             mY.velocity * mY.velocity);
+        if (absVelocity < STOPPED_THRESHOLD)
+            mState = PanZoomState.PANNING_HOLD;
+
+        mX.applyEdgeResistance(); mX.displace();
+        mY.applyEdgeResistance(); mY.displace();
+        updatePosition();
+    }
+
+    private void fling(long timestamp) {
+        long timeStep = timestamp - mLastTimestamp;
+        mLastTimestamp = timestamp;
+
+        if (mState != PanZoomState.FLING)
+            mX.velocity = mY.velocity = 0.0f;
+
+        mX.displace(); mY.displace();
+
+        if (mFlingTimer != null)
+            mFlingTimer.cancel();
+
+        mX.startFling(); mY.startFling();
+
+        mFlingTimer = new Timer();
+        mFlingTimer.scheduleAtFixedRate(new TimerTask() {
+            public void run() { mController.post(new FlingRunnable()); }
+        }, 0, 1000L/60L);
+    }
+
+    private void updatePosition() {
+        mController.scrollTo(mX.viewportPos, mY.viewportPos);
+        mController.notifyLayerClientOfGeometryChange();
+    }
+
+    // Populates the viewport info and length in the axes.
+    private void populatePositionAndLength() {
+        IntSize pageSize = mController.getPageSize();
+        FloatRect visibleRect = mController.getVisibleRect();
+        IntSize screenSize = mController.getScreenSize();
+
+        mX.setPageLength(pageSize.width);
+        mX.viewportPos = visibleRect.x;
+        mX.setViewportLength(visibleRect.width);
+
+        mY.setPageLength(pageSize.height);
+        mY.viewportPos = visibleRect.y;
+        mY.setViewportLength(visibleRect.height);
+    }
+
+    // The callback that performs the fling animation.
+    private class FlingRunnable implements Runnable {
+        public void run() {
+            populatePositionAndLength();
+            mX.advanceFling(); mY.advanceFling();
+
+            // If both X and Y axes are overscrolled, we have to wait until both axes have stopped
+            // to snap back to avoid a jarring effect.
+            boolean waitingToSnapX = mX.getFlingState() == Axis.FlingStates.WAITING_TO_SNAP;
+            boolean waitingToSnapY = mY.getFlingState() == Axis.FlingStates.WAITING_TO_SNAP;
+            if ((mX.getOverscroll() == Axis.Overscroll.PLUS || mX.getOverscroll() == Axis.Overscroll.MINUS) &&
+                (mY.getOverscroll() == Axis.Overscroll.PLUS || mY.getOverscroll() == Axis.Overscroll.MINUS))
+            {
+                if (waitingToSnapX && waitingToSnapY) {
+                    mX.startSnap(); mY.startSnap();
+                }
+            } else {
+                if (waitingToSnapX)
+                    mX.startSnap();
+                if (waitingToSnapY)
+                    mY.startSnap();
+            }
+
+            mX.displace(); mY.displace();
+            updatePosition();
+
+            if (mX.getFlingState() == Axis.FlingStates.STOPPED &&
+                    mY.getFlingState() == Axis.FlingStates.STOPPED) {
+                stop();
+            }
+        }
+
+        private void stop() {
+            mState = PanZoomState.NOTHING;
+            if (mFlingTimer != null) {
+                mFlingTimer.cancel();
+                mFlingTimer = null;
+            }
+        }
+    }
+
+    private float computeElasticity(float excess, float viewportLength) {
+        return 1.0f - excess / (viewportLength * SNAP_LIMIT);
+    }
+
+    // Physics information for one axis (X or Y).
+    private static class Axis {
+        public enum FlingStates {
+            STOPPED,
+            SCROLLING,
+            WAITING_TO_SNAP,
+            SNAPPING,
+        }
+
+        public enum Overscroll {
+            NONE,
+            MINUS,      // Overscrolled in the negative direction
+            PLUS,       // Overscrolled in the positive direction
+            BOTH,       // Overscrolled in both directions (page is zoomed to smaller than screen)
+        }
+
+        public float touchPos;                  /* Position of the last touch. */
+        public float velocity;                  /* Velocity in this direction. */
+
+        private FlingStates mFlingState;        /* The fling state we're in on this axis. */
+        private EaseOutAnimation mSnapAnim;     /* The animation when the page is snapping back. */
+
+        /* These three need to be kept in sync with the layer controller. */
+        public float viewportPos;
+        private float mViewportLength;
+        private int mScreenLength;
+        private int mPageLength;
+
+        public FlingStates getFlingState() { return mFlingState; }
+
+        public void setViewportLength(float viewportLength) { mViewportLength = viewportLength; }
+        public void setScreenLength(int screenLength) { mScreenLength = screenLength; }
+        public void setPageLength(int pageLength) { mPageLength = pageLength; }
+
+        private float getViewportEnd() { return viewportPos + mViewportLength; }
+
+        public Overscroll getOverscroll() {
+            boolean minus = (viewportPos < 0.0f);
+            boolean plus = (getViewportEnd() > mPageLength);
+            if (minus && plus)
+                return Overscroll.BOTH;
+            else if (minus)
+                return Overscroll.MINUS;
+            else if (plus)
+                return Overscroll.PLUS;
+            else
+                return Overscroll.NONE;
+        }
+
+        // Returns the amount that the page has been overscrolled. If the page hasn't been
+        // overscrolled on this axis, returns 0.
+        private float getExcess() {
+            switch (getOverscroll()) {
+            case MINUS:     return Math.min(-viewportPos, mPageLength - getViewportEnd());
+            case PLUS:      return Math.min(viewportPos, getViewportEnd() - mPageLength);
+            default:        return 0.0f;
+            }
+        }
+
+        // Applies resistance along the edges when tracking.
+        public void applyEdgeResistance() {
+            float excess = getExcess();
+            if (excess > 0.0f)
+                velocity *= SNAP_LIMIT - excess / mViewportLength;
+        }
+
+        public void startFling() { mFlingState = FlingStates.SCROLLING; }
+
+        // Advances a fling animation by one step.
+        public void advanceFling() {
+            switch (mFlingState) {
+            case SCROLLING:
+                scroll();
+                return;
+            case WAITING_TO_SNAP:
+                // We don't do anything until the controller switches us into the snapping state.
+                return;
+            case SNAPPING:
+                snap();
+                return;
+            }
+        }
+
+        // Performs one frame of a scroll operation if applicable.
+        private void scroll() {
+            // If we aren't overscrolled, just apply friction.
+            float excess = getExcess();
+            if (excess == 0.0f) {
+                velocity *= FRICTION;
+                if (Math.abs(velocity) < 0.1f) {
+                    velocity = 0.0f;
+                    mFlingState = FlingStates.STOPPED;
+                }
+                return;
+            }
+
+            // Otherwise, decrease the velocity linearly.
+            float elasticity = 1.0f - excess / (mViewportLength * SNAP_LIMIT);
+            if (getOverscroll() == Overscroll.MINUS)
+                velocity = Math.min((velocity + OVERSCROLL_DECEL_RATE) * elasticity, 0.0f);
+            else // must be Overscroll.PLUS
+                velocity = Math.max((velocity - OVERSCROLL_DECEL_RATE) * elasticity, 0.0f);
+
+            if (Math.abs(velocity) < 0.3f) {
+                velocity = 0.0f;
+                mFlingState = FlingStates.WAITING_TO_SNAP;
+            }
+        }
+
+        // Starts a snap-into-place operation.
+        public void startSnap() {
+            switch (getOverscroll()) {
+            case MINUS:
+                mSnapAnim = new EaseOutAnimation(viewportPos, viewportPos + getExcess());
+                break;
+            case PLUS:
+                mSnapAnim = new EaseOutAnimation(viewportPos, viewportPos - getExcess());
+                break;
+            default:
+                throw new RuntimeException("Not overscrolled at startSnap()");
+            }
+
+            mFlingState = FlingStates.SNAPPING;
+        }
+
+        // Performs one frame of a snap-into-place operation.
+        private void snap() {
+            mSnapAnim.advance();
+            viewportPos = mSnapAnim.getPosition();
+
+            if (mSnapAnim.getFinished()) {
+                mSnapAnim = null;
+                mFlingState = FlingStates.STOPPED;
+            }
+        }
+
+        // Performs displacement of the viewport position according to the current velocity.
+        public void displace() { viewportPos += velocity; }
+    }
+
+    private static class EaseOutAnimation {
+        private float[] mFrames;
+        private float mPosition;
+        private float mOrigin;
+        private float mDest;
+        private long mTimestamp;
+        private boolean mFinished;
+
+        public EaseOutAnimation(float position, float dest) {
+            mPosition = mOrigin = position;
+            mDest = dest;
+            mFrames = new float[SNAP_TIME];
+            mTimestamp = System.currentTimeMillis();
+            mFinished = false;
+            plot(position, dest, mFrames);
+        }
+
+        public float getPosition() { return mPosition; }
+        public boolean getFinished() { return mFinished; }
+
+        private void advance() {
+            int frame = (int)(System.currentTimeMillis() - mTimestamp);
+            if (frame >= SNAP_TIME) {
+                mPosition = mDest;
+                mFinished = true;
+                return;
+            }
+
+            mPosition = mFrames[frame];
+        }
+
+        private static void plot(float from, float to, float[] frames) {
+            int nextX = 0;
+            for (int i = 0; i < SUBDIVISION_COUNT; i++) {
+                float t = (float)i / (float)SUBDIVISION_COUNT;
+                float xPos = (3.0f*t*t - 2.0f*t*t*t) * (float)frames.length;
+                if ((int)xPos < nextX)
+                    continue;
+
+                int oldX = nextX;
+                nextX = (int)xPos;
+
+                float yPos = 1.74f*t*t - 0.74f*t*t*t;
+                float framePos = from + (to - from) * yPos;
+
+                while (oldX < nextX)
+                    frames[oldX++] = framePos;
+
+                if (nextX >= frames.length)
+                    break;
+            }
+
+            // Pad out any remaining frames.
+            while (nextX < frames.length) {
+                frames[nextX] = frames[nextX - 1];
+                nextX++;
+            }
+        }
+    }
+
+    /*
+     * Zooming
+     */
+    @Override
+    public boolean onScale(ScaleGestureDetector detector) {
+        mState = PanZoomState.PINCHING;
+        float newZoom = detector.getCurrentSpan() / mInitialZoomSpan;
+
+        IntSize screenSize = mController.getScreenSize();
+        float x = mInitialZoomFocus.x - (detector.getFocusX() / newZoom);
+        float y = mInitialZoomFocus.y - (detector.getFocusY() / newZoom);
+        float width = screenSize.width / newZoom;
+        float height = screenSize.height / newZoom;
+        mController.setVisibleRect(x, y, width, height);
+        mController.notifyLayerClientOfGeometryChange();
+        populatePositionAndLength();
+        return true;
+    }
+
+    @Override
+    public boolean onScaleBegin(ScaleGestureDetector detector) {
+        mState = PanZoomState.PINCHING;
+        FloatRect initialZoomRect = mController.getVisibleRect();
+        float initialZoom = mController.getZoomFactor();
+
+        mInitialZoomFocus =
+            new FloatPoint(initialZoomRect.x + (detector.getFocusX() / initialZoom),
+                           initialZoomRect.y + (detector.getFocusY() / initialZoom));
+        mInitialZoomSpan = detector.getCurrentSpan() / initialZoom;
+        return true;
+    }
+
+    @Override
+    public void onScaleEnd(ScaleGestureDetector detector) {
+        mState = PanZoomState.PANNING_HOLD;
+        mLastTimestamp = System.currentTimeMillis();
+        mX.touchPos = detector.getFocusX();
+        mY.touchPos = detector.getFocusY();
+    }
+
+    @Override
+    public void onLongPress(MotionEvent motionEvent) {
+        JSONObject ret = new JSONObject();
+        try {
+            FloatPoint point = new FloatPoint(motionEvent.getX(), motionEvent.getY());
+            point = mController.convertViewPointToLayerPoint(point);
+            ret.put("x", (int)Math.round(point.x));
+            ret.put("y", (int)Math.round(point.y));
+        } catch(Exception ex) {
+            Log.w(LOG_NAME, "Error building return: " + ex);
+        }
+
+        GeckoEvent e = new GeckoEvent("Gesture:LongPress", ret.toString());
+        GeckoAppShell.sendEventToGecko(e);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/embedding/android/ui/ViewportController.java
@@ -0,0 +1,112 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.ui;
+
+import org.mozilla.gecko.gfx.FloatPoint;
+import org.mozilla.gecko.gfx.FloatRect;
+import org.mozilla.gecko.gfx.IntPoint;
+import org.mozilla.gecko.gfx.IntRect;
+import org.mozilla.gecko.gfx.IntSize;
+import org.mozilla.gecko.gfx.LayerController;
+
+/** Manages the dimensions of the page viewport. */
+public class ViewportController {
+    private IntSize mPageSize;
+    private FloatRect mVisibleRect;
+
+    public ViewportController(IntSize pageSize, FloatRect visibleRect) {
+        mPageSize = pageSize;
+        mVisibleRect = visibleRect;
+    }
+
+    private float clamp(float min, float value, float max) {
+        if (max < min)
+            return min;
+        return (value < min) ? min : (value > max) ? max : value;
+    }
+
+    /** Returns the given rect, clamped to the boundaries of a tile. */
+    public FloatRect clampRect(FloatRect rect) {
+        float x = clamp(0, rect.x, mPageSize.width - LayerController.TILE_WIDTH);
+        float y = clamp(0, rect.y, mPageSize.height - LayerController.TILE_HEIGHT);
+        return new FloatRect(x, y, rect.width, rect.height);
+    }
+
+    /** Returns the coordinates of a tile centered on the given rect. */
+    public static FloatRect widenRect(FloatRect rect) {
+        FloatPoint center = rect.getCenter();
+        return new FloatRect(center.x - LayerController.TILE_WIDTH / 2,
+                             center.y - LayerController.TILE_HEIGHT / 2,
+                             LayerController.TILE_WIDTH,
+                             LayerController.TILE_HEIGHT);
+    }
+
+    /**
+     * Given the layer controller's visible rect, page size, and screen size, returns the zoom
+     * factor.
+     */
+    public float getZoomFactor(FloatRect layerVisibleRect, IntSize layerPageSize,
+                               IntSize screenSize) {
+        FloatRect transformed = transformVisibleRect(layerVisibleRect, layerPageSize);
+        return (float)screenSize.width / transformed.width;
+    }
+
+    /**
+     * Given the visible rectangle that the user is viewing and the layer controller's page size,
+     * returns the dimensions of the box that this corresponds to on the page.
+     */
+    public FloatRect transformVisibleRect(FloatRect layerVisibleRect, IntSize layerPageSize) {
+        float zoomFactor = (float)layerPageSize.width / (float)mPageSize.width;
+        return layerVisibleRect.scaleAll(1.0f / zoomFactor);
+    }
+
+    /**
+     * Given the visible rectangle that the user is viewing and the layer controller's page size,
+     * returns the dimensions in layer coordinates that this corresponds to.
+     */
+    public FloatRect untransformVisibleRect(FloatRect viewportVisibleRect, IntSize layerPageSize) {
+        float zoomFactor = (float)layerPageSize.width / (float)mPageSize.width;
+        return viewportVisibleRect.scaleAll(zoomFactor);
+    }
+
+    public IntSize getPageSize() { return mPageSize; }
+    public void setPageSize(IntSize pageSize) { mPageSize = pageSize; }
+    public FloatRect getVisibleRect() { return mVisibleRect; }
+    public void setVisibleRect(FloatRect visibleRect) { mVisibleRect = visibleRect; }
+}
+
--- a/gfx/thebes/GLContextProviderEGL.cpp
+++ b/gfx/thebes/GLContextProviderEGL.cpp
@@ -1864,16 +1864,17 @@ CreateConfig(EGLConfig* aConfig)
     return false;
 }
 
 static EGLSurface
 CreateSurfaceForWindow(nsIWidget *aWidget, EGLConfig config)
 {
     EGLSurface surface;
 
+#ifdef PCWALTON_BROKEN
 
 #ifdef DEBUG
     sEGLLibrary.DumpEGLConfig(config);
 #endif
 
 #ifdef ANDROID
     // On Android, we have to ask Java to make the eglCreateWindowSurface
     // call for us.  See GLHelpers.java for a description of why.
@@ -1884,16 +1885,18 @@ CreateSurfaceForWindow(nsIWidget *aWidge
     surface = mozilla::AndroidBridge::Bridge()->
         CallEglCreateWindowSurface(EGL_DISPLAY(), config,
                                    mozilla::AndroidBridge::Bridge()->SurfaceView());
     printf_stderr("got surface %p\n", surface);
 #else
     surface = sEGLLibrary.fCreateWindowSurface(EGL_DISPLAY(), config, GET_NATIVE_WINDOW(aWidget), 0);
 #endif
 
+#endif
+
     return surface;
 }
 
 const char*
 GetVendor()
 {
     if (!sEGLLibrary.EnsureInitialized()) {
         return nsnull;
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -71,16 +71,30 @@ const kMinKineticSpeed = 0.015;
 // Maximum kinetic panning speed. 9 pixels/ms is equivalent to 150 pixels per
 // frame at 60fps.
 const kMaxKineticSpeed = 9;
 
 // The maximum magnitude of disparity allowed between axes acceleration. If
 // it's larger than this, lock the slow-moving axis.
 const kAxisLockRatio = 5;
 
+// The element tag names that are considered to receive input. Mouse-down
+// events directed to one of these are allowed to go through.
+const kElementsReceivingInput = {
+    applet: true,
+    audio: true,
+    button: true,
+    embed: true,
+    input: true,
+    map: true,
+    select: true,
+    textarea: true,
+    video: true
+};
+
 function dump(a) {
   Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a);
 }
 
 function sendMessageToJava(aMessage) {
   let bridge = Cc["@mozilla.org/android/bridge;1"].getService(Ci.nsIAndroidBridge);
   return bridge.handleGeckoMessage(JSON.stringify(aMessage));
 }
@@ -141,16 +155,17 @@ var BrowserApp = {
     Services.obs.addObserver(this, "Session:Forward", false);
     Services.obs.addObserver(this, "Session:Reload", false);
     Services.obs.addObserver(this, "SaveAs:PDF", false);
     Services.obs.addObserver(this, "Browser:Quit", false);
     Services.obs.addObserver(this, "Preferences:Get", false);
     Services.obs.addObserver(this, "Preferences:Set", false);
     Services.obs.addObserver(this, "ScrollTo:FocusedInput", false);
     Services.obs.addObserver(this, "Sanitize:ClearAll", false);
+    Services.obs.addObserver(this, "PanZoom:PanZoom", false);
     Services.obs.addObserver(this, "FullScreen:Exit", false);
 
     Services.obs.addObserver(XPInstallObserver, "addon-install-blocked", false);
     Services.obs.addObserver(XPInstallObserver, "addon-install-started", false);
 
     window.addEventListener("fullscreen", function() {
       sendMessageToJava({
         gecko: {
@@ -250,16 +265,22 @@ var BrowserApp = {
     let tabs = this._tabs;
     for (let i = 0; i < tabs.length; i++) {
       if (tabs[i].browser.contentDocument == aDocument)
         return tabs[i].browser;
     }
     return null;
   },
 
+  getPageSizeForBrowser: function getPageSizeForBrowser(aBrowser) {
+    let html = aBrowser.contentDocument.documentElement;
+    let body = aBrowser.contentDocument.body;
+    return { width: body.scrollWidth, height: body.scrollHeight };
+  },
+
   loadURI: function loadURI(aURI, aParams) {
     let browser = this.selectedBrowser;
     if (!browser)
       return;
 
     let flags = aParams.flags || Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
     let postData = ("postData" in aParams && aParams.postData) ? aParams.postData.value : null;
     let referrerURI = "referrerURI" in aParams ? aParams.referrerURI : null;
@@ -473,16 +494,30 @@ var BrowserApp = {
     let doc = aBrowser.contentDocument;
     if (!doc)
       return;
     let focused = doc.activeElement;
     if ((focused instanceof HTMLInputElement && focused.mozIsTextField(false)) || (focused instanceof HTMLTextAreaElement))
       focused.scrollIntoView(false);
   },
 
+  panZoom: function(aData) {
+    let data = JSON.parse(aData);
+
+    let browser = this.selectedBrowser;
+
+    /*let documentElement = browser.contentDocument.documentElement;
+    documentElement.style.MozTransform = 'translate(-' + data.x + 'px, -' + data.y + 'px) ' +
+        'translate(-50%, -50%) scale(' + data.zoomFactor + ') ' +
+        'translate(50%, 50%)';*/
+    browser.contentWindow.scrollTo(data.x, data.y);
+
+    sendMessageToJava({ gecko: { type: "PanZoom:Ack", rect: data } });
+  },
+
   updateScrollbarsFor: function(aElement) {
     // only draw the scrollbars if we're scrolling the root content element
     let doc = this.selectedBrowser.contentDocument;
     if (aElement != doc.documentElement && aElement != doc.body)
       return;
 
     // draw the vertical scrollbar as needed
     let scrollMax = aElement.scrollHeight;
@@ -513,16 +548,23 @@ var BrowserApp = {
     }
   },
 
   hideScrollbars: function() {
     this.vertScroller.setAttribute("panning", "");
     this.horizScroller.setAttribute("panning", "");
   },
 
+  /* FIXME: Awful hack to tide us over until the display port is usable. */
+  fakeDisplayPort: function(aBrowser) {
+    let html = aBrowser.contentDocument.documentElement;
+    html.style.width = '980px';
+    html.style.height = '1500px';
+  },
+
   observe: function(aSubject, aTopic, aData) {
     let browser = this.selectedBrowser;
     if (!browser)
       return;
 
     if (aTopic == "Session:Back") {
       browser.goBack();
     } else if (aTopic == "Session:Forward") {
@@ -547,16 +589,18 @@ var BrowserApp = {
     } else if (aTopic == "Preferences:Get") {
       this.getPreferences(aData);
     } else if (aTopic == "Preferences:Set") {
       this.setPreferences(aData);
     } else if (aTopic == "ScrollTo:FocusedInput") {
       this.scrollToFocusedInput(browser);
     } else if (aTopic == "Sanitize:ClearAll") {
       Sanitizer.sanitize();
+    } else if (aTopic == "PanZoom:PanZoom") {
+      this.panZoom(aData);
     } else if (aTopic == "FullScreen:Exit") {
       browser.contentDocument.mozCancelFullScreen();
     }
   }
 }
 
 var NativeWindow = {
   init: function() {
@@ -891,16 +935,18 @@ function Tab(aURL, aParams) {
 
 Tab.prototype = {
   create: function(aURL, aParams) {
     if (this.browser)
       return;
 
     this.browser = document.createElement("browser");
     this.browser.setAttribute("type", "content");
+    this.browser.setAttribute("width", "980");
+    this.browser.setAttribute("height", "480");
     BrowserApp.deck.appendChild(this.browser);
     this.browser.stop();
 
     this.id = ++gTabIDFactory;
     let aParams = aParams || { selected: true };
     let message = {
       gecko: {
         type: "Tab:Added",
@@ -966,17 +1012,17 @@ Tab.prototype = {
       let message = {
         gecko: {
           type: "Content:StateChange",
           tabID: this.id,
           uri: uri,
           state: aStateFlags
         }
       };
-  
+
       sendMessageToJava(message);
     }
   },
 
   onLocationChange: function(aWebProgress, aRequest, aLocationURI) {
     let contentWin = aWebProgress.DOMWindow;
     if (contentWin != contentWin.top)
         return;
@@ -1075,22 +1121,21 @@ Tab.prototype = {
 
 var BrowserEventHandler = {
   init: function init() {
     window.addEventListener("click", this, true);
     window.addEventListener("mousedown", this, true);
     window.addEventListener("mouseup", this, true);
     window.addEventListener("mousemove", this, true);
 
-    BrowserApp.deck.addEventListener("MozMagnifyGestureStart", this, true);
-    BrowserApp.deck.addEventListener("MozMagnifyGestureUpdate", this, true);
     BrowserApp.deck.addEventListener("DOMContentLoaded", this, true);
     BrowserApp.deck.addEventListener("DOMLinkAdded", this, true);
     BrowserApp.deck.addEventListener("DOMTitleChanged", this, true);
     BrowserApp.deck.addEventListener("DOMUpdatePageReport", PopupBlockerObserver.onUpdatePageReport, false);
+    BrowserApp.deck.addEventListener("MozScrolledAreaChanged", this, true);
   },
 
   handleEvent: function(aEvent) {
     switch (aEvent.type) {
       case "DOMContentLoaded": {
         let browser = BrowserApp.getBrowserForDocument(aEvent.target);
         browser.focus();
 
@@ -1113,16 +1158,19 @@ var BrowserEventHandler = {
         if (/^about:/.test(aEvent.originalTarget.documentURI)) {
           let browser = BrowserApp.getBrowserForDocument(aEvent.originalTarget);
           browser.addEventListener("click", ErrorPageEventHandler, false);
           browser.addEventListener("pagehide", function listener() {
             browser.removeEventListener("click", ErrorPageEventHandler, false);
             browser.removeEventListener("pagehide", listener, true);
           }, true);
         }
+
+        BrowserApp.fakeDisplayPort(browser);
+
         break;
       }
 
       case "DOMLinkAdded": {
         let target = aEvent.originalTarget;
         if (!target.href || target.disabled)
           return;
 
@@ -1200,16 +1248,18 @@ var BrowserEventHandler = {
 
         this.motionBuffer = [];
         this._updateLastPosition(aEvent.clientX, aEvent.clientY, 0, 0);
         this.panElement = this._findScrollableElement(aEvent.originalTarget,
                                                       true);
 
         if (this.panElement)
           this.panning = true;
+        if (!this._elementReceivesInput(aEvent.target))
+          aEvent.preventDefault();  // Stops selection.
         break;
 
       case "mousemove":
         aEvent.stopPropagation();
         aEvent.preventDefault();
 
         if (!this.panning)
           break;
@@ -1276,19 +1326,16 @@ var BrowserEventHandler = {
               this.lockYaxis = false;
           } else {
             this.edy = 0;
           }
         }
         break;
 
       case "mouseup":
-        if (!this.panning)
-          break;
-
         this.panning = false;
 
         // hide the scrollbars in case we're done scrolling. if the
         // kinetic scrolling kicks in, it will re-enable the scrollbars
         // anyway by calling _scrollElementBy below
         BrowserApp.hideScrollbars();
 
         if (Math.abs(aEvent.clientX - this.startX) > kDragThreshold ||
@@ -1466,51 +1513,32 @@ var BrowserEventHandler = {
           else if (Math.abs(this.panY) < Math.abs(this.panX) / kAxisLockRatio)
             this.panY = 0;
 
           // Start the panning animation
           window.mozRequestAnimationFrame(callback);
         }
         break;
 
-      case "MozMagnifyGestureStart":
-        this._pinchDelta = 0;
-        this.zoomCallbackFired = true;
-        break;
-
-      case "MozMagnifyGestureUpdate":
-        if (!aEvent.delta)
-          break;
-  
-        this._pinchDelta += aEvent.delta;
+      case "MozScrolledAreaChanged":
+        dump("### Resize!");
 
-        if ((Math.abs(this._pinchDelta) >= 1) && this.zoomCallbackFired) {
-          // pinchDelta is the difference in pixels since the last call, so can
-          // be viewed as the number of extra/fewer pixels visible.
-          //
-          // We can work out the new zoom level by looking at the window width
-          // and height, and the existing zoom level.
-          let currentZoom = BrowserApp.selectedBrowser.markupDocumentViewer.fullZoom;
-          let currentSize = Math.sqrt(Math.pow(window.innerWidth, 2) + Math.pow(window.innerHeight, 2));
-          let newZoom = ((currentSize * currentZoom) + this._pinchDelta) / currentSize;
+        /* TODO: Only for tab in foreground */
+        let browser = BrowserApp.getBrowserForDocument(aEvent.target);
+        if (!browser) {
+          dump("### Resize: No browser!");
+          return;
+        }
 
-          let self = this;
-          let callback = {
-            onBeforePaint: function zoomCallback(timeStamp) {
-              BrowserApp.selectedBrowser.markupDocumentViewer.fullZoom = newZoom;
-              self.zoomCallbackFired = true;
-            }
-          };
-
-          this._pinchDelta = 0;
-
-          // Use mozRequestAnimationFrame to stop from flooding fullZoom
-          this.zoomCallbackFired = false;
-          window.mozRequestAnimationFrame(callback);
-        }
+        sendMessageToJava({
+          gecko: {
+            type: "PanZoom:Resize",
+            size: BrowserApp.getPageSizeForBrowser(browser)
+          }
+        });
         break;
     }
   },
 
   _updateLastPosition: function(x, y, dx, dy) {
     this.lastX = x;
     this.lastY = y;
     this.lastTime = Date.now();
@@ -1555,26 +1583,24 @@ var BrowserEventHandler = {
 
   _findScrollableElement: function(elem, checkElem) {
     // Walk the DOM tree until we find a scrollable element
     let scrollable = false;
     while (elem) {
       /* Element is scrollable if its scroll-size exceeds its client size, and:
        * - It has overflow 'auto' or 'scroll'
        * - It's a textarea
-       * - It's an HTML/BODY node
+       * We don't consider HTML/BODY nodes here, since Java pans those.
        */
       if (checkElem) {
         if (((elem.scrollHeight > elem.clientHeight) ||
              (elem.scrollWidth > elem.clientWidth)) &&
             (elem.style.overflow == 'auto' ||
              elem.style.overflow == 'scroll' ||
-             elem.localName == 'textarea' ||
-             elem.localName == 'html' ||
-             elem.localName == 'body')) {
+             elem.localName == 'textarea')) {
           scrollable = true;
           break;
         }
       } else {
         checkElem = true;
       }
 
       // Propagate up iFrames
@@ -1586,16 +1612,22 @@ var BrowserEventHandler = {
     }
 
     if (!scrollable)
       return null;
 
     return elem;
   },
 
+  _elementReceivesInput: function(aElement) {
+    return aElement instanceof Element &&
+        (kElementsReceivingInput.hasOwnProperty(aElement.tagName.toLowerCase()) ||
+        aElement.contentEditable === "true" || aElement.contentEditable === "");
+  },
+
   _scrollElementBy: function(elem, x, y) {
     elem.scrollTop = elem.scrollTop + y;
     elem.scrollLeft = elem.scrollLeft + x;
     BrowserApp.updateScrollbarsFor(elem);
   },
 
   _elementCanScroll: function(elem, x, y) {
     let scrollX = true;
--- a/other-licenses/android/APKOpen.cpp
+++ b/other-licenses/android/APKOpen.cpp
@@ -228,16 +228,17 @@ Java_org_mozilla_gecko_GeckoAppShell_ ##
   f_ ## name(jenv, jc, one, two, three); \
 }
 
 SHELL_WRAPPER0(nativeInit)
 SHELL_WRAPPER1(nativeRun, jstring)
 SHELL_WRAPPER1(notifyGeckoOfEvent, jobject)
 SHELL_WRAPPER0(processNextNativeEvent)
 SHELL_WRAPPER1(setSurfaceView, jobject)
+SHELL_WRAPPER1(setSoftwareLayerClient, jobject)
 SHELL_WRAPPER0(onResume)
 SHELL_WRAPPER0(onLowMemory)
 SHELL_WRAPPER3(callObserver, jstring, jstring, jstring)
 SHELL_WRAPPER1(removeObserver, jstring)
 SHELL_WRAPPER1(onChangeNetworkLinkStatus, jstring)
 SHELL_WRAPPER1(reportJavaCrash, jstring)
 SHELL_WRAPPER0(executeNextRunnable)
 SHELL_WRAPPER1(cameraCallbackBridge, jbyteArray)
@@ -635,16 +636,17 @@ loadLibs(const char *apkName)
     __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't get a handle to libxul!");
 
 #define GETFUNC(name) f_ ## name = (name ## _t) __wrap_dlsym(xul_handle, "Java_org_mozilla_gecko_GeckoAppShell_" #name)
   GETFUNC(nativeInit);
   GETFUNC(nativeRun);
   GETFUNC(notifyGeckoOfEvent);
   GETFUNC(processNextNativeEvent);
   GETFUNC(setSurfaceView);
+  GETFUNC(setSoftwareLayerClient);
   GETFUNC(onResume);
   GETFUNC(onLowMemory);
   GETFUNC(callObserver);
   GETFUNC(removeObserver);
   GETFUNC(onChangeNetworkLinkStatus);
   GETFUNC(reportJavaCrash);
   GETFUNC(executeNextRunnable);
   GETFUNC(cameraCallbackBridge);
--- a/widget/src/android/AndroidBridge.cpp
+++ b/widget/src/android/AndroidBridge.cpp
@@ -767,73 +767,28 @@ AndroidBridge::GetShowPasswordSetting()
 bool
 AndroidBridge::GetAccessibilityEnabled()
 {
     ALOG_BRIDGE("AndroidBridge::GetAccessibilityEnabled");
     return mJNIEnv->CallStaticBooleanMethod(mGeckoAppShellClass, jGetAccessibilityEnabled);
 }
 
 void
-AndroidBridge::SetSurfaceView(jobject obj)
+AndroidBridge::SetSoftwareLayerClient(jobject obj)
 {
-    mSurfaceView.Init(obj);
+    mSoftwareLayerClient.Init(obj);
 }
 
 void
 AndroidBridge::ShowInputMethodPicker()
 {
     ALOG_BRIDGE("AndroidBridge::ShowInputMethodPicker");
     mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass, jShowInputMethodPicker);
 }
 
-void *
-AndroidBridge::CallEglCreateWindowSurface(void *dpy, void *config, AndroidGeckoSurfaceView &sview)
-{
-    ALOG_BRIDGE("AndroidBridge::CallEglCreateWindowSurface");
-    AutoLocalJNIFrame jniFrame;
-
-    /*
-     * This is basically:
-     *
-     *    s = EGLContext.getEGL().eglCreateWindowSurface(new EGLDisplayImpl(dpy),
-     *                                                   new EGLConfigImpl(config),
-     *                                                   view.getHolder(), null);
-     *    return s.mEGLSurface;
-     *
-     * We can't do it from java, because the EGLConfigImpl constructor is private.
-     */
-
-    jobject surfaceHolder = sview.GetSurfaceHolder();
-    if (!surfaceHolder)
-        return nsnull;
-
-    // grab some fields and methods we'll need
-    jmethodID constructConfig = mJNIEnv->GetMethodID(jEGLConfigImplClass, "<init>", "(I)V");
-    jmethodID constructDisplay = mJNIEnv->GetMethodID(jEGLDisplayImplClass, "<init>", "(I)V");
-
-    jmethodID getEgl = mJNIEnv->GetStaticMethodID(jEGLContextClass, "getEGL", "()Ljavax/microedition/khronos/egl/EGL;");
-    jmethodID createWindowSurface = mJNIEnv->GetMethodID(jEGL10Class, "eglCreateWindowSurface", "(Ljavax/microedition/khronos/egl/EGLDisplay;Ljavax/microedition/khronos/egl/EGLConfig;Ljava/lang/Object;[I)Ljavax/microedition/khronos/egl/EGLSurface;");
-
-    jobject egl = mJNIEnv->CallStaticObjectMethod(jEGLContextClass, getEgl);
-
-    jobject jdpy = mJNIEnv->NewObject(jEGLDisplayImplClass, constructDisplay, (int) dpy);
-    jobject jconf = mJNIEnv->NewObject(jEGLConfigImplClass, constructConfig, (int) config);
-
-    // make the call
-    jobject surf = mJNIEnv->CallObjectMethod(egl, createWindowSurface, jdpy, jconf, surfaceHolder, NULL);
-    if (!surf)
-        return nsnull;
-
-    jfieldID sfield = mJNIEnv->GetFieldID(jEGLSurfaceImplClass, "mEGLSurface", "I");
-
-    jint realSurface = mJNIEnv->GetIntField(surf, sfield);
-
-    return (void*) realSurface;
-}
-
 bool
 AndroidBridge::GetStaticIntField(const char *className, const char *fieldName, PRInt32* aInt)
 {
     ALOG_BRIDGE("AndroidBridge::GetStaticIntField %s", fieldName);
     AutoLocalJNIFrame jniFrame(3);
     jclass cls = mJNIEnv->FindClass(className);
     if (!cls)
         return false;
--- a/widget/src/android/AndroidBridge.h
+++ b/widget/src/android/AndroidBridge.h
@@ -35,16 +35,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef AndroidBridge_h__
 #define AndroidBridge_h__
 
 #include <jni.h>
 #include <android/log.h>
+#include <cstdlib>
 
 #include "nsCOMPtr.h"
 #include "nsCOMArray.h"
 #include "nsIRunnable.h"
 #include "nsIObserver.h"
 
 #include "AndroidJavaWrappers.h"
 
@@ -144,18 +145,18 @@ public:
     void ReturnIMEQueryResult(const PRUnichar *aResult, PRUint32 aLen, int aSelStart, int aSelLen);
 
     void NotifyAppShellReady();
 
     void NotifyXreExit();
 
     void ScheduleRestart();
 
-    void SetSurfaceView(jobject jobj);
-    AndroidGeckoSurfaceView& SurfaceView() { return mSurfaceView; }
+    void SetSoftwareLayerClient(jobject jobj);
+    AndroidGeckoSoftwareLayerClient &GetSoftwareLayerClient() { return mSoftwareLayerClient; }
 
     bool GetHandlersForURL(const char *aURL, 
                              nsIMutableArray* handlersArray = nsnull,
                              nsIHandlerApp **aDefaultApp = nsnull,
                              const nsAString& aAction = EmptyString());
 
     bool GetHandlersForMimeType(const char *aMimeType,
                                   nsIMutableArray* handlersArray = nsnull,
@@ -242,19 +243,16 @@ public:
                 AndroidBridge::Bridge()->JNI()->ExceptionDescribe();
                 AndroidBridge::Bridge()->JNI()->ExceptionClear();
             }
             AndroidBridge::Bridge()->JNI()->PopLocalFrame(NULL);
         }
         int mEntries;
     };
 
-    /* See GLHelpers.java as to why this is needed */
-    void *CallEglCreateWindowSurface(void *dpy, void *config, AndroidGeckoSurfaceView& surfaceView);
-
     bool GetStaticStringField(const char *classID, const char *field, nsAString &result);
 
     bool GetStaticIntField(const char *className, const char *fieldName, PRInt32* aInt);
 
     void SetKeepScreenOn(bool on);
 
     void ScanMedia(const nsAString& aFile, const nsACString& aMimeType);
 
@@ -309,18 +307,18 @@ protected:
 
     // the global JavaVM
     JavaVM *mJavaVM;
 
     // the JNIEnv for the main thread
     JNIEnv *mJNIEnv;
     void *mThread;
 
-    // the GeckoSurfaceView
-    AndroidGeckoSurfaceView mSurfaceView;
+    // the software rendering layer client
+    AndroidGeckoSoftwareLayerClient mSoftwareLayerClient;
 
     // the GeckoAppShell java class
     jclass mGeckoAppShellClass;
 
     AndroidBridge() { }
     bool Init(JNIEnv *jEnv, jclass jGeckoApp);
 
     void EnsureJNIThread();
--- a/widget/src/android/AndroidJNI.cpp
+++ b/widget/src/android/AndroidJNI.cpp
@@ -39,16 +39,17 @@
 #include "nsILocalFile.h"
 #include "nsString.h"
 
 #include "AndroidBridge.h"
 
 #include <jni.h>
 #include <pthread.h>
 #include <dlfcn.h>
+#include <stdio.h>
 
 #include "nsAppShell.h"
 #include "nsWindow.h"
 #include <android/log.h>
 #include "nsIObserverService.h"
 #include "mozilla/Services.h"
 #include "nsINetworkLinkService.h"
 #include "nsAndroidHistory.h"
@@ -62,17 +63,17 @@
 using namespace mozilla;
 
 /* Forward declare all the JNI methods as extern "C" */
 
 extern "C" {
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_nativeInit(JNIEnv *, jclass);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyGeckoOfEvent(JNIEnv *, jclass, jobject event);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_processNextNativeEvent(JNIEnv *, jclass);
-    NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_setSurfaceView(JNIEnv *jenv, jclass, jobject sv);
+    NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_setSoftwareLayerClient(JNIEnv *jenv, jclass, jobject sv);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onResume(JNIEnv *, jclass);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onLowMemory(JNIEnv *, jclass);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_callObserver(JNIEnv *, jclass, jstring observerKey, jstring topic, jstring data);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_removeObserver(JNIEnv *jenv, jclass, jstring jObserverKey);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onChangeNetworkLinkStatus(JNIEnv *, jclass, jstring status);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_reportJavaCrash(JNIEnv *, jclass, jstring stack);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_executeNextRunnable(JNIEnv *, jclass);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyUriVisited(JNIEnv *, jclass, jstring uri);
@@ -82,39 +83,43 @@ extern "C" {
 
 /*
  * Incoming JNI methods
  */
 
 NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_nativeInit(JNIEnv *jenv, jclass jc)
 {
+    ALOG("Native init!");
     AndroidBridge::ConstructBridge(jenv, jc);
 }
 
 NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_notifyGeckoOfEvent(JNIEnv *jenv, jclass jc, jobject event)
 {
     // poke the appshell
-    if (nsAppShell::gAppShell)
+    if (nsAppShell::gAppShell) {
         nsAppShell::gAppShell->PostEvent(new AndroidGeckoEvent(jenv, event));
+    }
 }
 
 NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_processNextNativeEvent(JNIEnv *jenv, jclass)
 {
     // poke the appshell
     if (nsAppShell::gAppShell)
         nsAppShell::gAppShell->ProcessNextNativeEvent(false);
 }
 
 NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoAppShell_setSurfaceView(JNIEnv *jenv, jclass, jobject obj)
+Java_org_mozilla_gecko_GeckoAppShell_setSoftwareLayerClient(JNIEnv *jenv, jclass, jobject obj)
 {
-    AndroidBridge::Bridge()->SetSurfaceView(jenv->NewGlobalRef(obj));
+    ALOG("setSoftwareLayerClient before");
+    AndroidBridge::Bridge()->SetSoftwareLayerClient(jenv->NewGlobalRef(obj));
+    ALOG("setSoftwareLayerClient after");
 }
 
 NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_onLowMemory(JNIEnv *jenv, jclass jc)
 {
     if (nsAppShell::gAppShell) {
         nsAppShell::gAppShell->NotifyObservers(nsnull,
                                                "memory-pressure",
--- a/widget/src/android/AndroidJavaWrappers.cpp
+++ b/widget/src/android/AndroidJavaWrappers.cpp
@@ -98,25 +98,21 @@ jmethodID AndroidAddress::jGetFeatureNam
 jmethodID AndroidAddress::jGetLocalityMethod;
 jmethodID AndroidAddress::jGetPostalCodeMethod;
 jmethodID AndroidAddress::jGetPremisesMethod;
 jmethodID AndroidAddress::jGetSubAdminAreaMethod;
 jmethodID AndroidAddress::jGetSubLocalityMethod;
 jmethodID AndroidAddress::jGetSubThoroughfareMethod;
 jmethodID AndroidAddress::jGetThoroughfareMethod;
 
-jclass AndroidGeckoSurfaceView::jGeckoSurfaceViewClass = 0;
-jmethodID AndroidGeckoSurfaceView::jBeginDrawingMethod = 0;
-jmethodID AndroidGeckoSurfaceView::jEndDrawingMethod = 0;
-jmethodID AndroidGeckoSurfaceView::jDraw2DBitmapMethod = 0;
-jmethodID AndroidGeckoSurfaceView::jDraw2DBufferMethod = 0;
-jmethodID AndroidGeckoSurfaceView::jGetSoftwareDrawBitmapMethod = 0;
-jmethodID AndroidGeckoSurfaceView::jGetSoftwareDrawBufferMethod = 0;
-jmethodID AndroidGeckoSurfaceView::jGetSurfaceMethod = 0;
-jmethodID AndroidGeckoSurfaceView::jGetHolderMethod = 0;
+jclass AndroidGeckoSoftwareLayerClient::jGeckoSoftwareLayerClientClass = 0;
+jmethodID AndroidGeckoSoftwareLayerClient::jLockBufferMethod = 0;
+jmethodID AndroidGeckoSoftwareLayerClient::jUnlockBufferMethod = 0;
+jmethodID AndroidGeckoSoftwareLayerClient::jBeginDrawingMethod = 0;
+jmethodID AndroidGeckoSoftwareLayerClient::jEndDrawingMethod = 0;
 
 #define JNI()  (AndroidBridge::JNI())
 
 #define initInit() jclass jClass
 
 // note that this also sets jClass
 #define getClassGlobalRef(cname) \
     (jClass = jclass(jEnv->NewGlobalRef(jEnv->FindClass(cname))))
@@ -126,20 +122,21 @@ jmethodID AndroidGeckoSurfaceView::jGetH
 
 #define getMethod(fname, ftype) \
     ((jmethodID) jEnv->GetMethodID(jClass, fname, ftype))
 
 void
 mozilla::InitAndroidJavaWrappers(JNIEnv *jEnv)
 {
     AndroidGeckoEvent::InitGeckoEventClass(jEnv);
-    AndroidGeckoSurfaceView::InitGeckoSurfaceViewClass(jEnv);
     AndroidPoint::InitPointClass(jEnv);
+    AndroidRect::InitRectClass(jEnv);
     AndroidLocation::InitLocationClass(jEnv);
     AndroidAddress::InitAddressClass(jEnv);
+    AndroidGeckoSoftwareLayerClient::InitGeckoSoftwareLayerClientClass(jEnv);
 }
 
 void
 AndroidGeckoEvent::InitGeckoEventClass(JNIEnv *jEnv)
 {
     initInit();
 
     jGeckoEventClass = getClassGlobalRef("org/mozilla/gecko/GeckoEvent");
@@ -169,33 +166,16 @@ AndroidGeckoEvent::InitGeckoEventClass(J
     jRangeStylesField = getField("mRangeStyles", "I");
     jRangeForeColorField = getField("mRangeForeColor", "I");
     jRangeBackColorField = getField("mRangeBackColor", "I");
     jLocationField = getField("mLocation", "Landroid/location/Location;");
     jAddressField = getField("mAddress", "Landroid/location/Address;");
 }
 
 void
-AndroidGeckoSurfaceView::InitGeckoSurfaceViewClass(JNIEnv *jEnv)
-{
-    initInit();
-
-    jGeckoSurfaceViewClass = getClassGlobalRef("org/mozilla/gecko/GeckoSurfaceView");
-
-    jBeginDrawingMethod = getMethod("beginDrawing", "()I");
-    jGetSoftwareDrawBitmapMethod = getMethod("getSoftwareDrawBitmap", "()Landroid/graphics/Bitmap;");
-    jGetSoftwareDrawBufferMethod = getMethod("getSoftwareDrawBuffer", "()Ljava/nio/ByteBuffer;");
-    jEndDrawingMethod = getMethod("endDrawing", "()V");
-    jDraw2DBitmapMethod = getMethod("draw2D", "(Landroid/graphics/Bitmap;II)V");
-    jDraw2DBufferMethod = getMethod("draw2D", "(Ljava/nio/ByteBuffer;I)V");
-    jGetSurfaceMethod = getMethod("getSurface", "()Landroid/view/Surface;");
-    jGetHolderMethod = getMethod("getHolder", "()Landroid/view/SurfaceHolder;");
-}
-
-void
 AndroidLocation::InitLocationClass(JNIEnv *jEnv)
 {
     initInit();
 
     jLocationClass = getClassGlobalRef("android/location/Location");
     jGetLatitudeMethod = getMethod("getLatitude", "()D");
     jGetLongitudeMethod = getMethod("getLongitude", "()D");
     jGetAltitudeMethod = getMethod("getAltitude", "()D");
@@ -299,16 +279,30 @@ AndroidRect::InitRectClass(JNIEnv *jEnv)
     jRectClass = getClassGlobalRef("android/graphics/Rect");
 
     jBottomField = getField("bottom", "I");
     jLeftField = getField("left", "I");
     jTopField = getField("top", "I");
     jRightField = getField("right", "I");
 }
 
+void
+AndroidGeckoSoftwareLayerClient::InitGeckoSoftwareLayerClientClass(JNIEnv *jEnv)
+{
+    initInit();
+
+    jGeckoSoftwareLayerClientClass =
+        getClassGlobalRef("org/mozilla/gecko/gfx/GeckoSoftwareLayerClient");
+
+    jLockBufferMethod = getMethod("lockBuffer", "()Ljava/nio/ByteBuffer;");
+    jUnlockBufferMethod = getMethod("unlockBuffer", "()V");
+    jBeginDrawingMethod = getMethod("beginDrawing", "()V");
+    jEndDrawingMethod = getMethod("endDrawing", "(IIII)V");
+}
+
 #undef initInit
 #undef initClassGlobalRef
 #undef getField
 #undef getMethod
 
 void
 AndroidGeckoEvent::ReadP0Field(JNIEnv *jenv)
 {
@@ -418,17 +412,19 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jo
                 mRangeForeColor =
                     jenv->GetIntField(jobj, jRangeForeColorField);
                 mRangeBackColor =
                     jenv->GetIntField(jobj, jRangeBackColorField);
             }
             break;
 
         case DRAW:
+            ALOG("### Draw, before ReadRectField");
             ReadRectField(jenv);
+            ALOG("### Draw, after ReadRectField");
             break;
 
         case ORIENTATION_EVENT:
             mAlpha = jenv->GetDoubleField(jobj, jAlphaField);
             mBeta = jenv->GetDoubleField(jobj, jBetaField);
             mGamma = jenv->GetDoubleField(jobj, jGammaField);
             break;
 
@@ -475,121 +471,118 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jo
 
 void
 AndroidGeckoEvent::Init(int aType)
 {
     mType = aType;
 }
 
 void
-AndroidGeckoEvent::Init(int x1, int y1, int x2, int y2)
+AndroidGeckoEvent::Init(int aType, const nsIntRect &aRect)
 {
-    mType = DRAW;
-    mRect.SetEmpty();
+    mType = aType;
+    mRect = aRect;
 }
 
 void
 AndroidGeckoEvent::Init(AndroidGeckoEvent *aResizeEvent)
 {
     NS_ASSERTION(aResizeEvent->Type() == SIZE_CHANGED, "Init called on non-SIZE_CHANGED event");
 
     mType = FORCED_RESIZE;
     mTime = aResizeEvent->mTime;
     mP0.x = aResizeEvent->mP0.x;
     mP0.y = aResizeEvent->mP0.y;
     mP1.x = aResizeEvent->mP1.x;
     mP1.y = aResizeEvent->mP1.y;
 }
 
 void
-AndroidGeckoSurfaceView::Init(jobject jobj)
-{
-    NS_ASSERTION(wrapped_obj == nsnull, "Init called on non-null wrapped_obj!");
-
-    wrapped_obj = jobj;
-}
-
-int
-AndroidGeckoSurfaceView::BeginDrawing()
-{
-    NS_ASSERTION(!isNull(), "BeginDrawing called on null surfaceview!");
-
-    return JNI()->CallIntMethod(wrapped_obj, jBeginDrawingMethod);
-}
-
-void
-AndroidGeckoSurfaceView::EndDrawing()
-{
-    JNI()->CallVoidMethod(wrapped_obj, jEndDrawingMethod);
-}
-
-void
-AndroidGeckoSurfaceView::Draw2D(jobject bitmap, int width, int height)
-{
-    JNI()->CallVoidMethod(wrapped_obj, jDraw2DBitmapMethod, bitmap, width, height);
-}
-
-void
-AndroidGeckoSurfaceView::Draw2D(jobject buffer, int stride)
-{
-    JNI()->CallVoidMethod(wrapped_obj, jDraw2DBufferMethod, buffer, stride);
-}
-
-jobject
-AndroidGeckoSurfaceView::GetSoftwareDrawBitmap()
-{
-    return JNI()->CallObjectMethod(wrapped_obj, jGetSoftwareDrawBitmapMethod);
-}
-
-jobject
-AndroidGeckoSurfaceView::GetSoftwareDrawBuffer()
-{
-    return JNI()->CallObjectMethod(wrapped_obj, jGetSoftwareDrawBufferMethod);
-}
-
-jobject
-AndroidGeckoSurfaceView::GetSurface()
-{
-    return JNI()->CallObjectMethod(wrapped_obj, jGetSurfaceMethod);
-}
-
-jobject
-AndroidGeckoSurfaceView::GetSurfaceHolder()
-{
-    return JNI()->CallObjectMethod(wrapped_obj, jGetHolderMethod);
-}
-
-void
 AndroidPoint::Init(JNIEnv *jenv, jobject jobj)
 {
     NS_ASSERTION(wrapped_obj == nsnull, "Init called on non-null wrapped_obj!");
 
     wrapped_obj = jobj;
 
     if (jobj) {
         mX = jenv->GetIntField(jobj, jXField);
         mY = jenv->GetIntField(jobj, jYField);
     } else {
         mX = 0;
         mY = 0;
     }
 }
 
 void
-AndroidRect::Init(JNIEnv *jenv, jobject jobj)
+AndroidGeckoSoftwareLayerClient::Init(jobject jobj)
 {
     NS_ASSERTION(wrapped_obj == nsnull, "Init called on non-null wrapped_obj!");
 
     wrapped_obj = jobj;
+}
+
+jobject
+AndroidGeckoSoftwareLayerClient::LockBuffer()
+{
+    NS_ASSERTION(!isNull(), "LockBuffer() called on null software layer client!");
+
+    return JNI()->CallObjectMethod(wrapped_obj, jLockBufferMethod);
+}
+
+unsigned char *
+AndroidGeckoSoftwareLayerClient::LockBufferBits()
+{
+    return reinterpret_cast<unsigned char *>(JNI()->GetDirectBufferAddress(LockBuffer()));
+}
+
+void
+AndroidGeckoSoftwareLayerClient::UnlockBuffer()
+{
+    NS_ASSERTION(!isNull(), "UnlockBuffer() called on null software layer client!");
+
+    JNI()->CallVoidMethod(wrapped_obj, jUnlockBufferMethod);
+}
+
+void
+AndroidGeckoSoftwareLayerClient::BeginDrawing()
+{
+    NS_ASSERTION(!isNull(), "BeginDrawing() called on null software layer client!");
+
+    return JNI()->CallVoidMethod(wrapped_obj, jBeginDrawingMethod);
+}
+
+void
+AndroidGeckoSoftwareLayerClient::EndDrawing(const nsIntRect &aRect)
+{
+    NS_ASSERTION(!isNull(), "EndDrawing() called on null software layer client!");
+
+    return JNI()->CallVoidMethod(wrapped_obj, jEndDrawingMethod, aRect.x, aRect.y, aRect.width,
+                                 aRect.height);
+}
+
+void
+AndroidRect::Init(JNIEnv *jenv, jobject jobj)
+{
+    NS_ASSERTION(wrapped_obj == nsnull, "Init called on non-null wrapped_obj!");
+
+    ALOG("AndroidRect::Init point a");
+
+    wrapped_obj = jobj;
+
+    ALOG("AndroidRect::Init point b");
 
     if (jobj) {
+        ALOG("AndroidRect::Init point c");
+
         mTop = jenv->GetIntField(jobj, jTopField);
         mLeft = jenv->GetIntField(jobj, jLeftField);
         mRight = jenv->GetIntField(jobj, jRightField);
         mBottom = jenv->GetIntField(jobj, jBottomField);
+
+        ALOG("AndroidRect::Init point d");
     } else {
         mTop = 0;
         mLeft = 0;
         mRight = 0;
         mBottom = 0;
     }
 }
 
--- a/widget/src/android/AndroidJavaWrappers.h
+++ b/widget/src/android/AndroidJavaWrappers.h
@@ -41,17 +41,17 @@
 #include <jni.h>
 #include <android/log.h>
 
 #include "nsGeoPosition.h"
 #include "nsPoint.h"
 #include "nsRect.h"
 #include "nsString.h"
 
-//#define FORCE_ALOG 1
+#define FORCE_ALOG 1
 
 #ifndef ALOG
 #if defined(DEBUG) || defined(FORCE_ALOG)
 #define ALOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Gecko" , ## args)
 #else
 #define ALOG(args...)
 #endif
 #endif
@@ -144,58 +144,37 @@ protected:
 
     static jclass jRectClass;
     static jfieldID jBottomField;
     static jfieldID jLeftField;
     static jfieldID jRightField;
     static jfieldID jTopField;
 };
 
-class AndroidGeckoSurfaceView : public WrappedJavaObject
-{
+class AndroidGeckoSoftwareLayerClient : public WrappedJavaObject {
 public:
-    static void InitGeckoSurfaceViewClass(JNIEnv *jEnv);
-
-    AndroidGeckoSurfaceView() { }
-    AndroidGeckoSurfaceView(jobject jobj) {
-        Init(jobj);
-    }
+    static void InitGeckoSoftwareLayerClientClass(JNIEnv *jEnv);
 
     void Init(jobject jobj);
 
-    enum {
-        DRAW_ERROR = 0,
-        DRAW_GLES_2 = 1,
-        DRAW_2D = 2,
-        DRAW_DISABLED = 3
-    };
+    AndroidGeckoSoftwareLayerClient() {}
+    AndroidGeckoSoftwareLayerClient(jobject jobj) { Init(jobj); }
 
-    int BeginDrawing();
-    jobject GetSoftwareDrawBitmap();
-    jobject GetSoftwareDrawBuffer();
-    void EndDrawing();
-    void Draw2D(jobject bitmap, int width, int height);
-    void Draw2D(jobject buffer, int stride);
+    jobject LockBuffer();
+    unsigned char *LockBufferBits();
+    void UnlockBuffer();
+    void BeginDrawing();
+    void EndDrawing(const nsIntRect &aRect);
 
-    jobject GetSurface();
-
-    // must have a JNI local frame when calling this,
-    // and you'd better know what you're doing
-    jobject GetSurfaceHolder();
-
-protected:
-    static jclass jGeckoSurfaceViewClass;
+private:
+    static jclass jGeckoSoftwareLayerClientClass;
+    static jmethodID jLockBufferMethod;
+    static jmethodID jUnlockBufferMethod;
     static jmethodID jBeginDrawingMethod;
     static jmethodID jEndDrawingMethod;
-    static jmethodID jDraw2DBitmapMethod;
-    static jmethodID jDraw2DBufferMethod;
-    static jmethodID jGetSoftwareDrawBitmapMethod;
-    static jmethodID jGetSoftwareDrawBufferMethod;
-    static jmethodID jGetSurfaceMethod;
-    static jmethodID jGetHolderMethod;
 };
 
 class AndroidKeyEvent
 {
 public:
     enum {
         KEYCODE_UNKNOWN            = 0,
         KEYCODE_SOFT_LEFT          = 1,
@@ -380,29 +359,29 @@ class AndroidGeckoEvent : public Wrapped
 {
 public:
     static void InitGeckoEventClass(JNIEnv *jEnv);
 
     AndroidGeckoEvent() { }
     AndroidGeckoEvent(int aType) {
         Init(aType);
     }
-    AndroidGeckoEvent(int x1, int y1, int x2, int y2) {
-        Init(x1, y1, x2, y2);
+    AndroidGeckoEvent(int aType, const nsIntRect &aRect) {
+        Init(aType, aRect);
     }
     AndroidGeckoEvent(JNIEnv *jenv, jobject jobj) {
         Init(jenv, jobj);
     }
     AndroidGeckoEvent(AndroidGeckoEvent *aResizeEvent) {
         Init(aResizeEvent);
     }
 
     void Init(JNIEnv *jenv, jobject jobj);
     void Init(int aType);
-    void Init(int x1, int y1, int x2, int y2);
+    void Init(int aType, const nsIntRect &aRect);
     void Init(AndroidGeckoEvent *aResizeEvent);
 
     int Action() { return mAction; }
     int Type() { return mType; }
     int64_t Time() { return mTime; }
     const nsIntPoint& P0() { return mP0; }
     const nsIntPoint& P1() { return mP1; }
     double Alpha() { return mAlpha; }
--- a/widget/src/android/nsAppShell.cpp
+++ b/widget/src/android/nsAppShell.cpp
@@ -55,16 +55,18 @@
 #include <pthread.h>
 #include <wchar.h>
 
 #ifdef MOZ_LOGGING
 #define FORCE_PR_LOG
 #include "prlog.h"
 #endif
 
+#define DEBUG_ANDROID_EVENTS 1
+
 #ifdef DEBUG_ANDROID_EVENTS
 #define EVLOG(args...)  ALOG(args)
 #else
 #define EVLOG(args...) do { } while (0)
 #endif
 
 using namespace mozilla;
 
--- a/widget/src/android/nsWindow.cpp
+++ b/widget/src/android/nsWindow.cpp
@@ -71,16 +71,22 @@ using mozilla::unused;
 #include "GLContextProvider.h"
 
 #include "nsTArray.h"
 
 #include "AndroidBridge.h"
 
 #include "imgIEncoder.h"
 
+#include "nsStringGlue.h"
+
+// NB: Keep these in sync with LayerController.java in embedding/android/.
+#define TILE_WIDTH      1024
+#define TILE_HEIGHT     2048
+
 using namespace mozilla;
 
 NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, nsBaseWidget)
 
 // The dimensions of the current android view
 static gfxIntSize gAndroidBounds;
 static gfxIntSize gAndroidScreenBounds;
 
@@ -130,17 +136,16 @@ static bool gMenuConsumed;
 // stacking order, so the window at gAndroidBounds[0] is the topmost
 // one.
 static nsTArray<nsWindow*> gTopLevelWindows;
 
 static nsRefPtr<gl::GLContext> sGLContext;
 static bool sFailedToCreateGLContext = false;
 static bool sValidSurface;
 static bool sSurfaceExists = false;
-static void *sNativeWindow = nsnull;
 
 // Multitouch swipe thresholds in inches
 static const double SWIPE_MAX_PINCH_DELTA_INCHES = 0.4;
 static const double SWIPE_MIN_DISTANCE_INCHES = 0.6;
 
 static nsWindow*
 TopWindow()
 {
@@ -291,16 +296,25 @@ nsWindow::ConfigureChildren(const nsTArr
                          config[i].mBounds.width,
                          config[i].mBounds.height,
                          false);
     }
 
     return NS_OK;
 }
 
+void
+nsWindow::RedrawAll()
+{
+    nsIntRect entireRect(0, 0, TILE_WIDTH, TILE_HEIGHT);
+    AndroidGeckoEvent *event = new AndroidGeckoEvent(AndroidGeckoEvent::DRAW,
+                                                     entireRect);
+    nsAppShell::gAppShell->PostEvent(event);
+}
+
 NS_IMETHODIMP
 nsWindow::SetParent(nsIWidget *aNewParent)
 {
     if ((nsIWidget*)mParent == aNewParent)
         return NS_OK;
 
     // If we had a parent before, remove ourselves from its list of
     // children.  If we didn't have a parent, then remove ourselves
@@ -311,17 +325,17 @@ nsWindow::SetParent(nsIWidget *aNewParen
 
     mParent = (nsWindow*)aNewParent;
 
     if (mParent)
         mParent->mChildren.AppendElement(this);
 
     // if we are now in the toplevel window's hierarchy, schedule a redraw
     if (FindTopLevel() == TopWindow())
-        nsAppShell::gAppShell->PostEvent(new AndroidGeckoEvent(-1, -1, -1, -1));
+        RedrawAll();
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWindow::ReparentNativeWidget(nsIWidget *aNewParent)
 {
     NS_PRECONDITION(aNewParent, "");
@@ -379,17 +393,17 @@ nsWindow::Show(bool aState)
                 if (!win->mIsVisible)
                     continue;
 
                 win->BringToFront();
                 break;
             }
         }
     } else if (FindTopLevel() == TopWindow()) {
-        nsAppShell::gAppShell->PostEvent(new AndroidGeckoEvent(-1, -1, -1, -1));
+        RedrawAll();
     }
 
 #ifdef ACCESSIBILITY
     static bool sAccessibilityChecked = false;
 
     if (!sAccessibilityChecked) {
         sAccessibilityChecked = true;
         sAccessibilityEnabled =
@@ -506,17 +520,17 @@ nsWindow::Resize(PRInt32 aX,
     mBounds.width = aWidth;
     mBounds.height = aHeight;
 
     if (needSizeDispatch)
         OnSizeChanged(gfxIntSize(aWidth, aHeight));
 
     // Should we skip honoring aRepaint here?
     if (aRepaint && FindTopLevel() == TopWindow())
-        nsAppShell::gAppShell->PostEvent(new AndroidGeckoEvent(-1, -1, -1, -1));
+        RedrawAll();
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWindow::SetZIndex(PRInt32 aZIndex)
 {
     ALOG("nsWindow[%p]::SetZIndex %d ignored", (void*)this, aZIndex);
@@ -559,17 +573,18 @@ nsWindow::IsEnabled(bool *aState)
     *aState = true;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWindow::Invalidate(const nsIntRect &aRect,
                      bool aIsSynchronous)
 {
-    nsAppShell::gAppShell->PostEvent(new AndroidGeckoEvent(-1, -1, -1, -1));
+    AndroidGeckoEvent *event = new AndroidGeckoEvent(AndroidGeckoEvent::DRAW, aRect);
+    nsAppShell::gAppShell->PostEvent(event);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWindow::Update()
 {
     return NS_OK;
 }
@@ -628,17 +643,17 @@ nsWindow::BringToFront()
         DispatchEvent(&event);
     }
 
     nsGUIEvent event(true, NS_ACTIVATE, this);
     DispatchEvent(&event);
 
     // force a window resize
     nsAppShell::gAppShell->ResendLastResizeEvent(this);
-    nsAppShell::gAppShell->PostEvent(new AndroidGeckoEvent(-1, -1, -1, -1));
+    RedrawAll();
 }
 
 NS_IMETHODIMP
 nsWindow::GetScreenBounds(nsIntRect &aRect)
 {
     nsIntPoint p = WidgetToScreenOffset();
 
     aRect.x = p.x;
@@ -792,17 +807,18 @@ nsWindow::DrawToFile(const nsAString &pa
         new gfxImageSurface(gfxIntSize(mBounds.width, mBounds.height),
                             gfxImageSurface::ImageFormatARGB32);
     
     if (imgSurface->CairoStatus()) {
         ALOG("### Failed to create a valid surface");
         return PR_FALSE;
     }
 
-    bool result = DrawTo(imgSurface);
+    nsIntRect boundsRect(0, 0, mBounds.width, mBounds.height);
+    bool result = DrawTo(imgSurface, boundsRect);
     NS_ENSURE_TRUE(result, PR_FALSE);
 
     nsCOMPtr<imgIEncoder> encoder = do_CreateInstance("@mozilla.org/image/encoder;2?type=image/png");
     NS_ENSURE_TRUE(encoder, PR_FALSE);
 
     encoder->InitFromData(imgSurface->Data(),
                           imgSurface->Stride() * mBounds.height,
                           mBounds.width,
@@ -952,37 +968,22 @@ nsWindow::OnGlobalAndroidEvent(AndroidGe
             } else {
                 NS_WARNING("Sending unexpected IME event to top window");
                 win->OnIMEEvent(ae);
             }
             break;
 
         case AndroidGeckoEvent::SURFACE_CREATED:
             sSurfaceExists = true;
-
-            if (AndroidBridge::Bridge()->HasNativeWindowAccess()) {
-                AndroidGeckoSurfaceView& sview(AndroidBridge::Bridge()->SurfaceView());
-                jobject surface = sview.GetSurface();
-                if (surface) {
-                    sNativeWindow = AndroidBridge::Bridge()->AcquireNativeWindow(surface);
-                    if (sNativeWindow) {
-                        AndroidBridge::Bridge()->SetNativeWindowFormat(sNativeWindow, AndroidBridge::WINDOW_FORMAT_RGB_565);
-                    }
-                }
-            }
             break;
 
         case AndroidGeckoEvent::SURFACE_DESTROYED:
             if (sGLContext && sValidSurface) {
                 sGLContext->ReleaseSurface();
             }
-            if (sNativeWindow) {
-                AndroidBridge::Bridge()->ReleaseNativeWindow(sNativeWindow);
-                sNativeWindow = nsnull;
-            }
             sSurfaceExists = false;
             sValidSurface = false;
             break;
 
         case AndroidGeckoEvent::GECKO_EVENT_SYNC:
             AndroidBridge::Bridge()->AcknowledgeEventSync();
             break;
 
@@ -1008,21 +1009,25 @@ nsWindow::OnAndroidEvent(AndroidGeckoEve
 
         default:
             ALOG("Window got targetted android event type %d, but didn't handle!", ae->Type());
             break;
     }
 }
 
 bool
-nsWindow::DrawTo(gfxASurface *targetSurface)
+nsWindow::DrawTo(gfxASurface *targetSurface, const nsIntRect &invalidRect)
 {
     if (!mIsVisible)
         return false;
 
+    nsWindowType windowType;
+    GetWindowType(windowType);
+    ALOG("Window type is %d", (int)windowType);
+
     nsEventStatus status;
     nsIntRect boundsRect(0, 0, mBounds.width, mBounds.height);
 
     // Figure out if any of our children cover this widget completely
     PRInt32 coveringChildIndex = -1;
     for (PRUint32 i = 0; i < mChildren.Length(); ++i) {
         if (mChildren[i]->mBounds.IsEmpty())
             continue;
@@ -1030,17 +1035,17 @@ nsWindow::DrawTo(gfxASurface *targetSurf
         if (mChildren[i]->mBounds.Contains(boundsRect)) {
             coveringChildIndex = PRInt32(i);
         }
     }
 
     // If we have no covering child, then we need to render this.
     if (coveringChildIndex == -1) {
         nsPaintEvent event(true, NS_PAINT, this);
-        event.region = boundsRect;
+        event.region = boundsRect.Intersect(invalidRect);
         switch (GetLayerManager(nsnull)->GetBackendType()) {
             case LayerManager::LAYERS_BASIC: {
                 nsRefPtr<gfxContext> ctx = new gfxContext(targetSurface);
 
                 {
                     AutoLayerManagerSetup
                       setupLayerManager(this, ctx, BasicLayerManager::BUFFER_NONE);
                     status = DispatchEvent(&event);
@@ -1085,141 +1090,67 @@ nsWindow::DrawTo(gfxASurface *targetSurf
             !mChildren[i]->mBounds.Intersects(boundsRect)) {
             continue;
         }
 
         if (targetSurface)
             targetSurface->SetDeviceOffset(offset + gfxPoint(mChildren[i]->mBounds.x,
                                                              mChildren[i]->mBounds.y));
 
-        bool ok = mChildren[i]->DrawTo(targetSurface);
+        bool ok = mChildren[i]->DrawTo(targetSurface, invalidRect);
 
         if (!ok) {
             ALOG("nsWindow[%p]::DrawTo child %d[%p] returned FALSE!", (void*) this, i, (void*)mChildren[i]);
         }
     }
 
     if (targetSurface)
         targetSurface->SetDeviceOffset(offset);
 
     return true;
 }
 
 void
 nsWindow::OnDraw(AndroidGeckoEvent *ae)
 {
-  
-    if (!sSurfaceExists) {
-        return;
-    }
-
     if (!IsTopLevel()) {
         ALOG("##### redraw for window %p, which is not a toplevel window -- sending to toplevel!", (void*) this);
         DumpWindows();
         return;
     }
 
     if (!mIsVisible) {
         ALOG("##### redraw for window %p, which is not visible -- ignoring!", (void*) this);
         DumpWindows();
         return;
     }
 
     AndroidBridge::AutoLocalJNIFrame jniFrame;
 
-    AndroidGeckoSurfaceView& sview(AndroidBridge::Bridge()->SurfaceView());
-
-    NS_ASSERTION(!sview.isNull(), "SurfaceView is null!");
-
     if (GetLayerManager(nsnull)->GetBackendType() == LayerManager::LAYERS_BASIC) {
-        if (sNativeWindow) {
-            unsigned char *bits;
-            int width, height, format, stride;
-            if (!AndroidBridge::Bridge()->LockWindow(sNativeWindow, &bits, &width, &height, &format, &stride)) {
-                ALOG("failed to lock buffer - skipping draw");
-                return;
-            }
-
-            if (!bits || format != AndroidBridge::WINDOW_FORMAT_RGB_565 ||
-                width != mBounds.width || height != mBounds.height) {
-
-                ALOG("surface is not expected dimensions or format - skipping draw");
-                AndroidBridge::Bridge()->UnlockWindow(sNativeWindow);
-                return;
-            }
+        AndroidGeckoSoftwareLayerClient &client =
+            AndroidBridge::Bridge()->GetSoftwareLayerClient();
 
-            nsRefPtr<gfxImageSurface> targetSurface =
-                new gfxImageSurface(bits,
-                                    gfxIntSize(mBounds.width, mBounds.height),
-                                    stride * 2,
-                                    gfxASurface::ImageFormatRGB16_565);
-            if (targetSurface->CairoStatus()) {
-                ALOG("### Failed to create a valid surface from the bitmap");
-            } else {
-                DrawTo(targetSurface);
-            }
-
-            AndroidBridge::Bridge()->UnlockWindow(sNativeWindow);
-        } else if (AndroidBridge::Bridge()->HasNativeBitmapAccess()) {
-            jobject bitmap = sview.GetSoftwareDrawBitmap();
-            if (!bitmap) {
-                ALOG("no bitmap to draw into - skipping draw");
-                return;
-            }
-
-            if (!AndroidBridge::Bridge()->ValidateBitmap(bitmap, mBounds.width, mBounds.height))
-                return;
+        client.BeginDrawing();
+        unsigned char *bits = client.LockBufferBits();
 
-            void *buf = AndroidBridge::Bridge()->LockBitmap(bitmap);
-            if (buf == nsnull) {
-                ALOG("### Software drawing, but failed to lock bitmap.");
-                return;
-            }
-
-            nsRefPtr<gfxImageSurface> targetSurface =
-                new gfxImageSurface((unsigned char *)buf,
-                                    gfxIntSize(mBounds.width, mBounds.height),
-                                    mBounds.width * 2,
-                                    gfxASurface::ImageFormatRGB16_565);
-            if (targetSurface->CairoStatus()) {
-                ALOG("### Failed to create a valid surface from the bitmap");
-            } else {
-                DrawTo(targetSurface);
-            }
-
-            AndroidBridge::Bridge()->UnlockBitmap(bitmap);
-            sview.Draw2D(bitmap, mBounds.width, mBounds.height);
+        nsRefPtr<gfxImageSurface> targetSurface =
+            new gfxImageSurface(bits, gfxIntSize(TILE_WIDTH, TILE_HEIGHT), TILE_WIDTH * 2,
+                                gfxASurface::ImageFormatRGB16_565);
+        if (targetSurface->CairoStatus()) {
+            ALOG("### Failed to create a valid surface from the bitmap");
         } else {
-            jobject bytebuf = sview.GetSoftwareDrawBuffer();
-            if (!bytebuf) {
-                ALOG("no buffer to draw into - skipping draw");
-                return;
-            }
-
-            void *buf = AndroidBridge::JNI()->GetDirectBufferAddress(bytebuf);
-            int cap = AndroidBridge::JNI()->GetDirectBufferCapacity(bytebuf);
-            if (!buf || cap != (mBounds.width * mBounds.height * 2)) {
-                ALOG("### Software drawing, but unexpected buffer size %d expected %d (or no buffer %p)!", cap, mBounds.width * mBounds.height * 2, buf);
-                return;
-            }
+            DrawTo(targetSurface, ae->Rect());
+        }
 
-            nsRefPtr<gfxImageSurface> targetSurface =
-                new gfxImageSurface((unsigned char *)buf,
-                                    gfxIntSize(mBounds.width, mBounds.height),
-                                    mBounds.width * 2,
-                                    gfxASurface::ImageFormatRGB16_565);
-            if (targetSurface->CairoStatus()) {
-                ALOG("### Failed to create a valid surface");
-            } else {
-                DrawTo(targetSurface);
-            }
-
-            sview.Draw2D(bytebuf, mBounds.width * 2);
-        }
+        client.UnlockBuffer();
+        client.EndDrawing(ae->Rect());
     } else {
+        ALOG("### GL layers are disabled for now in the native UI Fennec");
+#if 0
         int drawType = sview.BeginDrawing();
 
         if (drawType == AndroidGeckoSurfaceView::DRAW_DISABLED) {
             return;
         }
 
         if (drawType == AndroidGeckoSurfaceView::DRAW_ERROR) {
             ALOG("##### BeginDrawing failed!");
@@ -1229,19 +1160,20 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae)
         if (!sValidSurface) {
             sGLContext->RenewSurface();
             sValidSurface = true;
         }
 
 
         NS_ASSERTION(sGLContext, "Drawing with GLES without a GL context?");
 
-        DrawTo(nsnull);
+        DrawTo(nsnull, ae->P0(), ae->Alpha());
 
         sview.EndDrawing();
+#endif
     }
 }
 
 void
 nsWindow::OnSizeChanged(const gfxIntSize& aSize)
 {
     int w = aSize.width;
     int h = aSize.height;
--- a/widget/src/android/nsWindow.h
+++ b/widget/src/android/nsWindow.h
@@ -173,17 +173,17 @@ public:
 
     NS_IMETHOD ReparentNativeWidget(nsIWidget* aNewParent);
 #ifdef ACCESSIBILITY
     static bool sAccessibilityEnabled;
 #endif
 protected:
     void BringToFront();
     nsWindow *FindTopLevel();
-    bool DrawTo(gfxASurface *targetSurface);
+    bool DrawTo(gfxASurface *targetSurface, const nsIntRect &aRect);
     bool DrawToFile(const nsAString &path);
     bool IsTopLevel();
     void OnIMEAddRange(mozilla::AndroidGeckoEvent *ae);
 
     // Call this function when the users activity is the direct cause of an
     // event (like a keypress or mouse click).
     void UserActivity();
 
@@ -215,16 +215,17 @@ protected:
     static void LogWindow(nsWindow *win, int index, int indent);
 
 private:
     void InitKeyEvent(nsKeyEvent& event, mozilla::AndroidGeckoEvent& key);
     void DispatchGestureEvent(mozilla::AndroidGeckoEvent *ae);
     void DispatchGestureEvent(PRUint32 msg, PRUint32 direction, double delta,
                                const nsIntPoint &refPoint, PRUint64 time);
     void HandleSpecialKey(mozilla::AndroidGeckoEvent *ae);
+    void RedrawAll();
 
 #ifdef ACCESSIBILITY
     nsRefPtr<nsAccessible> mRootAccessible;
 
     /**
      * Request to create the accessible for this window if it is top level.
      */
     void CreateRootAccessible();