reverting enbedding/android to pre-birch IGNORE BAD COMMIT MESSAGES
authorBrad Lassey <blassey@mozilla.com>
Tue, 06 Dec 2011 01:37:14 -0500
changeset 82828 0e397568c71ed693dd8da831d3f2731d5cff6604
parent 82827 c29569f9df99b5811d674054175032c1eac0138e
child 82829 d3191b5fa8aeb908195e88e60a8376bc9b66b8ed
push id628
push userclegnitto@mozilla.com
push dateWed, 21 Dec 2011 14:41:57 +0000
treeherdermozilla-aurora@24a61ad789e8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone11.0a1
reverting enbedding/android to pre-birch IGNORE BAD COMMIT MESSAGES
embedding/android/AlertNotification.java
embedding/android/AndroidManifest.xml.in
embedding/android/App.java.in
embedding/android/CrashReporter.java.in
embedding/android/GeckoApp.java
embedding/android/GeckoAppShell.java
embedding/android/GeckoBatteryManager.java
embedding/android/GeckoConnectivityReceiver.java
embedding/android/GeckoEvent.java
embedding/android/GeckoInputConnection.java
embedding/android/GeckoSmsManager.java
embedding/android/GeckoSurfaceView.java
embedding/android/LauncherShortcuts.java.in
embedding/android/Makefile.in
embedding/android/NotificationHandler.java.in
embedding/android/Restarter.java.in
embedding/android/SurfaceInfo.java
embedding/android/locales/Makefile.in
embedding/android/locales/en-US/android_strings.dtd
embedding/android/locales/jar.mn
embedding/android/locales/l10n.ini
embedding/android/resources/drawable/crash_reporter.png
embedding/android/resources/drawable/desktop_notification.png
embedding/android/resources/layout/crash_reporter.xml
embedding/android/resources/layout/launch_app_list.xml
embedding/android/resources/layout/launch_app_listitem.xml
embedding/android/resources/layout/notification_icon_text.xml
embedding/android/resources/layout/notification_progress.xml
embedding/android/resources/layout/notification_progress_text.xml
embedding/android/resources/values/colors.xml
embedding/android/resources/values/themes.xml
embedding/android/strings.xml.in
new file mode 100644
--- /dev/null
+++ b/embedding/android/AlertNotification.java
@@ -0,0 +1,145 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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 Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Alex Pakhotin <alexp@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.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.graphics.*;
+import android.net.Uri;
+import android.util.Log;
+import android.widget.RemoteViews;
+import java.net.*;
+import java.text.NumberFormat;
+
+public class AlertNotification
+    extends Notification
+{
+    Context mContext;
+    int mId;
+    int mIcon;
+    String mTitle;
+    String mText;
+    boolean mProgressStyle;
+    NotificationManager mNotificationManager;
+    double mPrevPercent  = -1;
+    String mPrevAlertText = "";
+    static final double UPDATE_THRESHOLD = .01;
+
+    public AlertNotification(Context aContext, int aNotificationId, int aIcon,
+                             String aTitle, String aText, long aWhen) {
+        super(aIcon, (aText.length() > 0) ? aText : aTitle, aWhen);
+
+        mContext = aContext;
+        mIcon = aIcon;
+        mTitle = aTitle;
+        mText = aText;
+        mProgressStyle = false;
+        mId = aNotificationId;
+
+        mNotificationManager = (NotificationManager)
+            mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+    }
+
+    public boolean isProgressStyle() {
+        return mProgressStyle;
+    }
+
+    public void show() {
+        mNotificationManager.notify(mId, this);
+    }
+
+    public void setCustomIcon(Uri aIconUri) {
+        if (aIconUri == null || aIconUri.getScheme() == null)
+            return;
+
+        // Custom view
+        int layout = R.layout.notification_icon_text;
+        RemoteViews view = new RemoteViews(GeckoApp.mAppContext.getPackageName(), layout);
+        try {
+            URL url = new URL(aIconUri.toString());
+            Bitmap bm = BitmapFactory.decodeStream(url.openStream());
+            view.setImageViewBitmap(R.id.notificationImage, bm);
+            view.setTextViewText(R.id.notificationTitle, mTitle);
+            if (mText.length() > 0) {
+                view.setTextViewText(R.id.notificationText, mText);
+            }
+            contentView = view;
+            mNotificationManager.notify(mId, this); 
+        } catch(Exception ex) {
+            Log.e("GeckoAlert", "failed to create bitmap", ex);
+        }
+    }
+
+    public void updateProgress(String aAlertText, long aProgress, long aProgressMax) {
+        if (!mProgressStyle) {
+            // Custom view
+            int layout =  aAlertText.length() > 0 ? R.layout.notification_progress_text : R.layout.notification_progress;
+
+            RemoteViews view = new RemoteViews(GeckoApp.mAppContext.getPackageName(), layout);
+            view.setImageViewResource(R.id.notificationImage, mIcon);
+            view.setTextViewText(R.id.notificationTitle, mTitle);
+            contentView = view;
+            flags |= FLAG_ONGOING_EVENT | FLAG_ONLY_ALERT_ONCE;
+
+            mProgressStyle = true;
+        }
+
+        String text;
+        double percent = 0;
+        if (aProgressMax > 0)
+            percent = ((double)aProgress / (double)aProgressMax);
+
+        if (aAlertText.length() > 0)
+            text = aAlertText;
+        else
+            text = NumberFormat.getPercentInstance().format(percent); 
+
+        if (mPrevAlertText.equals(text) && Math.abs(mPrevPercent - percent) < UPDATE_THRESHOLD)
+            return;
+
+        contentView.setTextViewText(R.id.notificationText, text);
+        contentView.setProgressBar(R.id.notificationProgressbar, (int)aProgressMax, (int)aProgress, false);
+
+        // Update the notification
+        mNotificationManager.notify(mId, this);
+
+        mPrevPercent = percent;
+        mPrevAlertText = text;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/embedding/android/AndroidManifest.xml.in
@@ -0,0 +1,129 @@
+#filter substitution
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="@ANDROID_PACKAGE_NAME@"
+      android:installLocation="auto"
+      android:versionCode="@ANDROID_VERSION_CODE@"
+      android:versionName="@MOZ_APP_VERSION@"
+#ifdef MOZ_ANDROID_SHARED_ID
+      android:sharedUserId="@MOZ_ANDROID_SHARED_ID@"
+#endif
+      >
+    <uses-sdk android:minSdkVersion="5"
+              android:targetSdkVersion="11"/>
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
+
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.VIBRATE"/>
+
+    <!-- WebSMS -->
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+
+    <uses-feature android:name="android.hardware.location" android:required="false"/>
+    <uses-feature android:name="android.hardware.location.gps" android:required="false"/>
+    <uses-feature android:name="android.hardware.touchscreen"/>
+
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-feature android:name="android.hardware.camera" android:required="false"/>
+    <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
+ 
+    <application android:label="@MOZ_APP_DISPLAYNAME@"
+		 android:icon="@drawable/icon"
+#if MOZILLA_OFFICIAL
+		 android:debuggable="false">
+#else
+		 android:debuggable="true">
+#endif
+
+        <activity android:name="App"
+                  android:label="@MOZ_APP_DISPLAYNAME@"
+                  android:configChanges="keyboard|keyboardHidden|mcc|mnc"
+                  android:windowSoftInputMode="stateUnspecified|adjustResize"
+                  android:launchMode="singleTask"
+                  android:theme="@style/GreyTheme">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+
+            <!-- Default browser intents -->
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:scheme="about" />
+                <data android:scheme="javascript" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="file" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:mimeType="text/html"/>
+                <data android:mimeType="text/plain"/>
+                <data android:mimeType="application/xhtml+xml"/>
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.WEB_SEARCH" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+            </intent-filter>
+
+            <!-- For debugging -->
+            <intent-filter>
+                <action android:name="org.mozilla.gecko.DEBUG" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <receiver android:name="NotificationHandler">
+            <intent-filter>
+                <action android:name="org.mozilla.gecko.ACTION_ALERT_CLICK" />
+                <action android:name="org.mozilla.gecko.ACTION_ALERT_CLEAR" />
+            </intent-filter>
+        </receiver>
+
+        <activity android:name="Restarter"
+                  android:process="@ANDROID_PACKAGE_NAME@Restarter"
+                  android:theme="@style/GreyTheme"
+                  android:excludeFromRecents="true">
+          <intent-filter>
+            <action android:name="org.mozilla.gecko.restart"/>
+          </intent-filter>
+        </activity>
+
+#if MOZ_CRASHREPORTER
+  <activity android:name="CrashReporter"
+            android:label="@string/crash_reporter_title"
+            android:icon="@drawable/crash_reporter"
+            android:excludeFromRecents="true">
+          <intent-filter>
+            <action android:name="org.mozilla.gecko.reportCrash" />
+          </intent-filter>
+	</activity>
+#endif
+
+        <activity android:name="LauncherShortcuts"
+                  android:label="@string/launcher_shortcuts_title"
+                  android:theme="@android:style/Theme.Translucent">
+            <!--  This intent-filter allows your shortcuts to be created in the launcher. -->
+            <intent-filter>
+                <action android:name="android.intent.action.CREATE_SHORTCUT" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest> 
new file mode 100644
--- /dev/null
+++ b/embedding/android/App.java.in
@@ -0,0 +1,51 @@
+/* -*- 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):
+ *   Brad Lassey <blassey@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 ***** */
+
+#filter substitution
+package @ANDROID_PACKAGE_NAME@;
+
+import org.mozilla.gecko.GeckoApp;
+
+public class App extends GeckoApp {
+    public String getPackageName() {
+	return "@ANDROID_PACKAGE_NAME@";
+    }
+    public String getContentProcessName() {
+        return "@MOZ_CHILD_PROCESS_NAME@";
+    }
+};
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/CrashReporter.java.in
@@ -0,0 +1,336 @@
+/* -*- Mode: Java; 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):
+ *   Brad Lassey <blassey@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 ***** */
+
+#filter substitution
+package @ANDROID_PACKAGE_NAME@;
+
+
+import android.app.*;
+import android.content.*;
+import android.os.*;
+import android.util.*;
+import android.view.*;
+import android.view.View.*;
+import android.widget.*;
+
+import org.mozilla.gecko.*;
+import java.util.*;
+import java.io.*;
+import java.net.*;
+import java.nio.channels.*;
+
+public class CrashReporter extends Activity
+{
+  static final String kMiniDumpPathKey = "upload_file_minidump";
+  static final String kPageURLKey = "URL";
+  static final String kNotesKey = "Notes";
+  Handler mHandler = null;
+  ProgressDialog mProgressDialog;
+  File mPendingMinidumpFile;
+  File mPendingExtrasFile;
+  HashMap<String, String> mExtrasStringMap;
+
+  boolean moveFile(File inFile, File outFile)
+  {
+    Log.i("GeckoCrashReporter", "moving " + inFile + " to " + outFile);
+    if (inFile.renameTo(outFile))
+      return true;
+    try {
+      outFile.createNewFile();
+      Log.i("GeckoCrashReporter", "couldn't rename minidump file");
+      // so copy it instead
+      FileChannel inChannel = new FileInputStream(inFile).getChannel();
+      FileChannel outChannel = new FileOutputStream(outFile).getChannel();
+      long transferred = inChannel.transferTo(0, inChannel.size(), outChannel);
+      inChannel.close();
+      outChannel.close();
+
+      if (transferred > 0)
+        inFile.delete();
+    } catch (Exception e) {
+      Log.e("GeckoCrashReporter",
+            "exception while copying minidump file: ", e);
+      return false;
+    }
+    return true;
+  }
+
+  void doFinish() {
+    if (mHandler != null) {
+      mHandler.post(new Runnable(){
+        public void run() {
+          finish();
+        }});
+    }
+  }
+
+  @Override
+  public void finish()
+  {
+    mProgressDialog.dismiss();
+    super.finish();
+  }
+
+  @Override
+  public void onCreate(Bundle savedInstanceState)
+  {
+    super.onCreate(savedInstanceState);
+    // mHandler is created here so runnables can be run on the main thread
+    mHandler = new Handler();
+    setContentView(R.layout.crash_reporter);
+    mProgressDialog = new ProgressDialog(CrashReporter.this);
+    mProgressDialog.setMessage(getString(R.string.sending_crash_report));
+
+    final Button restartButton = (Button) findViewById(R.id.restart);
+    final Button closeButton = (Button) findViewById(R.id.close);
+    String passedMinidumpPath = getIntent().getStringExtra("minidumpPath");
+    File passedMinidumpFile = new File(passedMinidumpPath);
+    File pendingDir =
+      new File("/data/data/@ANDROID_PACKAGE_NAME@/files/mozilla/Crash Reports/pending");
+    pendingDir.mkdirs();
+    mPendingMinidumpFile = new File(pendingDir, passedMinidumpFile.getName());
+    moveFile(passedMinidumpFile, mPendingMinidumpFile);
+
+    File extrasFile = new File(passedMinidumpPath.replaceAll(".dmp", ".extra"));
+    mPendingExtrasFile = new File(pendingDir, extrasFile.getName());
+    moveFile(extrasFile, mPendingExtrasFile);
+
+    mExtrasStringMap = new HashMap<String, String>();
+    readStringsFromFile(mPendingExtrasFile.getPath(), mExtrasStringMap);
+  }
+
+  void backgroundSendReport()
+  {
+    final CheckBox sendReportCheckbox = (CheckBox) findViewById(R.id.send_report);
+    if (!sendReportCheckbox.isChecked()) {
+      doFinish();
+      return;
+    }
+
+    mProgressDialog.show();
+    new Thread(new Runnable() {
+      public void run() {
+        sendReport(mPendingMinidumpFile, mExtrasStringMap, mPendingExtrasFile);
+      }}).start();
+  }
+
+  public void onCloseClick(View v)
+  {
+    backgroundSendReport();
+  }
+
+  public void onRestartClick(View v)
+  {
+    doRestart();
+    backgroundSendReport();
+  }
+
+  boolean readStringsFromFile(String filePath, Map stringMap)
+  {
+    try {
+      BufferedReader reader = new BufferedReader(
+        new FileReader(filePath));
+      return readStringsFromReader(reader, stringMap);
+    } catch (Exception e) {
+      Log.e("GeckoCrashReporter", "exception while reading strings: ", e);
+      return false;
+    }
+  }
+
+  boolean readStringsFromReader(BufferedReader reader, Map stringMap)
+    throws java.io.IOException
+  {
+    String line;
+    while ((line = reader.readLine()) != null) {
+      int equalsPos = -1;
+      if ((equalsPos = line.indexOf('=')) != -1) {
+        String key = line.substring(0, equalsPos);
+        String val = unescape(line.substring(equalsPos + 1));
+        stringMap.put(key, val);
+      }
+    }
+    reader.close();
+    return true;
+  }
+
+  String generateBoundary()
+  {
+    // Generate some random numbers to fill out the boundary
+    int r0 = (int)((double)Integer.MAX_VALUE * Math.random());
+    int r1 = (int)((double)Integer.MAX_VALUE * Math.random());
+
+    return String.format("---------------------------%08X%08X", r0, r1);
+  }
+
+  void sendPart(OutputStream os, String boundary, String name, String data)
+  {
+    try {
+      os.write(("--" + boundary + "\r\n" +
+                "Content-Disposition: form-data; name=\"" +
+                name + "\"\r\n\r\n" +
+                data + "\r\n").getBytes());
+    } catch (Exception ex) {
+      Log.e("GeckoCrashReporter", "Exception when sending \"" + name + "\"", ex);
+    }
+  }
+
+  void sendFile(OutputStream os, String boundary, String name, File file)
+    throws IOException
+  {
+    os.write(("--" + boundary + "\r\n" +
+              "Content-Disposition: form-data; " +
+              "name=\"" + name + "\"; " +
+              "filename=\"" + file.getName() + "\"\r\n" +
+              "Content-Type: application/octet-stream\r\n" +
+              "\r\n").getBytes());
+    FileChannel fc =
+      new FileInputStream(file).getChannel();
+    fc.transferTo(0, fc.size(), Channels.newChannel(os));
+    fc.close();
+  }
+
+  void sendReport(File minidumpFile, Map<String, String> extras,
+                  File extrasFile)
+  {
+    Log.i("GeckoCrashReport", "sendReport: " + minidumpFile.getPath());
+    final CheckBox includeURLCheckbox = (CheckBox) findViewById(R.id.include_url);
+
+    String spec = extras.get("ServerURL");
+    if (spec == null)
+      doFinish();
+
+    Log.i("GeckoCrashReport", "server url: " + spec);
+    try {
+      URL url = new URL(spec);
+      HttpURLConnection conn = (HttpURLConnection)url.openConnection();
+      conn.setRequestMethod("POST");
+      String boundary = generateBoundary();
+      conn.setDoOutput(true);
+      conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
+
+      OutputStream os = conn.getOutputStream();
+      Iterator<String> keys = extras.keySet().iterator();
+      while (keys.hasNext()) {
+        String key = keys.next();
+        if (key.equals(kPageURLKey)) {
+          if (includeURLCheckbox.isChecked())
+            sendPart(os, boundary, key, extras.get(key));
+        } else if (!key.equals("ServerURL") && !key.equals(kNotesKey)) {
+          sendPart(os, boundary, key, extras.get(key));
+        }
+      }
+
+      // Add some extra information to notes so its displayed by 
+      // crash-stats.mozilla.org. Remove this when bug 607942 is fixed.
+      String notes = extras.containsKey(kNotesKey) ? extras.get(kNotesKey) + 
+        "\n" : "";
+      if (@MOZ_MIN_CPU_VERSION@ < 7)
+        notes += "nothumb Build\n";
+      notes += Build.MANUFACTURER + " ";
+      notes += Build.MODEL + "\n";
+      notes += Build.FINGERPRINT;
+      sendPart(os, boundary, kNotesKey, notes);
+
+      sendPart(os, boundary, "Min_ARM_Version", "@MOZ_MIN_CPU_VERSION@");
+      sendPart(os, boundary, "Android_Manufacturer", Build.MANUFACTURER);
+      sendPart(os, boundary, "Android_Model", Build.MODEL);
+      sendPart(os, boundary, "Android_Board", Build.BOARD);
+      sendPart(os, boundary, "Android_Brand", Build.BRAND);
+      sendPart(os, boundary, "Android_Device", Build.DEVICE);
+      sendPart(os, boundary, "Android_Display", Build.DISPLAY);
+      sendPart(os, boundary, "Android_Fingerprint", Build.FINGERPRINT);
+      sendPart(os, boundary, "Android_CPU_ABI", Build.CPU_ABI); 
+      if (Build.VERSION.SDK_INT >= 8) {
+        try {
+          sendPart(os, boundary, "Android_CPU_ABI2", Build.CPU_ABI2);
+          sendPart(os, boundary, "Android_Hardware", Build.HARDWARE);
+        } catch (Exception ex) {
+          Log.e("GeckoCrashReporter", "Exception while sending SDK version 8 keys", ex);
+        }
+      }
+      sendPart(os, boundary, "Android_Version",  Build.VERSION.SDK_INT + " (" + Build.VERSION.CODENAME + ")");
+
+      sendFile(os, boundary, kMiniDumpPathKey, minidumpFile);
+      os.write(("\r\n--" + boundary + "--\r\n").getBytes());
+      os.flush();
+      os.close();
+      BufferedReader br = new BufferedReader(
+        new InputStreamReader(conn.getInputStream()));
+      HashMap<String, String>  responseMap = new HashMap<String, String>();
+      readStringsFromReader(br, responseMap);
+
+      if (conn.getResponseCode() == conn.HTTP_OK) {
+        File submittedDir = new File(
+          "/data/data/@ANDROID_PACKAGE_NAME@/files/mozilla/Crash Reports/submitted");
+        submittedDir.mkdirs();
+        minidumpFile.delete();
+        extrasFile.delete();
+        String crashid = responseMap.get("CrashID");
+        File file = new File(submittedDir, crashid + ".txt");
+        FileOutputStream fos = new FileOutputStream(file);
+        fos.write("Crash ID: ".getBytes());
+        fos.write(crashid.getBytes());
+        fos.close();
+      }
+    } catch (IOException e) {
+      Log.e("GeckoCrashReporter", "exception during send: ", e);
+    }
+
+    doFinish();
+  }
+
+  void doRestart()
+  {
+    try {
+      String action = "android.intent.action.MAIN";
+      Intent intent = new Intent(action);
+      intent.setClassName("@ANDROID_PACKAGE_NAME@",
+                          "@ANDROID_PACKAGE_NAME@.App");
+      Log.i("GeckoCrashReporter", intent.toString());
+      startActivity(intent);
+    } catch (Exception e) {
+      Log.e("GeckoCrashReporter", "error while trying to restart", e);
+    }
+  }
+
+  public String unescape(String string)
+  {
+    return string.replaceAll("\\\\", "\\").replaceAll("\\n", "\n")
+      .replaceAll("\\t", "\t");
+  }
+}
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/GeckoApp.java
@@ -0,0 +1,881 @@
+/* -*- 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):
+ *   Vladimir Vukicevic <vladimir@pobox.com>
+ *   Matt Brubeck <mbrubeck@mozilla.com>
+ *   Vivien Nicolas <vnicolas@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 java.io.*;
+import java.util.*;
+import java.util.zip.*;
+import java.nio.*;
+import java.nio.channels.FileChannel;
+import java.util.concurrent.*;
+import java.lang.reflect.*;
+
+import android.os.*;
+import android.app.*;
+import android.text.*;
+import android.view.*;
+import android.view.inputmethod.*;
+import android.content.*;
+import android.content.res.*;
+import android.graphics.*;
+import android.widget.*;
+import android.hardware.*;
+
+import android.util.*;
+import android.net.*;
+import android.database.*;
+import android.provider.*;
+import android.content.pm.*;
+import android.content.pm.PackageManager.*;
+import dalvik.system.*;
+
+abstract public class GeckoApp
+    extends Activity
+{
+    private static final String LOG_FILE_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";
+
+    public static AbsoluteLayout mainLayout;
+    public static GeckoSurfaceView surfaceView;
+    public static SurfaceView cameraView;
+    public static GeckoApp mAppContext;
+    public static boolean mFullscreen = false;
+    public static File sGREDir = null;
+    static Thread mLibLoadThread = null;
+    public Handler mMainHandler;
+    private IntentFilter mConnectivityFilter;
+    private BroadcastReceiver mConnectivityReceiver;
+    private BroadcastReceiver mBatteryReceiver;
+    private BroadcastReceiver mSmsReceiver;
+
+    enum LaunchState {PreLaunch, Launching, WaitForDebugger,
+                      Launched, GeckoRunning, GeckoExiting};
+    private static LaunchState sLaunchState = LaunchState.PreLaunch;
+    private static boolean sTryCatchAttached = false;
+
+
+    static boolean checkLaunchState(LaunchState checkState) {
+        synchronized(sLaunchState) {
+            return sLaunchState == checkState;
+        }
+    }
+
+    static void setLaunchState(LaunchState setState) {
+        synchronized(sLaunchState) {
+            sLaunchState = setState;
+        }
+    }
+
+    // if mLaunchState is equal to checkState this sets mLaunchState to setState
+    // and return true. Otherwise we return false.
+    static boolean checkAndSetLaunchState(LaunchState checkState, LaunchState setState) {
+        synchronized(sLaunchState) {
+            if (sLaunchState != checkState)
+                return false;
+            sLaunchState = setState;
+            return true;
+        }
+    }
+
+    void showErrorDialog(String message)
+    {
+        new AlertDialog.Builder(this)
+            .setMessage(message)
+            .setCancelable(false)
+            .setPositiveButton(R.string.exit_label,
+                               new DialogInterface.OnClickListener() {
+                                   public void onClick(DialogInterface dialog,
+                                                       int id)
+                                   {
+                                       GeckoApp.this.finish();
+                                       System.exit(0);
+                                   }
+                               }).show();
+    }
+
+    public static final String PLUGIN_ACTION = "android.webkit.PLUGIN";
+
+    /**
+     * A plugin that wish to be loaded in the WebView must provide this permission
+     * in their AndroidManifest.xml.
+     */
+    public static final String PLUGIN_PERMISSION = "android.webkit.permission.PLUGIN";
+
+    private static final String LOGTAG = "PluginManager";
+
+    private static final String PLUGIN_SYSTEM_LIB = "/system/lib/plugins/";
+
+    private static final String PLUGIN_TYPE = "type";
+    private static final String TYPE_NATIVE = "native";
+    public ArrayList<PackageInfo> mPackageInfoCache = new ArrayList<PackageInfo>();
+
+    String[] getPluginDirectories() {
+
+        ArrayList<String> directories = new ArrayList<String>();
+        PackageManager pm = this.mAppContext.getPackageManager();
+        List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(PLUGIN_ACTION),
+                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+
+        synchronized(mPackageInfoCache) {
+
+            // clear the list of existing packageInfo objects
+            mPackageInfoCache.clear();
+
+
+            for (ResolveInfo info : plugins) {
+
+                // retrieve the plugin's service information
+                ServiceInfo serviceInfo = info.serviceInfo;
+                if (serviceInfo == null) {
+                    Log.w(LOGTAG, "Ignore bad plugin");
+                    continue;
+                }
+
+                Log.w(LOGTAG, "Loading plugin: " + serviceInfo.packageName);
+
+
+                // retrieve information from the plugin's manifest
+                PackageInfo pkgInfo;
+                try {
+                    pkgInfo = pm.getPackageInfo(serviceInfo.packageName,
+                                    PackageManager.GET_PERMISSIONS
+                                    | PackageManager.GET_SIGNATURES);
+                } catch (Exception e) {
+                    Log.w(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
+                    continue;
+                }
+                if (pkgInfo == null) {
+                    Log.w(LOGTAG, "Loading plugin: " + serviceInfo.packageName + ". Could not load package information.");
+                    continue;
+                }
+
+                /*
+                 * find the location of the plugin's shared library. The default
+                 * is to assume the app is either a user installed app or an
+                 * updated system app. In both of these cases the library is
+                 * stored in the app's data directory.
+                 */
+                String directory = pkgInfo.applicationInfo.dataDir + "/lib";
+                final int appFlags = pkgInfo.applicationInfo.flags;
+                final int updatedSystemFlags = ApplicationInfo.FLAG_SYSTEM |
+                                               ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+                // preloaded system app with no user updates
+                if ((appFlags & updatedSystemFlags) == ApplicationInfo.FLAG_SYSTEM) {
+                    directory = PLUGIN_SYSTEM_LIB + pkgInfo.packageName;
+                }
+
+                // check if the plugin has the required permissions
+                String permissions[] = pkgInfo.requestedPermissions;
+                if (permissions == null) {
+                    Log.w(LOGTAG, "Loading plugin: " + serviceInfo.packageName + ". Does not have required permission.");
+                    continue;
+                }
+                boolean permissionOk = false;
+                for (String permit : permissions) {
+                    if (PLUGIN_PERMISSION.equals(permit)) {
+                        permissionOk = true;
+                        break;
+                    }
+                }
+                if (!permissionOk) {
+                    Log.w(LOGTAG, "Loading plugin: " + serviceInfo.packageName + ". Does not have required permission (2).");
+                    continue;
+                }
+
+                // check to ensure the plugin is properly signed
+                Signature signatures[] = pkgInfo.signatures;
+                if (signatures == null) {
+                    Log.w(LOGTAG, "Loading plugin: " + serviceInfo.packageName + ". Not signed.");
+                    continue;
+                }
+
+                // determine the type of plugin from the manifest
+                if (serviceInfo.metaData == null) {
+                    Log.e(LOGTAG, "The plugin '" + serviceInfo.name + "' has no type defined");
+                    continue;
+                }
+
+                String pluginType = serviceInfo.metaData.getString(PLUGIN_TYPE);
+                if (!TYPE_NATIVE.equals(pluginType)) {
+                    Log.e(LOGTAG, "Unrecognized plugin type: " + pluginType);
+                    continue;
+                }
+
+                try {
+                    Class<?> cls = getPluginClass(serviceInfo.packageName, serviceInfo.name);
+
+                    //TODO implement any requirements of the plugin class here!
+                    boolean classFound = true;
+
+                    if (!classFound) {
+                        Log.e(LOGTAG, "The plugin's class' " + serviceInfo.name + "' does not extend the appropriate class.");
+                        continue;
+                    }
+
+                } catch (NameNotFoundException e) {
+                    Log.e(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
+                    continue;
+                } catch (ClassNotFoundException e) {
+                    Log.e(LOGTAG, "Can't find plugin's class: " + serviceInfo.name);
+                    continue;
+                }
+
+                // if all checks have passed then make the plugin available
+                mPackageInfoCache.add(pkgInfo);
+                directories.add(directory);
+            }
+        }
+
+        return directories.toArray(new String[directories.size()]);
+    }
+
+    Class<?> getPluginClass(String packageName, String className)
+            throws NameNotFoundException, ClassNotFoundException {
+        Context pluginContext = this.mAppContext.createPackageContext(packageName,
+                Context.CONTEXT_INCLUDE_CODE |
+                Context.CONTEXT_IGNORE_SECURITY);
+        ClassLoader pluginCL = pluginContext.getClassLoader();
+        return pluginCL.loadClass(className);
+    }
+
+    // Returns true when the intent is going to be handled by gecko launch
+    boolean launch(Intent intent)
+    {
+        if (!checkAndSetLaunchState(LaunchState.Launching, LaunchState.Launched))
+            return false;
+
+        if (intent == null)
+            intent = getIntent();
+        final Intent i = intent;
+        new Thread() {
+            public void run() {
+                try {
+                    if (mLibLoadThread != null)
+                        mLibLoadThread.join();
+                } catch (InterruptedException ie) {}
+
+                // Show the URL we are about to load, if the intent has one
+                if (Intent.ACTION_VIEW.equals(i.getAction())) {
+                    surfaceView.mSplashURL = i.getDataString();
+                }
+                surfaceView.drawSplashScreen();
+
+                // unpack files in the components directory
+                try {
+                    unpackComponents();
+                } catch (FileNotFoundException fnfe) {
+                    Log.e(LOG_FILE_NAME, "error unpacking components", fnfe);
+                    Looper.prepare();
+                    showErrorDialog(getString(R.string.error_loading_file));
+                    Looper.loop();
+                    return;
+                } catch (IOException ie) {
+                    Log.e(LOG_FILE_NAME, "error unpacking components", ie);
+                    String msg = ie.getMessage();
+                    Looper.prepare();
+                    if (msg != null && msg.equalsIgnoreCase("No space left on device"))
+                        showErrorDialog(getString(R.string.no_space_to_start_error));
+                    else
+                        showErrorDialog(getString(R.string.error_loading_file));
+                    Looper.loop();
+                    return;
+                }
+
+                // and then fire us up
+                try {
+                    String env = i.getStringExtra("env0");
+                    GeckoAppShell.runGecko(getApplication().getPackageResourcePath(),
+                                           i.getStringExtra("args"),
+                                           i.getDataString());
+                } catch (Exception e) {
+                    Log.e(LOG_FILE_NAME, "top level exception", e);
+                    StringWriter sw = new StringWriter();
+                    PrintWriter pw = new PrintWriter(sw);
+                    e.printStackTrace(pw);
+                    pw.flush();
+                    GeckoAppShell.reportJavaCrash(sw.toString());
+                }
+            }
+        }.start();
+        return true;
+    }
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState)
+    {
+        mAppContext = this;
+        mMainHandler = new Handler();
+
+        if (!sTryCatchAttached) {
+            sTryCatchAttached = true;
+            mMainHandler.post(new Runnable() {
+                public void run() {
+                    try {
+                        Looper.loop();
+                    } catch (Exception e) {
+                        Log.e(LOG_FILE_NAME, "top level exception", e);
+                        StringWriter sw = new StringWriter();
+                        PrintWriter pw = new PrintWriter(sw);
+                        e.printStackTrace(pw);
+                        pw.flush();
+                        GeckoAppShell.reportJavaCrash(sw.toString());
+                    }
+                    // resetting this is kinda pointless, but oh well
+                    sTryCatchAttached = false;
+                }
+            });
+        }
+
+        SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
+        String localeCode = settings.getString(getPackageName() + ".locale", "");
+        if (localeCode != null && localeCode.length() > 0)
+            GeckoAppShell.setSelectedLocale(localeCode);
+
+        Log.i(LOG_FILE_NAME, "create");
+        super.onCreate(savedInstanceState);
+
+        if (sGREDir == null)
+            sGREDir = new File(this.getApplicationInfo().dataDir);
+
+        getWindow().setFlags(mFullscreen ?
+                             WindowManager.LayoutParams.FLAG_FULLSCREEN : 0,
+                             WindowManager.LayoutParams.FLAG_FULLSCREEN);
+
+        if (cameraView == null) {
+            cameraView = new SurfaceView(this);
+            cameraView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+        }
+
+        if (surfaceView == null)
+            surfaceView = new GeckoSurfaceView(this);
+        else
+            mainLayout.removeAllViews();
+
+        mainLayout = new AbsoluteLayout(this);
+        mainLayout.addView(surfaceView,
+                           new AbsoluteLayout.LayoutParams(AbsoluteLayout.LayoutParams.MATCH_PARENT, // level 8
+                                                           AbsoluteLayout.LayoutParams.MATCH_PARENT,
+                                                           0,
+                                                           0));
+
+        setContentView(mainLayout,
+                       new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+                                                  ViewGroup.LayoutParams.FILL_PARENT));
+
+        mConnectivityFilter = new IntentFilter();
+        mConnectivityFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        mConnectivityReceiver = new GeckoConnectivityReceiver();
+
+        IntentFilter batteryFilter = new IntentFilter();
+        batteryFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+        mBatteryReceiver = new GeckoBatteryManager();
+        registerReceiver(mBatteryReceiver, batteryFilter);
+
+        IntentFilter smsFilter = new IntentFilter();
+        smsFilter.addAction("android.provider.Telephony.SMS_RECEIVED");
+        mSmsReceiver = new GeckoSmsManager();
+        registerReceiver(mSmsReceiver, smsFilter);
+
+        if (!checkAndSetLaunchState(LaunchState.PreLaunch,
+                                    LaunchState.Launching))
+            return;
+
+        checkAndLaunchUpdate();
+        mLibLoadThread = new Thread(new Runnable() {
+            public void run() {
+                // At some point while loading the gecko libs our default locale gets set
+                // so just save it to locale here and reset it as default after the join
+                Locale locale = Locale.getDefault();
+                GeckoAppShell.loadGeckoLibs(
+                    getApplication().getPackageResourcePath());
+                Locale.setDefault(locale);
+                Resources res = getBaseContext().getResources();
+                Configuration config = res.getConfiguration();
+                config.locale = locale;
+                res.updateConfiguration(config, res.getDisplayMetrics());
+            }});
+        mLibLoadThread.start();
+    }
+
+    public void enableCameraView() {
+        // Some phones (eg. nexus S) need at least a 8x16 preview size
+        mainLayout.addView(cameraView, new AbsoluteLayout.LayoutParams(8, 16, 0, 0));
+    }
+
+    public void disableCameraView() {
+        mainLayout.removeView(cameraView);
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        if (checkLaunchState(LaunchState.GeckoExiting)) {
+            // We're exiting and shouldn't try to do anything else just incase
+            // we're hung for some reason we'll force the process to exit
+            System.exit(0);
+            return;
+        }
+        final String action = intent.getAction();
+        if (ACTION_DEBUG.equals(action) &&
+            checkAndSetLaunchState(LaunchState.Launching, LaunchState.WaitForDebugger)) {
+
+            mMainHandler.postDelayed(new Runnable() {
+                public void run() {
+                    Log.i(LOG_FILE_NAME, "Launching from debug intent after 5s wait");
+                    setLaunchState(LaunchState.Launching);
+                    launch(null);
+                }
+            }, 1000 * 5 /* 5 seconds */);
+            Log.i(LOG_FILE_NAME, "Intent : ACTION_DEBUG - waiting 5s before launching");
+            return;
+        }
+        if (checkLaunchState(LaunchState.WaitForDebugger) || launch(intent))
+            return;
+
+        if (Intent.ACTION_MAIN.equals(action)) {
+            Log.i(LOG_FILE_NAME, "Intent : ACTION_MAIN");
+            GeckoAppShell.sendEventToGecko(new GeckoEvent(""));
+        }
+        else if (Intent.ACTION_VIEW.equals(action)) {
+            String uri = intent.getDataString();
+            GeckoAppShell.sendEventToGecko(new GeckoEvent(uri));
+            Log.i(LOG_FILE_NAME,"onNewIntent: "+uri);
+        }
+        else if (ACTION_WEBAPP.equals(action)) {
+            String uri = intent.getStringExtra("args");
+            GeckoAppShell.sendEventToGecko(new GeckoEvent(uri));
+            Log.i(LOG_FILE_NAME,"Intent : WEBAPP - " + uri);
+        }
+        else if (ACTION_BOOKMARK.equals(action)) {
+            String args = intent.getStringExtra("args");
+            GeckoAppShell.sendEventToGecko(new GeckoEvent(args));
+            Log.i(LOG_FILE_NAME,"Intent : BOOKMARK - " + args);
+        }
+    }
+
+    @Override
+    public void onPause()
+    {
+        Log.i(LOG_FILE_NAME, "pause");
+        GeckoAppShell.sendEventToGecko(new GeckoEvent(GeckoEvent.ACTIVITY_PAUSING));
+        // The user is navigating away from this activity, but nothing
+        // has come to the foreground yet; for Gecko, we may want to
+        // stop repainting, for example.
+
+        // Whatever we do here should be fast, because we're blocking
+        // the next activity from showing up until we finish.
+
+        // onPause will be followed by either onResume or onStop.
+        super.onPause();
+
+        unregisterReceiver(mConnectivityReceiver);
+    }
+
+    @Override
+    public void onResume()
+    {
+        Log.i(LOG_FILE_NAME, "resume");
+        if (checkLaunchState(LaunchState.GeckoRunning))
+            GeckoAppShell.onResume();
+        // After an onPause, the activity is back in the foreground.
+        // Undo whatever we did in onPause.
+        super.onResume();
+
+        // Just in case. Normally we start in onNewIntent
+        if (checkLaunchState(LaunchState.PreLaunch) ||
+            checkLaunchState(LaunchState.Launching))
+            onNewIntent(getIntent());
+
+        registerReceiver(mConnectivityReceiver, mConnectivityFilter);
+    }
+
+    @Override
+    public void onStop()
+    {
+        Log.i(LOG_FILE_NAME, "stop");
+        // We're about to be stopped, potentially in preparation for
+        // being destroyed.  We're killable after this point -- as I
+        // understand it, in extreme cases the process can be terminated
+        // without going through onDestroy.
+        //
+        // We might also get an onRestart after this; not sure what
+        // that would mean for Gecko if we were to kill it here.
+        // Instead, what we should do here is save prefs, session,
+        // etc., and generally mark the profile as 'clean', and then
+        // dirty it again if we get an onResume.
+
+        GeckoAppShell.sendEventToGecko(new GeckoEvent(GeckoEvent.ACTIVITY_STOPPING));
+        super.onStop();
+        GeckoAppShell.putChildInBackground();
+    }
+
+    @Override
+    public void onRestart()
+    {
+        Log.i(LOG_FILE_NAME, "restart");
+        GeckoAppShell.putChildInForeground();
+        super.onRestart();
+    }
+
+    @Override
+    public void onStart()
+    {
+        Log.i(LOG_FILE_NAME, "start");
+        GeckoAppShell.sendEventToGecko(new GeckoEvent(GeckoEvent.ACTIVITY_START));
+        super.onStart();
+    }
+
+    @Override
+    public void onDestroy()
+    {
+        Log.i(LOG_FILE_NAME, "destroy");
+
+        // Tell Gecko to shutting down; we'll end up calling System.exit()
+        // in onXreExit.
+        if (isFinishing())
+            GeckoAppShell.sendEventToGecko(new GeckoEvent(GeckoEvent.ACTIVITY_SHUTDOWN));
+
+        super.onDestroy();
+
+        unregisterReceiver(mSmsReceiver);
+        unregisterReceiver(mBatteryReceiver);
+    }
+
+    @Override
+    public void onConfigurationChanged(android.content.res.Configuration newConfig)
+    {
+        Log.i(LOG_FILE_NAME, "configuration changed");
+        // nothing, just ignore
+        super.onConfigurationChanged(newConfig);
+    }
+
+    @Override
+    public void onLowMemory()
+    {
+        Log.e(LOG_FILE_NAME, "low memory");
+        if (checkLaunchState(LaunchState.GeckoRunning))
+            GeckoAppShell.onLowMemory();
+        super.onLowMemory();
+    }
+
+    abstract public String getPackageName();
+    abstract public String getContentProcessName();
+
+    protected void unpackComponents()
+        throws IOException, FileNotFoundException
+    {
+        File applicationPackage = new File(getApplication().getPackageResourcePath());
+        File componentsDir = new File(sGREDir, "components");
+        if (componentsDir.lastModified() == applicationPackage.lastModified())
+            return;
+
+        componentsDir.mkdir();
+        componentsDir.setLastModified(applicationPackage.lastModified());
+
+        GeckoAppShell.killAnyZombies();
+
+        ZipFile zip = new ZipFile(applicationPackage);
+
+        byte[] buf = new byte[32768];
+        try {
+            if (unpackFile(zip, buf, null, "removed-files"))
+                removeFiles();
+        } catch (Exception ex) {
+            // This file may not be there, so just log any errors and move on
+            Log.w(LOG_FILE_NAME, "error removing files", ex);
+        }
+
+        // copy any .xpi file into an extensions/ directory
+        Enumeration<? extends ZipEntry> zipEntries = zip.entries();
+        while (zipEntries.hasMoreElements()) {
+            ZipEntry entry = zipEntries.nextElement();
+            if (entry.getName().startsWith("extensions/") && entry.getName().endsWith(".xpi")) {
+                Log.i("GeckoAppJava", "installing extension : " + entry.getName());
+                unpackFile(zip, buf, entry, entry.getName());
+            }
+        }
+    }
+
+    void removeFiles() throws IOException {
+        BufferedReader reader = new BufferedReader(
+            new FileReader(new File(sGREDir, "removed-files")));
+        try {
+            for (String removedFileName = reader.readLine(); 
+                 removedFileName != null; removedFileName = reader.readLine()) {
+                File removedFile = new File(sGREDir, removedFileName);
+                if (removedFile.exists())
+                    removedFile.delete();
+            }
+        } finally {
+            reader.close();
+        }
+        
+    }
+
+    private boolean unpackFile(ZipFile zip, byte[] buf, ZipEntry fileEntry,
+                            String name)
+        throws IOException, FileNotFoundException
+    {
+        if (fileEntry == null)
+            fileEntry = zip.getEntry(name);
+        if (fileEntry == null)
+            throw new FileNotFoundException("Can't find " + name + " in " +
+                                            zip.getName());
+
+        File outFile = new File(sGREDir, name);
+        if (outFile.lastModified() == fileEntry.getTime() &&
+            outFile.length() == fileEntry.getSize())
+            return false;
+
+        File dir = outFile.getParentFile();
+        if (!dir.exists())
+            dir.mkdirs();
+
+        InputStream fileStream;
+        fileStream = zip.getInputStream(fileEntry);
+
+        OutputStream outStream = new FileOutputStream(outFile);
+
+        while (fileStream.available() > 0) {
+            int read = fileStream.read(buf, 0, buf.length);
+            outStream.write(buf, 0, read);
+        }
+
+        fileStream.close();
+        outStream.close();
+        outFile.setLastModified(fileEntry.getTime());
+        return true;
+    }
+
+    public void addEnvToIntent(Intent intent) {
+        Map<String,String> envMap = System.getenv();
+        Set<Map.Entry<String,String>> envSet = envMap.entrySet();
+        Iterator<Map.Entry<String,String>> envIter = envSet.iterator();
+        StringBuffer envstr = new StringBuffer();
+        int c = 0;
+        while (envIter.hasNext()) {
+            Map.Entry<String,String> entry = envIter.next();
+            intent.putExtra("env" + c, entry.getKey() + "="
+                            + entry.getValue());
+            c++;
+        }
+    }
+
+    public void doRestart() {
+        try {
+            String action = "org.mozilla.gecko.restart";
+            Intent intent = new Intent(action);
+            intent.setClassName(getPackageName(),
+                                getPackageName() + ".Restarter");
+            addEnvToIntent(intent);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+                            Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+            Log.i(LOG_FILE_NAME, intent.toString());
+            GeckoAppShell.killAnyZombies();
+            startActivity(intent);
+        } catch (Exception e) {
+            Log.i(LOG_FILE_NAME, "error doing restart", e);
+        }
+        finish();
+        // Give the restart process time to start before we die
+        GeckoAppShell.waitForAnotherGeckoProc();
+    }
+
+    public void handleNotification(String action, String alertName, String alertCookie) {
+        GeckoAppShell.handleNotification(action, alertName, alertCookie);
+    }
+
+    private void checkAndLaunchUpdate() {
+        Log.i(LOG_FILE_NAME, "Checking for an update");
+
+        int statusCode = 8; // UNEXPECTED_ERROR
+        File baseUpdateDir = null;
+        if (Build.VERSION.SDK_INT >= 8)
+            baseUpdateDir = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
+        else
+            baseUpdateDir = new File(Environment.getExternalStorageDirectory().getPath(), "download");
+
+        File updateDir = new File(new File(baseUpdateDir, "updates"),"0");
+
+        File updateFile = new File(updateDir, "update.apk");
+        File statusFile = new File(updateDir, "update.status");
+
+        if (!statusFile.exists() || !readUpdateStatus(statusFile).equals("pending"))
+            return;
+
+        if (!updateFile.exists())
+            return;
+
+        Log.i(LOG_FILE_NAME, "Update is available!");
+
+        // Launch APK
+        File updateFileToRun = new File(updateDir, getPackageName() + "-update.apk");
+        try {
+            if (updateFile.renameTo(updateFileToRun)) {
+                String amCmd = "/system/bin/am start -a android.intent.action.VIEW " +
+                               "-n com.android.packageinstaller/.PackageInstallerActivity -d file://" +
+                               updateFileToRun.getPath();
+                Log.i(LOG_FILE_NAME, amCmd);
+                Runtime.getRuntime().exec(amCmd);
+                statusCode = 0; // OK
+            } else {
+                Log.i(LOG_FILE_NAME, "Cannot rename the update file!");
+                statusCode = 7; // WRITE_ERROR
+            }
+        } catch (Exception e) {
+            Log.i(LOG_FILE_NAME, "error launching installer to update", e);
+        }
+
+        // Update the status file
+        String status = statusCode == 0 ? "succeeded\n" : "failed: "+ statusCode + "\n";
+
+        OutputStream outStream;
+        try {
+            byte[] buf = status.getBytes("UTF-8");
+            outStream = new FileOutputStream(statusFile);
+            outStream.write(buf, 0, buf.length);
+            outStream.close();
+        } catch (Exception e) {
+            Log.i(LOG_FILE_NAME, "error writing status file", e);
+        }
+
+        if (statusCode == 0)
+            System.exit(0);
+    }
+
+    private String readUpdateStatus(File statusFile) {
+        String status = "";
+        try {
+            BufferedReader reader = new BufferedReader(new FileReader(statusFile));
+            status = reader.readLine();
+            reader.close();
+        } catch (Exception e) {
+            Log.i(LOG_FILE_NAME, "error reading update status", e);
+        }
+        return status;
+    }
+
+    static final int FILE_PICKER_REQUEST = 1;
+
+    private SynchronousQueue<String> mFilePickerResult = new SynchronousQueue();
+    public String showFilePicker(String aMimeType) {
+        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+        intent.addCategory(Intent.CATEGORY_OPENABLE);
+        intent.setType(aMimeType);
+        GeckoApp.this.
+            startActivityForResult(
+                Intent.createChooser(intent, getString(R.string.choose_file)),
+                FILE_PICKER_REQUEST);
+        String filePickerResult = "";
+
+        try {
+            while (null == (filePickerResult = mFilePickerResult.poll(1, TimeUnit.MILLISECONDS))) {
+                Log.i("GeckoApp", "processing events from showFilePicker ");
+                GeckoAppShell.processNextNativeEvent();
+            }
+        } catch (InterruptedException e) {
+            Log.i(LOG_FILE_NAME, "showing file picker ",  e);
+        }
+
+        return filePickerResult;
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode,
+                                    Intent data) {
+        String filePickerResult = "";
+        if (data != null && resultCode == RESULT_OK) {
+            try {
+                ContentResolver cr = getContentResolver();
+                Uri uri = data.getData();
+                Cursor cursor = GeckoApp.mAppContext.getContentResolver().query(
+                    uri, 
+                    new String[] { OpenableColumns.DISPLAY_NAME },
+                    null, 
+                    null, 
+                    null);
+                String name = null;
+                if (cursor != null) {
+                    try {
+                        if (cursor.moveToNext()) {
+                            name = cursor.getString(0);
+                        }
+                    } finally {
+                        cursor.close();
+                    }
+                }
+                String fileName = "tmp_";
+                String fileExt = null;
+                int period;
+                if (name == null || (period = name.lastIndexOf('.')) == -1) {
+                    String mimeType = cr.getType(uri);
+                    fileExt = "." + GeckoAppShell.getExtensionFromMimeType(mimeType);
+                } else {
+                    fileExt = name.substring(period);
+                    fileName = name.substring(0, period);
+                }
+                File file = File.createTempFile(fileName, fileExt, sGREDir);
+
+                FileOutputStream fos = new FileOutputStream(file);
+                InputStream is = cr.openInputStream(uri);
+                byte[] buf = new byte[4096];
+                int len = is.read(buf);
+                while (len != -1) {
+                    fos.write(buf, 0, len);
+                    len = is.read(buf);
+                }
+                fos.close();
+                filePickerResult =  file.getAbsolutePath();
+            }catch (Exception e) {
+                Log.e(LOG_FILE_NAME, "showing file picker", e);
+            }
+        }
+        try {
+            mFilePickerResult.put(filePickerResult);
+        } catch (InterruptedException e) {
+            Log.i(LOG_FILE_NAME, "error returning file picker result", e);
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/embedding/android/GeckoAppShell.java
@@ -0,0 +1,1666 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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.lang.reflect.*;
+import java.nio.*;
+import java.nio.channels.*;
+import java.text.*;
+import java.util.*;
+import java.util.zip.*;
+import java.util.concurrent.*;
+
+import android.os.*;
+import android.app.*;
+import android.text.*;
+import android.view.*;
+import android.view.inputmethod.*;
+import android.content.*;
+import android.content.res.*;
+import android.content.pm.*;
+import android.graphics.*;
+import android.widget.*;
+import android.hardware.*;
+import android.location.*;
+import android.webkit.MimeTypeMap;
+import android.media.MediaScannerConnection;
+import android.media.MediaScannerConnection.MediaScannerConnectionClient;
+import android.provider.Settings;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityEvent;
+
+import android.util.*;
+import android.net.Uri;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+import android.graphics.drawable.*;
+import android.graphics.Bitmap;
+
+public class GeckoAppShell
+{
+    private static final String LOG_FILE_NAME = "GeckoAppShell";
+
+    // static members only
+    private GeckoAppShell() { }
+
+    static private LinkedList<GeckoEvent> gPendingEvents =
+        new LinkedList<GeckoEvent>();
+
+    static private boolean gRestartScheduled = false;
+
+    static private final Timer mIMETimer = new Timer();
+    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;
+
+    static private File sCacheFile = null;
+    static private int sFreeSpace = -1;
+
+    /* 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 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);
+
+    public static native void processNextNativeEvent();
+
+    public static native void notifyBatteryChange(double aLevel, boolean aCharging, double aRemainingTime);
+
+    public static native void notifySmsReceived(String aSender, String aBody, long aTimestamp);
+
+    // A looper thread, accessed by GeckoAppShell.getHandler
+    private static class LooperThread extends Thread {
+        public SynchronousQueue<Handler> mHandlerQueue =
+            new SynchronousQueue<Handler>();
+        
+        public void run() {
+            Looper.prepare();
+            try {
+                mHandlerQueue.put(new Handler());
+            } catch (InterruptedException ie) {}
+            Looper.loop();
+        }
+    }
+
+    private static class GeckoMediaScannerClient implements MediaScannerConnectionClient {
+        private String mFile = "";
+        private String mMimeType = "";
+        private MediaScannerConnection mScanner = null;
+
+        public GeckoMediaScannerClient(Context aContext, String aFile, String aMimeType) {
+            mFile = aFile;
+            mMimeType = aMimeType;
+            mScanner = new MediaScannerConnection(aContext, this);
+            if (mScanner != null)
+                mScanner.connect();
+        }
+
+        public void onMediaScannerConnected() {
+            mScanner.scanFile(mFile, mMimeType);
+        }
+
+        public void onScanCompleted(String path, Uri uri) {
+            if(path.equals(mFile)) {
+                mScanner.disconnect();
+                mScanner = null;
+            }
+        }
+    }
+
+    // Get a Handler for the main java thread
+    public static Handler getMainHandler() {
+        return GeckoApp.mAppContext.mMainHandler;
+    }
+
+    private static Handler sHandler = null;
+
+    // Get a Handler for a looper thread, or create one if it doesn't exist yet
+    public static Handler getHandler() {
+        if (sHandler == null) {
+            LooperThread lt = new LooperThread();
+            lt.start();
+            try {
+                sHandler = lt.mHandlerQueue.take();
+            } catch (InterruptedException ie) {}
+        }
+        return sHandler;
+    }
+
+    public static File getCacheDir() {
+        if (sCacheFile == null)
+            sCacheFile = GeckoApp.mAppContext.getCacheDir();
+        return sCacheFile;
+    }
+
+    public static long getFreeSpace() {
+        try {
+            if (sFreeSpace == -1) {
+                File cacheDir = getCacheDir();
+                if (cacheDir != null) {
+                    StatFs cacheStats = new StatFs(cacheDir.getPath());
+                    sFreeSpace = cacheStats.getFreeBlocks() *
+                        cacheStats.getBlockSize();
+                } else {
+                    Log.i(LOG_FILE_NAME, "Unable to get cache dir");
+                }
+            }
+        } catch (Exception e) {
+            Log.e(LOG_FILE_NAME, "exception while stating cache dir: ", e);
+        }
+        return sFreeSpace;
+    }
+
+    static boolean moveFile(File inFile, File outFile)
+    {
+        Log.i(LOG_FILE_NAME, "moving " + inFile + " to " + outFile);
+        if (outFile.isDirectory())
+            outFile = new File(outFile, inFile.getName());
+        try {
+            if (inFile.renameTo(outFile))
+                return true;
+        } catch (SecurityException se) {
+            Log.w(LOG_FILE_NAME, "error trying to rename file", se);
+        }
+        try {
+            long lastModified = inFile.lastModified();
+            outFile.createNewFile();
+            // so copy it instead
+            FileChannel inChannel = new FileInputStream(inFile).getChannel();
+            FileChannel outChannel = new FileOutputStream(outFile).getChannel();
+            long size = inChannel.size();
+            long transferred = inChannel.transferTo(0, size, outChannel);
+            inChannel.close();
+            outChannel.close();
+            outFile.setLastModified(lastModified);
+
+            if (transferred == size)
+                inFile.delete();
+            else
+                return false;
+        } catch (Exception e) {
+            Log.e(LOG_FILE_NAME, "exception while moving file: ", e);
+            try {
+                outFile.delete();
+            } catch (SecurityException se) {
+                Log.w(LOG_FILE_NAME, "error trying to delete file", se);
+            }
+            return false;
+        }
+        return true;
+    }
+
+    static boolean moveDir(File from, File to) {
+        try {
+            to.mkdirs();
+            if (from.renameTo(to))
+                return true;
+        } catch (SecurityException se) {
+            Log.w(LOG_FILE_NAME, "error trying to rename file", se);
+        }
+        File[] files = from.listFiles();
+        boolean retVal = true;
+        if (files == null)
+            return false;
+        try {
+            Iterator fileIterator = Arrays.asList(files).iterator();
+            while (fileIterator.hasNext()) {
+                File file = (File)fileIterator.next();
+                File dest = new File(to, file.getName());
+                if (file.isDirectory())
+                    retVal = moveDir(file, dest) ? retVal : false;
+                else
+                    retVal = moveFile(file, dest) ? retVal : false;
+            }
+            from.delete();
+        } catch(Exception e) {
+            Log.e(LOG_FILE_NAME, "error trying to move file", e);
+        }
+        return retVal;
+    }
+
+    // java-side stuff
+    public static void loadGeckoLibs(String apkName) {
+        // The package data lib directory isn't placed in ld.so's
+        // search path, so we have to manually load libraries that
+        // libxul will depend on.  Not ideal.
+        System.loadLibrary("mozutils");
+        GeckoApp geckoApp = GeckoApp.mAppContext;
+        String homeDir;
+        if (Build.VERSION.SDK_INT < 8 ||
+            geckoApp.getApplication().getPackageResourcePath().startsWith("/data") ||
+            geckoApp.getApplication().getPackageResourcePath().startsWith("/system")) {
+            File home = geckoApp.getFilesDir();
+            homeDir = home.getPath();
+            // handle the application being moved to phone from sdcard
+            File profileDir = new File(homeDir, "mozilla");
+            File oldHome = new File("/data/data/" + 
+                        GeckoApp.mAppContext.getPackageName() + "/mozilla");
+            if (oldHome.exists())
+                moveDir(oldHome, profileDir);
+            if (Build.VERSION.SDK_INT >= 8) {
+                File extHome =  geckoApp.getExternalFilesDir(null);
+                File extProf = new File (extHome, "mozilla");
+                if (extHome != null && extProf != null && extProf.exists())
+                    moveDir(extProf, profileDir);
+            }
+        } else {
+            File home = geckoApp.getExternalFilesDir(null);
+            homeDir = home.getPath();
+            // handle the application being moved to phone from sdcard
+            File profileDir = new File(homeDir, "mozilla");
+            File oldHome = new File("/data/data/" + 
+                        GeckoApp.mAppContext.getPackageName() + "/mozilla");
+            if (oldHome.exists())
+                moveDir(oldHome, profileDir);
+
+            File intHome =  geckoApp.getFilesDir();
+            File intProf = new File(intHome, "mozilla");
+            if (intHome != null && intProf != null && intProf.exists())
+                moveDir(intProf, profileDir);
+        }
+        try {
+            String[] dirs = GeckoApp.mAppContext.getPluginDirectories();
+            StringBuffer pluginSearchPath = new StringBuffer();
+            for (int i = 0; i < dirs.length; i++) {
+                Log.i("GeckoPlugins", "dir: " + dirs[i]);
+                pluginSearchPath.append(dirs[i]);
+                pluginSearchPath.append(":");
+            }
+            GeckoAppShell.putenv("MOZ_PLUGIN_PATH="+pluginSearchPath);
+        } catch (Exception ex) {
+            Log.i("GeckoPlugins", "exception getting plugin dirs", ex);
+        }
+
+        GeckoAppShell.putenv("HOME=" + homeDir);
+        GeckoAppShell.putenv("GRE_HOME=" + GeckoApp.sGREDir.getPath());
+        Intent i = geckoApp.getIntent();
+        String env = i.getStringExtra("env0");
+        Log.i(LOG_FILE_NAME, "env0: "+ env);
+        for (int c = 1; env != null; c++) {
+            GeckoAppShell.putenv(env);
+            env = i.getStringExtra("env" + c);
+            Log.i(LOG_FILE_NAME, "env"+ c +": "+ env);
+        }
+
+        File f = geckoApp.getDir("tmp", Context.MODE_WORLD_READABLE |
+                                 Context.MODE_WORLD_WRITEABLE );
+
+        if (!f.exists())
+            f.mkdirs();
+
+        GeckoAppShell.putenv("TMPDIR=" + f.getPath());
+
+        f = Environment.getDownloadCacheDirectory();
+        GeckoAppShell.putenv("EXTERNAL_STORAGE=" + f.getPath());
+
+        File cacheFile = getCacheDir();
+        GeckoAppShell.putenv("CACHE_PATH=" + cacheFile.getPath());
+
+        // gingerbread introduces File.getUsableSpace(). We should use that.
+        long freeSpace = getFreeSpace();
+        try {
+            File downloadDir = null;
+            File updatesDir  = null;
+            if (Build.VERSION.SDK_INT >= 8) {
+                downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+                updatesDir  = GeckoApp.mAppContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
+            } else {
+                updatesDir = downloadDir = new File(Environment.getExternalStorageDirectory().getPath(), "download");
+            }
+            GeckoAppShell.putenv("DOWNLOADS_DIRECTORY=" + downloadDir.getPath());
+            GeckoAppShell.putenv("UPDATES_DIRECTORY="   + updatesDir.getPath());
+        }
+        catch (Exception e) {
+            Log.i(LOG_FILE_NAME, "No download directory has been found: " + e);
+        }
+
+        putLocaleEnv();
+
+        boolean extractLibs = GeckoApp.ACTION_DEBUG.equals(i.getAction());
+        if (!extractLibs) {
+            // remove any previously extracted libs
+            File[] files = cacheFile.listFiles();
+            if (files != null) {
+                Iterator cacheFiles = Arrays.asList(files).iterator();
+                while (cacheFiles.hasNext()) {
+                    File libFile = (File)cacheFiles.next();
+                    if (libFile.getName().endsWith(".so"))
+                        libFile.delete();
+                }
+            }
+        }
+        loadLibs(apkName, extractLibs);
+    }
+
+    private static void putLocaleEnv() {
+        GeckoAppShell.putenv("LANG=" + Locale.getDefault().toString());
+        NumberFormat nf = NumberFormat.getInstance();
+        if (nf instanceof DecimalFormat) {
+            DecimalFormat df = (DecimalFormat)nf;
+            DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
+
+            GeckoAppShell.putenv("LOCALE_DECIMAL_POINT=" + dfs.getDecimalSeparator());
+            GeckoAppShell.putenv("LOCALE_THOUSANDS_SEP=" + dfs.getGroupingSeparator());
+            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);
+
+        // First argument is the .apk path
+        String combinedArgs = apkPath + " -greomni " + apkPath;
+        if (args != null)
+            combinedArgs += " " + args;
+        if (url != null)
+            combinedArgs += " " + url;
+
+        // and go
+        GeckoAppShell.nativeRun(combinedArgs);
+    }
+
+    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)) {
+            notifyGeckoOfEvent(e);
+        } else {
+            gPendingEvents.addLast(e);
+        }
+    }
+
+    public static void sendEventToGeckoSync(GeckoEvent e) {
+        sendEventToGecko(e);
+        geckoEventSync();
+    }
+
+    // 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);
+    }
+
+    /* 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;
+        }
+    }
+
+    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();
+    }
+
+    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);
+    }
+
+    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(
+            new GeckoEvent(GeckoEvent.GECKO_EVENT_SYNC));
+        while (sGeckoPendingAcks.getCount() != 0) {
+            try {
+                sGeckoPendingAcks.await();
+            } catch (InterruptedException e) {}
+        }
+        sGeckoPendingAcks = null;
+    }
+
+    // Signal the Java thread that it's time to wake up
+    public static void acknowledgeEventSync() {
+        CountDownLatch tmp = sGeckoPendingAcks;
+        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);
+
+        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);
+            if (gOrientationSensor != null)
+                sm.registerListener(GeckoApp.surfaceView, gOrientationSensor,   SensorManager.SENSOR_DELAY_GAME);
+        } else {
+            if (gAccelerometerSensor != null)
+                sm.unregisterListener(GeckoApp.surfaceView, gAccelerometerSensor);
+            if (gOrientationSensor != null)
+                sm.unregisterListener(GeckoApp.surfaceView, gOrientationSensor);
+        }
+    }
+
+    public static void enableLocation(final boolean enable) {
+     
+        getMainHandler().post(new Runnable() { 
+                public void run() {
+                    GeckoSurfaceView view = GeckoApp.surfaceView;
+                    LocationManager lm = (LocationManager)
+                        view.getContext().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);
+                        }
+                        lm.requestLocationUpdates(provider, 100, (float).5, view, l);
+                    } else {
+                        lm.removeUpdates(view);
+                    }
+                }
+            });
+    }
+
+    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) {
+        }
+    }
+
+    static void onAppShellReady()
+    {
+        // mLaunchState can only be Launched at this point
+        GeckoApp.setLaunchState(GeckoApp.LaunchState.GeckoRunning);
+        sendPendingEventsToGecko();
+    }
+
+    static void onXreExit() {
+        // mLaunchState can only be Launched or GeckoRunning at this point
+        GeckoApp.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();
+        }
+        System.exit(0);
+    }
+    static void scheduleRestart() {
+        Log.i("GeckoAppJava", "scheduling restart");
+        gRestartScheduled = true;
+    }
+
+    // "Installs" an application by creating a shortcut
+    static void createShortcut(String aTitle, String aURI, String aIconData, String aType) {
+        Log.w("GeckoAppJava", "createShortcut for " + aURI + " [" + aTitle + "] > " + aType);
+
+        // the intent to be launched by the shortcut
+        Intent shortcutIntent = new Intent();
+        if (aType.equalsIgnoreCase("webapp")) {
+            shortcutIntent.setAction("org.mozilla.gecko.WEBAPP");
+            shortcutIntent.putExtra("args", "--webapp=" + aURI);
+        } else {
+            shortcutIntent.setAction("org.mozilla.gecko.BOOKMARK");
+            shortcutIntent.putExtra("args", "--url=" + aURI);
+        }
+        shortcutIntent.setClassName(GeckoApp.mAppContext,
+                                    GeckoApp.mAppContext.getPackageName() + ".App");
+
+        Intent intent = new Intent();
+        intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
+        intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle);
+        byte[] raw = Base64.decode(aIconData.substring(22), Base64.DEFAULT);
+        Bitmap bitmap = BitmapFactory.decodeByteArray(raw, 0, raw.length);
+        intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap);
+        intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
+        GeckoApp.mAppContext.sendBroadcast(intent);
+    }
+
+    static String[] getHandlersForMimeType(String aMimeType, String aAction) {
+        Intent intent = getIntentForActionString(aAction);
+        if (aMimeType != null && aMimeType.length() > 0)
+            intent.setType(aMimeType);
+        return getHandlersForIntent(intent);
+    }
+
+    static String[] getHandlersForURL(String aURL, String aAction) {
+        // 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();
+        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";
+            else
+                ret[i * numAttr + 1] = "";
+            ret[i * numAttr + 2] = resolveInfo.activityInfo.applicationInfo.packageName;
+            ret[i * numAttr + 3] = resolveInfo.activityInfo.name;
+        }
+        return ret;
+    }
+
+    static Intent getIntentForActionString(String aAction) {
+        // Default to the view action if no other action as been specified.
+        if (aAction != null && aAction.length() > 0)
+            return new Intent(aAction);
+        else
+            return new Intent(Intent.ACTION_VIEW);
+    }
+
+    static String getExtensionFromMimeType(String aMimeType) {
+        return MimeTypeMap.getSingleton().getExtensionFromMimeType(aMimeType);
+    }
+
+    static String getMimeTypeFromExtensions(String aFileExt) {
+        MimeTypeMap mtm = MimeTypeMap.getSingleton();
+        StringTokenizer st = new StringTokenizer(aFileExt, "., ");
+        String type = null;
+        String subType = null;
+        while (st.hasMoreElements()) {
+            String ext = st.nextToken();
+            String mt = mtm.getMimeTypeFromExtension(ext);
+            if (mt == null)
+                continue;
+            int slash = mt.indexOf('/');
+            String tmpType = mt.substring(0, slash);
+            if (!tmpType.equalsIgnoreCase(type))
+                type = type == null ? tmpType : "*";
+            String tmpSubType = mt.substring(slash + 1);
+            if (!tmpSubType.equalsIgnoreCase(subType))
+                subType = subType == null ? tmpSubType : "*";
+        }
+        if (type == null)
+            type = "*";
+        if (subType == null)
+            subType = "*";
+        return type + "/" + subType;
+    }
+
+    static boolean openUriExternal(String aUriSpec, String aMimeType, String aPackageName,
+                                   String aClassName, String aAction, String aTitle) {
+        Intent intent = getIntentForActionString(aAction);
+        if (aAction.equalsIgnoreCase(Intent.ACTION_SEND)) {
+            intent.putExtra(Intent.EXTRA_TEXT, aUriSpec);
+            intent.putExtra(Intent.EXTRA_SUBJECT, aTitle);
+            if (aMimeType != null && aMimeType.length() > 0)
+                intent.setType(aMimeType);
+        } else if (aMimeType.length() > 0) {
+            intent.setDataAndType(Uri.parse(aUriSpec), aMimeType);
+        } else {
+            Uri uri = Uri.parse(aUriSpec);
+            if ("sms".equals(uri.getScheme())) {
+                // Have a apecial handling for the SMS, as the message body
+                // is not extracted from the URI automatically
+                final String query = uri.getEncodedQuery();
+                if (query != null && query.length() > 0) {
+                    final String[] fields = query.split("&");
+                    boolean foundBody = false;
+                    String resultQuery = "";
+                    for (int i = 0; i < fields.length; i++) {
+                        final String field = fields[i];
+                        if (field.length() > 5 && "body=".equals(field.substring(0, 5))) {
+                            final String body = Uri.decode(field.substring(5));
+                            intent.putExtra("sms_body", body);
+                            foundBody = true;
+                        }
+                        else {
+                            resultQuery = resultQuery.concat(resultQuery.length() > 0 ? "&" + field : field);
+                        }
+                    }
+                    if (foundBody) {
+                        // Put the query without the body field back into the URI
+                        final String prefix = aUriSpec.substring(0, aUriSpec.indexOf('?'));
+                        uri = Uri.parse(resultQuery.length() > 0 ? prefix + "?" + resultQuery : prefix);
+                    }
+                }
+            }
+            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);
+            return true;
+        } catch(ActivityNotFoundException e) {
+            return false;
+        }
+    }
+
+    static SynchronousQueue<String> sClipboardQueue =
+        new SynchronousQueue<String>();
+
+    // 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() { 
+            public void run() {
+                Context context = GeckoApp.surfaceView.getContext();
+                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);
+                            text = item.coerceToText(context).toString();
+                        }
+                    }
+                } else {
+                    android.text.ClipboardManager cm = (android.text.ClipboardManager)
+                        context.getSystemService(Context.CLIPBOARD_SERVICE);
+                    if (cm.hasText())
+                        text = cm.getText().toString();
+                }
+                try {
+                    sClipboardQueue.put(text != null ? text : "");
+                } catch (InterruptedException ie) {}
+            }});
+        try {
+            String ret = sClipboardQueue.take();
+            return ret == "" ? null : ret;
+        } catch (InterruptedException ie) {}
+        return null;
+    }
+
+    static void setClipboardText(final String text) {
+        getHandler().post(new Runnable() { 
+            public void run() {
+                Context context = GeckoApp.surfaceView.getContext();
+                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);
+                }
+            }});
+    }
+
+    public static void showAlertNotification(String aImageUrl, String aAlertTitle, String aAlertText,
+                                             String aAlertCookie, String aAlertName) {
+        Log.i("GeckoAppJava", "GeckoAppShell.showAlertNotification\n" +
+            "- image = '" + aImageUrl + "'\n" +
+            "- title = '" + aAlertTitle + "'\n" +
+            "- text = '" + aAlertText +"'\n" +
+            "- cookie = '" + aAlertCookie +"'\n" +
+            "- name = '" + aAlertName + "'");
+
+        int icon = R.drawable.icon; // Just use the app icon by default
+
+        Uri imageUri = Uri.parse(aImageUrl);
+        String scheme = imageUri.getScheme();
+        if ("drawable".equals(scheme)) {
+            String resource = imageUri.getSchemeSpecificPart();
+            resource = resource.substring(resource.lastIndexOf('/') + 1);
+            try {
+                Class drawableClass = R.drawable.class;
+                Field f = drawableClass.getField(resource);
+                icon = f.getInt(null);
+            } catch (Exception e) {} // just means the resource doesn't exist
+            imageUri = null;
+        }
+
+        int notificationID = aAlertName.hashCode();
+
+        // Remove the old notification with the same ID, if any
+        removeNotification(notificationID);
+
+        AlertNotification notification = 
+            new AlertNotification(GeckoApp.mAppContext,notificationID, icon, 
+                                  aAlertTitle, aAlertText, 
+                                  System.currentTimeMillis());
+
+        // The intent to launch when the user clicks the expanded notification
+        Intent notificationIntent = new Intent(GeckoApp.ACTION_ALERT_CLICK);
+        notificationIntent.setClassName(GeckoApp.mAppContext,
+            GeckoApp.mAppContext.getPackageName() + ".NotificationHandler");
+
+        // Put the strings into the intent as an URI "alert:<name>#<cookie>"
+        Uri dataUri = Uri.fromParts("alert", aAlertName, aAlertCookie);
+        notificationIntent.setData(dataUri);
+
+        PendingIntent contentIntent = PendingIntent.getBroadcast(GeckoApp.mAppContext, 0, notificationIntent, 0);
+        notification.setLatestEventInfo(GeckoApp.mAppContext, aAlertTitle, aAlertText, contentIntent);
+        notification.setCustomIcon(imageUri);
+        // The intent to execute when the status entry is deleted by the user with the "Clear All Notifications" button
+        Intent clearNotificationIntent = new Intent(GeckoApp.ACTION_ALERT_CLEAR);
+        clearNotificationIntent.setClassName(GeckoApp.mAppContext,
+            GeckoApp.mAppContext.getPackageName() + ".NotificationHandler");
+        clearNotificationIntent.setData(dataUri);
+        notification.deleteIntent = PendingIntent.getBroadcast(GeckoApp.mAppContext, 0, clearNotificationIntent, 0);
+
+        mAlertNotifications.put(notificationID, notification);
+
+        notification.show();
+
+        Log.i("GeckoAppJava", "Created notification ID " + notificationID);
+    }
+
+    public static void alertsProgressListener_OnProgress(String aAlertName, long aProgress, long aProgressMax, String aAlertText) {
+        Log.i("GeckoAppJava", "GeckoAppShell.alertsProgressListener_OnProgress\n" +
+            "- name = '" + aAlertName +"', " +
+            "progress = " + aProgress +" / " + aProgressMax + ", text = '" + aAlertText + "'");
+
+        int notificationID = aAlertName.hashCode();
+        AlertNotification notification = mAlertNotifications.get(notificationID);
+        if (notification != null)
+            notification.updateProgress(aAlertText, aProgress, aProgressMax);
+
+        if (aProgress == aProgressMax) {
+            // Hide the notification at 100%
+            removeObserver(aAlertName);
+            removeNotification(notificationID);
+        }
+    }
+
+    public static void alertsProgressListener_OnCancel(String aAlertName) {
+        Log.i("GeckoAppJava", "GeckoAppShell.alertsProgressListener_OnCancel('" + aAlertName + "'");
+
+        removeObserver(aAlertName);
+
+        int notificationID = aAlertName.hashCode();
+        removeNotification(notificationID);
+    }
+
+    public static void handleNotification(String aAction, String aAlertName, String aAlertCookie) {
+        int notificationID = aAlertName.hashCode();
+
+        if (GeckoApp.ACTION_ALERT_CLICK.equals(aAction)) {
+            Log.i("GeckoAppJava", "GeckoAppShell.handleNotification: callObserver(alertclickcallback)");
+            callObserver(aAlertName, "alertclickcallback", aAlertCookie);
+
+            AlertNotification notification = mAlertNotifications.get(notificationID);
+            if (notification != null && notification.isProgressStyle()) {
+                // When clicked, keep the notification, if it displays a progress
+                return;
+            }
+        }
+
+        callObserver(aAlertName, "alertfinished", aAlertCookie);
+
+        removeObserver(aAlertName);
+
+        removeNotification(notificationID);
+    }
+
+    private static void removeNotification(int notificationID) {
+        mAlertNotifications.remove(notificationID);
+
+        NotificationManager notificationManager = (NotificationManager)
+            GeckoApp.mAppContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        notificationManager.cancel(notificationID);
+    }
+
+    public static int getDpi() {
+        DisplayMetrics metrics = new DisplayMetrics();
+        GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+        return metrics.densityDpi;
+    }
+
+    public static void setFullScreen(boolean fullscreen) {
+        GeckoApp.mFullscreen = fullscreen;
+
+        // force a reconfiguration to hide/show the system bar
+        GeckoApp.mAppContext.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        GeckoApp.mAppContext.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+        GeckoApp.mAppContext.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
+    }
+
+    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);
+    }
+
+    private static Vibrator vibrator() {
+        return (Vibrator) GeckoApp.surfaceView.getContext().getSystemService(Context.VIBRATOR_SERVICE);
+    }
+
+    public static void vibrate(long milliseconds) {
+        vibrator().vibrate(milliseconds);
+    }
+
+    public static void vibrate(long[] pattern, int repeat) {
+        vibrator().vibrate(pattern, repeat);
+    }
+
+    public static void cancelVibrate() {
+        vibrator().cancel();
+    }
+
+    public static void showInputMethodPicker() {
+        InputMethodManager imm = (InputMethodManager) GeckoApp.surfaceView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+        imm.showInputMethodPicker();
+    }
+
+    public static void hideProgressDialog() {
+        GeckoApp.surfaceView.mShowingSplashScreen = false;
+    }
+
+    public static void setKeepScreenOn(final boolean on) {
+        GeckoApp.mAppContext.runOnUiThread(new Runnable() {
+            public void run() {
+                GeckoApp.surfaceView.setKeepScreenOn(on);
+            }
+        });
+    }
+
+    public static boolean isNetworkLinkUp() {
+        ConnectivityManager cm = (ConnectivityManager)
+            GeckoApp.mAppContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo info = cm.getActiveNetworkInfo();
+        if (info == null || !info.isConnected())
+            return false;
+        return true;
+    }
+
+    public static boolean isNetworkLinkKnown() {
+        ConnectivityManager cm = (ConnectivityManager)
+            GeckoApp.mAppContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+        if (cm.getActiveNetworkInfo() == null)
+            return false;
+        return true;
+    }
+
+    public static void setSelectedLocale(String localeCode) {
+        SharedPreferences settings =
+            GeckoApp.mAppContext.getPreferences(Activity.MODE_PRIVATE);
+        settings.edit().putString(GeckoApp.mAppContext.getPackageName() + ".locale",
+                                  localeCode).commit();
+        Locale locale;
+        int index;
+        if ((index = localeCode.indexOf('-')) != -1 ||
+            (index = localeCode.indexOf('_')) != -1) {
+            String langCode = localeCode.substring(0, index);
+            String countryCode = localeCode.substring(index + 1);
+            locale = new Locale(langCode, countryCode);
+        } else {
+            locale = new Locale(localeCode);
+        }
+        Locale.setDefault(locale);
+
+        Resources res = GeckoApp.mAppContext.getBaseContext().getResources();
+        Configuration config = res.getConfiguration();
+        config.locale = locale;
+        res.updateConfiguration(config, res.getDisplayMetrics());
+    }
+
+    public static int[] getSystemColors() {
+        // attrsAppearance[] must correspond to AndroidSystemColors structure in android/AndroidBridge.h
+        final int[] attrsAppearance = {
+            android.R.attr.textColor,
+            android.R.attr.textColorPrimary,
+            android.R.attr.textColorPrimaryInverse,
+            android.R.attr.textColorSecondary,
+            android.R.attr.textColorSecondaryInverse,
+            android.R.attr.textColorTertiary,
+            android.R.attr.textColorTertiaryInverse,
+            android.R.attr.textColorHighlight,
+            android.R.attr.colorForeground,
+            android.R.attr.colorBackground,
+            android.R.attr.panelColorForeground,
+            android.R.attr.panelColorBackground
+        };
+
+        int[] result = new int[attrsAppearance.length];
+
+        final ContextThemeWrapper contextThemeWrapper =
+            new ContextThemeWrapper(GeckoApp.mAppContext, android.R.style.TextAppearance);
+
+        final TypedArray appearance = contextThemeWrapper.getTheme().obtainStyledAttributes(attrsAppearance);
+
+        if (appearance != null) {
+            for (int i = 0; i < appearance.getIndexCount(); i++) {
+                int idx = appearance.getIndex(i);
+                int color = appearance.getColor(idx, 0);
+                result[idx] = color;
+            }
+            appearance.recycle();
+        }
+
+        return result;
+    }
+
+    public static void putChildInBackground() {
+        try {
+            File cgroupFile = new File("/proc/" + android.os.Process.myPid() + "/cgroup");
+            BufferedReader br = new BufferedReader(new FileReader(cgroupFile));
+            String[] cpuLine = br.readLine().split("/");
+            br.close();
+            final String backgroundGroup = cpuLine.length == 2 ? cpuLine[1] : "";
+            GeckoProcessesVisitor visitor = new GeckoProcessesVisitor() {
+                public boolean callback(int pid) {
+                    if (pid != android.os.Process.myPid()) {
+                        try {
+                            FileOutputStream fos = new FileOutputStream(
+                                new File("/dev/cpuctl/" + backgroundGroup +"/tasks"));
+                            fos.write(new Integer(pid).toString().getBytes());
+                            fos.close();
+                        } catch(Exception e) {
+                            Log.e(LOG_FILE_NAME, "error putting child in the background", e);
+                        }
+                    }
+                    return true;
+                }
+            };
+            EnumerateGeckoProcesses(visitor);
+        } catch (Exception e) {
+            Log.e("GeckoInputStream", "error reading cgroup", e);
+        }
+    }
+
+    public static void putChildInForeground() {
+        GeckoProcessesVisitor visitor = new GeckoProcessesVisitor() {
+            public boolean callback(int pid) {
+                if (pid != android.os.Process.myPid()) {
+                    try {
+                        FileOutputStream fos = new FileOutputStream(new File("/dev/cpuctl/tasks"));
+                        fos.write(new Integer(pid).toString().getBytes());
+                        fos.close();
+                    } catch(Exception e) {
+                        Log.e(LOG_FILE_NAME, "error putting child in the foreground", e);
+                    }
+                }
+                return true;
+            }
+        };   
+        EnumerateGeckoProcesses(visitor);
+    }
+
+    public static void killAnyZombies() {
+        GeckoProcessesVisitor visitor = new GeckoProcessesVisitor() {
+            public boolean callback(int pid) {
+                if (pid != android.os.Process.myPid())
+                    android.os.Process.killProcess(pid);
+                return true;
+            }
+        };
+            
+        EnumerateGeckoProcesses(visitor);
+    }
+
+    public static boolean checkForGeckoProcs() {
+
+        class GeckoPidCallback implements GeckoProcessesVisitor {
+            public boolean otherPidExist = false;
+            public boolean callback(int pid) {
+                if (pid != android.os.Process.myPid()) {
+                    otherPidExist = true;
+                    return false;
+                }
+                return true;
+            }            
+        }
+        GeckoPidCallback visitor = new GeckoPidCallback();            
+        EnumerateGeckoProcesses(visitor);
+        return visitor.otherPidExist;
+    }
+
+    interface GeckoProcessesVisitor{
+        boolean callback(int pid);
+    }
+
+    static int sPidColumn = -1;
+    static int sUserColumn = -1;
+    private static void EnumerateGeckoProcesses(GeckoProcessesVisitor visiter) {
+
+        try {
+
+            // run ps and parse its output
+            java.lang.Process ps = Runtime.getRuntime().exec("ps");
+            BufferedReader in = new BufferedReader(new InputStreamReader(ps.getInputStream()),
+                                                   2048);
+
+            String headerOutput = in.readLine();
+
+            // figure out the column offsets.  We only care about the pid and user fields
+            if (sPidColumn == -1 || sUserColumn == -1) {
+                StringTokenizer st = new StringTokenizer(headerOutput);
+                
+                int tokenSoFar = 0;
+                while(st.hasMoreTokens()) {
+                    String next = st.nextToken();
+                    if (next.equalsIgnoreCase("PID"))
+                        sPidColumn = tokenSoFar;
+                    else if (next.equalsIgnoreCase("USER"))
+                        sUserColumn = tokenSoFar;
+                    tokenSoFar++;
+                }
+            }
+
+            // alright, the rest are process entries.
+            String psOutput = null;
+            while ((psOutput = in.readLine()) != null) {
+                String[] split = psOutput.split("\\s+");
+                if (split.length <= sPidColumn || split.length <= sUserColumn)
+                    continue;
+                int uid = android.os.Process.getUidForName(split[sUserColumn]);
+                if (uid == android.os.Process.myUid() &&
+                    !split[split.length - 1].equalsIgnoreCase("ps")) {
+                    int pid = Integer.parseInt(split[sPidColumn]);
+                    boolean keepGoing = visiter.callback(pid);
+                    if (keepGoing == false)
+                        break;
+                }
+            }
+            in.close();
+        }
+        catch (Exception e) {
+            Log.i(LOG_FILE_NAME, "finding procs throws ",  e);
+        }
+    }
+
+    public static void waitForAnotherGeckoProc(){
+        int countdown = 40;
+        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();
+        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();
+            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)
+                bitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, true);
+
+            ByteBuffer buf = ByteBuffer.allocate(iconSize * iconSize * 4);
+            bitmap.copyPixelsToBuffer(buf);
+
+            return buf.array();
+        }
+        catch (Exception e) {
+            Log.i(LOG_FILE_NAME, "getIconForExtension error: ",  e);
+            return null;
+        }
+    }
+
+    private static Drawable getDrawableForExtension(PackageManager pm, String aExt) {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        MimeTypeMap mtm = MimeTypeMap.getSingleton();
+        String mimeType = mtm.getMimeTypeFromExtension(aExt);
+        if (mimeType != null && mimeType.length() > 0)
+            intent.setType(mimeType);
+        else
+            return null;
+
+        List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
+        if (list.size() == 0)
+            return null;
+
+        ResolveInfo resolveInfo = list.get(0);
+
+        if (resolveInfo == null)
+            return null;
+
+        ActivityInfo activityInfo = resolveInfo.activityInfo;
+
+        return activityInfo.loadIcon(pm);
+    }
+
+    public static boolean getShowPasswordSetting() {
+        try {
+            int showPassword =
+                Settings.System.getInt(GeckoApp.mAppContext.getContentResolver(),
+                                       Settings.System.TEXT_SHOW_PASSWORD, 1);
+            return (showPassword > 0);
+        }
+        catch (Exception e) {
+            return true;
+        }
+    }
+
+    public static boolean getAccessibilityEnabled() {
+        AccessibilityManager accessibilityManager =
+            (AccessibilityManager) GeckoApp.mAppContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+        return accessibilityManager.isEnabled();
+    }
+
+    public static void addPluginView(final View view,
+                                     final double x, final double y,
+                                     final double w, final double h) {
+
+        Log.i("GeckoAppShell", "addPluginView:" + view + " @ x:" + x + " y:" + y + " w:" + w + " h:" + h ) ;
+
+        getMainHandler().post(new Runnable() { 
+                public void run() {
+                    AbsoluteLayout.LayoutParams lp = new AbsoluteLayout.LayoutParams((int)w,
+                                                                                     (int)h,
+                                                                                     (int)x,
+                                                                                     (int)y);
+
+                    if (GeckoApp.mainLayout.indexOfChild(view) == -1) {
+                        view.setWillNotDraw(true);
+                        GeckoApp.mainLayout.addView(view, lp);
+                    }
+                    else
+                    {
+                        try {
+                            GeckoApp.mainLayout.updateViewLayout(view, lp);
+                        } catch (IllegalArgumentException e) {
+                            Log.i("updateViewLayout - IllegalArgumentException", "e:" + e);
+                            // it can be the case where we
+                            // get an update before the view
+                            // is actually attached.
+                        }
+                    }
+                }
+            });
+    }
+
+    public static void removePluginView(final View view) {
+        Log.i("GeckoAppShell", "remove view:" + view);
+        getMainHandler().post(new Runnable() { 
+                public void run() {
+                    try {
+                        GeckoApp.mainLayout.removeView(view);
+                    } catch (Exception e) {}
+                }
+            });
+    }
+
+    public static Class<?> loadPluginClass(String className, String libName) {
+        Log.i("GeckoAppShell", "in loadPluginClass... attempting to access className, then libName.....");
+        Log.i("GeckoAppShell", "className: " + className);
+        Log.i("GeckoAppShell", "libName: " + libName);
+
+        try {
+            String[] split = libName.split("/");
+            String packageName = split[split.length - 3];
+            Log.i("GeckoAppShell", "load \"" + className + "\" from \"" + packageName + 
+                  "\" for \"" + libName + "\"");
+            Context pluginContext = 
+                GeckoApp.mAppContext.createPackageContext(packageName,
+                                                          Context.CONTEXT_INCLUDE_CODE |
+                                                      Context.CONTEXT_IGNORE_SECURITY);
+            ClassLoader pluginCL = pluginContext.getClassLoader();
+            return pluginCL.loadClass(className);
+        } catch (java.lang.ClassNotFoundException cnfe) {
+            Log.i("GeckoAppShell", "class not found", cnfe);
+        } catch (android.content.pm.PackageManager.NameNotFoundException nnfe) {
+            Log.i("GeckoAppShell", "package not found", nnfe);
+        }
+        Log.e("GeckoAppShell", "couldn't find class");
+        return null;
+    }
+
+    public static SurfaceInfo getSurfaceInfo(SurfaceView sview)
+    {
+        Log.i("GeckoAppShell", "getSurfaceInfo " + sview);
+        if (sview == null)
+            return null;
+
+        int format = -1;
+        try {
+            Field privateFormatField = SurfaceView.class.getDeclaredField("mFormat");
+            privateFormatField.setAccessible(true);
+            format = privateFormatField.getInt(sview);
+        } catch (Exception e) {
+            Log.i("GeckoAppShell", "mFormat is not a field of sview: ", e);
+        }
+
+        int n = 0;
+        if (format == PixelFormat.RGB_565) {
+            n = 2;
+        } else if (format == PixelFormat.RGBA_8888) {
+            n = 4;
+        } else {
+            Log.i("GeckoAppShell", "Unknown pixel format: " + format);
+            return null;
+        }
+
+        SurfaceInfo info = new SurfaceInfo();
+
+        Rect r = sview.getHolder().getSurfaceFrame();
+        info.width = r.right;
+        info.height = r.bottom;
+        info.format = format;
+
+        return info;
+    }
+
+    public static Class getSurfaceInfoClass() {
+        Log.i("GeckoAppShell", "class name: " + SurfaceInfo.class.getName());
+        return SurfaceInfo.class;
+    }
+
+    static native void executeNextRunnable();
+
+    static class GeckoRunnableCallback implements Runnable {
+        public void run() {
+            Log.i("GeckoShell", "run GeckoRunnableCallback");
+            GeckoAppShell.executeNextRunnable();
+        }
+    }
+
+    public static void postToJavaThread(boolean mainThread) {
+        Log.i("GeckoShell", "post to " + (mainThread ? "main " : "") + "java thread");
+        getMainHandler().post(new GeckoRunnableCallback());
+    }
+    
+    public static android.hardware.Camera sCamera = null;
+    
+    static native void cameraCallbackBridge(byte[] data);
+
+    static int kPreferedFps = 25;
+    static byte[] sCameraBuffer = null;
+
+    static int[] initCamera(String aContentType, int aCamera, int aWidth, int aHeight) {
+        Log.i("GeckoAppJava", "initCamera(" + aContentType + ", " + aWidth + "x" + aHeight + ") on thread " + Thread.currentThread().getId());
+
+        getMainHandler().post(new Runnable() {
+                public void run() {
+                    try {
+                        GeckoApp.mAppContext.enableCameraView();
+                    } catch (Exception e) {}
+                }
+            });
+
+        // [0] = 0|1 (failure/success)
+        // [1] = width
+        // [2] = height
+        // [3] = fps
+        int[] result = new int[4];
+        result[0] = 0;
+
+        if (Build.VERSION.SDK_INT >= 9) {
+            if (android.hardware.Camera.getNumberOfCameras() == 0)
+                return result;
+        }
+
+        try {
+            // no front/back camera before API level 9
+            if (Build.VERSION.SDK_INT >= 9)
+                sCamera = android.hardware.Camera.open(aCamera);
+            else
+                sCamera = android.hardware.Camera.open();
+
+            android.hardware.Camera.Parameters params = sCamera.getParameters();
+            params.setPreviewFormat(ImageFormat.NV21);
+
+            // use the preview fps closest to 25 fps.
+            int fpsDelta = 1000;
+            try {
+                Iterator<Integer> it = params.getSupportedPreviewFrameRates().iterator();
+                while (it.hasNext()) {
+                    int nFps = it.next();
+                    if (Math.abs(nFps - kPreferedFps) < fpsDelta) {
+                        fpsDelta = Math.abs(nFps - kPreferedFps);
+                        params.setPreviewFrameRate(nFps);
+                    }
+                }
+            } catch(Exception e) {
+                params.setPreviewFrameRate(kPreferedFps);
+            }
+
+            // set up the closest preview size available
+            Iterator<android.hardware.Camera.Size> sit = params.getSupportedPreviewSizes().iterator();
+            int sizeDelta = 10000000;
+            int bufferSize = 0;
+            while (sit.hasNext()) {
+                android.hardware.Camera.Size size = sit.next();
+                if (Math.abs(size.width * size.height - aWidth * aHeight) < sizeDelta) {
+                    sizeDelta = Math.abs(size.width * size.height - aWidth * aHeight);
+                    params.setPreviewSize(size.width, size.height);
+                    bufferSize = size.width * size.height;
+                }
+            }
+
+            try {
+                sCamera.setPreviewDisplay(GeckoApp.cameraView.getHolder());
+            } catch(IOException e) {
+                Log.e("GeckoAppJava", "Error setPreviewDisplay:", e);
+            } catch(RuntimeException e) {
+                Log.e("GeckoAppJava", "Error setPreviewDisplay:", e);
+            }
+
+            sCamera.setParameters(params);
+            sCameraBuffer = new byte[(bufferSize * 12) / 8];
+            sCamera.addCallbackBuffer(sCameraBuffer);
+            sCamera.setPreviewCallbackWithBuffer(new android.hardware.Camera.PreviewCallback() {
+                public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
+                    cameraCallbackBridge(data);
+                    if (sCamera != null)
+                        sCamera.addCallbackBuffer(sCameraBuffer);
+                }
+            });
+            sCamera.startPreview();
+            params = sCamera.getParameters();
+            Log.i("GeckoAppJava", "Camera: " + params.getPreviewSize().width + "x" + params.getPreviewSize().height +
+                  " @ " + params.getPreviewFrameRate() + "fps. format is " + params.getPreviewFormat());
+            result[0] = 1;
+            result[1] = params.getPreviewSize().width;
+            result[2] = params.getPreviewSize().height;
+            result[3] = params.getPreviewFrameRate();
+
+            Log.i("GeckoAppJava", "Camera preview started");
+        } catch(RuntimeException e) {
+            Log.e("GeckoAppJava", "initCamera RuntimeException : ", e);
+            result[0] = result[1] = result[2] = result[3] = 0;
+        }
+        return result;
+    }
+
+    static synchronized void closeCamera() {
+        Log.i("GeckoAppJava", "closeCamera() on thread " + Thread.currentThread().getId());
+        getMainHandler().post(new Runnable() {
+                public void run() {
+                    try {
+                        GeckoApp.mAppContext.disableCameraView();
+                    } catch (Exception e) {}
+                }
+            });
+        if (sCamera != null) {
+            sCamera.stopPreview();
+            sCamera.release();
+            sCamera = null;
+            sCameraBuffer = null;
+        }
+    }
+
+
+    static SynchronousQueue<Date> sTracerQueue = new SynchronousQueue<Date>();
+    public static void fireAndWaitForTracerEvent() {
+        getMainHandler().post(new Runnable() { 
+                public void run() {
+                    try {
+                        sTracerQueue.put(new Date());
+                    } catch(InterruptedException ie) {
+                        Log.w("GeckoAppShell", "exception firing tracer", ie);
+                    }
+                }
+        });
+        try {
+            sTracerQueue.take();
+        } catch(InterruptedException ie) {
+            Log.w("GeckoAppShell", "exception firing tracer", ie);
+        }
+    }
+
+    // unused
+    public static String handleGeckoMessage(String message) {
+        return "";
+    }
+    // unused
+    static void checkUriVisited(String uri) {}
+    // unused
+    static void markUriVisited(final String uri) {}
+
+    /*
+     * Battery API related methods.
+     */
+    public static void enableBatteryNotifications() {
+        GeckoBatteryManager.enableNotifications();
+    }
+
+    public static void disableBatteryNotifications() {
+        GeckoBatteryManager.disableNotifications();
+    }
+
+    public static double[] getCurrentBatteryInformation() {
+        return GeckoBatteryManager.getCurrentInformation();
+    }
+
+    /*
+     * WebSMS related methods.
+     */
+    public static int getNumberOfMessagesForText(String aText) {
+        return GeckoSmsManager.getNumberOfMessagesForText(aText);
+    }
+
+    public static void sendMessage(String aNumber, String aMessage) {
+        GeckoSmsManager.send(aNumber, aMessage);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/embedding/android/GeckoBatteryManager.java
@@ -0,0 +1,182 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * 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.lang.Math;
+import java.util.Date;
+
+import android.util.Log;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import android.os.BatteryManager;
+
+public class GeckoBatteryManager
+  extends BroadcastReceiver
+{
+  // Those constants should be keep in sync with the ones in:
+  // dom/battery/Constants.h
+  private final static double  kDefaultLevel         = 1.0;
+  private final static boolean kDefaultCharging      = true;
+  private final static double  kDefaultRemainingTime = -1.0;
+  private final static double  kUnknownRemainingTime = -1.0;
+
+  private static Date    sLastLevelChange            = new Date(0);
+  private static boolean sNotificationsEnabled       = false;
+  private static double  sLevel                      = kDefaultLevel;
+  private static boolean sCharging                   = kDefaultCharging;
+  private static double  sRemainingTime              = kDefaultRemainingTime;;
+
+  @Override
+  public void onReceive(Context context, Intent intent) {
+    if (!intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) {
+      Log.e("GeckoBatteryManager", "Got an unexpected intent!");
+      return;
+    }
+
+    boolean previousCharging = isCharging();
+    double previousLevel = getLevel();
+
+    if (intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, false)) {
+      int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
+      if (plugged == -1) {
+        sCharging = kDefaultCharging;
+        Log.e("GeckoBatteryManager", "Failed to get the plugged status!");
+      } else {
+        // Likely, if plugged > 0, it's likely plugged and charging but the doc
+        // isn't clear about that.
+        sCharging = plugged != 0;
+      }
+
+      if (sCharging != previousCharging) {
+        sRemainingTime = kUnknownRemainingTime;
+        // The new remaining time is going to take some time to show up but
+        // it's the best way to show a not too wrong value.
+        sLastLevelChange = new Date(0);
+      }
+
+      // We need two doubles because sLevel is a double.
+      double current =  (double)intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+      double max = (double)intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
+      if (current == -1 || max == -1) {
+        Log.e("GeckoBatteryManager", "Failed to get battery level!");
+        sLevel = kDefaultLevel;
+      } else {
+        sLevel = current / max;
+      }
+
+      if (sLevel == 1.0 && sCharging) {
+        sRemainingTime = 0.0;
+      } else if (sLevel != previousLevel) {
+        // Estimate remaining time.
+        if (sLastLevelChange.getTime() != 0) {
+          Date currentTime = new Date();
+          long dt = (currentTime.getTime() - sLastLevelChange.getTime()) / 1000;
+          double dLevel = sLevel - previousLevel;
+
+          if (sCharging) {
+            if (dLevel < 0) {
+              Log.w("GeckoBatteryManager", "When charging, level should increase!");
+              sRemainingTime = kUnknownRemainingTime;
+            } else {
+              sRemainingTime = Math.round(dt / dLevel * (1.0 - sLevel));
+            }
+          } else {
+            if (dLevel > 0) {
+              Log.w("GeckoBatteryManager", "When discharging, level should decrease!");
+              sRemainingTime = kUnknownRemainingTime;
+            } else {
+              sRemainingTime = Math.round(dt / -dLevel * sLevel);
+            }
+          }
+
+          sLastLevelChange = currentTime;
+        } else {
+          // That's the first time we got an update, we can't do anything.
+          sLastLevelChange = new Date();
+        }
+      }
+    } else {
+      sLevel = kDefaultLevel;
+      sCharging = kDefaultCharging;
+      sRemainingTime = kDefaultRemainingTime;
+    }
+
+    /*
+     * We want to inform listeners if the following conditions are fulfilled:
+     *  - we have at least one observer;
+     *  - the charging state or the level has changed.
+     *
+     * Note: no need to check for a remaining time change given that it's only
+     * updated if there is a level change or a charging change.
+     *
+     * The idea is to prevent doing all the way to the DOM code in the child
+     * process to finally not send an event.
+     */
+    if (sNotificationsEnabled &&
+        (previousCharging != isCharging() || previousLevel != getLevel())) {
+      GeckoAppShell.notifyBatteryChange(getLevel(), isCharging(), getRemainingTime());
+    }
+  }
+
+  public static boolean isCharging() {
+    return sCharging;
+  }
+
+  public static double getLevel() {
+    return sLevel;
+  }
+
+  public static double getRemainingTime() {
+    return sRemainingTime;
+  }
+
+  public static void enableNotifications() {
+    sNotificationsEnabled = true;
+  }
+
+  public static void disableNotifications() {
+    sNotificationsEnabled = false;
+  }
+
+  public static double[] getCurrentInformation() {
+    return new double[] { getLevel(), isCharging() ? 1.0 : 0.0, getRemainingTime() };
+  }
+}
new file mode 100644
--- /dev/null
+++ b/embedding/android/GeckoConnectivityReceiver.java
@@ -0,0 +1,62 @@
+/* -*- 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):
+ *   Makoto Kato <m_kato@ga2.so-net.ne.jp> (Original Author)
+ *
+ * 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.content.*;
+import android.net.*;
+
+public class GeckoConnectivityReceiver
+    extends BroadcastReceiver
+{
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String status;
+        ConnectivityManager cm = (ConnectivityManager)
+            context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo info = cm.getActiveNetworkInfo();
+        if (info == null)
+            status = "unknown";
+        else if (!info.isConnected())
+            status = "down";
+        else
+            status = "up";
+
+        if (GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning))
+            GeckoAppShell.onChangeNetworkLinkStatus(status);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/embedding/android/GeckoEvent.java
@@ -0,0 +1,238 @@
+/* -*- Mode: Java; tab-width: 4; 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):
+ *   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 android.os.*;
+import android.app.*;
+import android.view.*;
+import android.content.*;
+import android.graphics.*;
+import android.widget.*;
+import android.hardware.*;
+import android.location.*;
+import android.util.FloatMath;
+
+import android.util.Log;
+
+/* We're not allowed to hold on to most events given to us
+ * so we save the parts of the events we want to use in GeckoEvent.
+ * Fields have different meanings depending on the event type.
+ */
+
+public class GeckoEvent {
+    public static final int INVALID = -1;
+    public static final int NATIVE_POKE = 0;
+    public static final int KEY_EVENT = 1;
+    public static final int MOTION_EVENT = 2;
+    public static final int ORIENTATION_EVENT = 3;
+    public static final int ACCELERATION_EVENT = 4;
+    public static final int LOCATION_EVENT = 5;
+    public static final int IME_EVENT = 6;
+    public static final int DRAW = 7;
+    public static final int SIZE_CHANGED = 8;
+    public static final int ACTIVITY_STOPPING = 9;
+    public static final int ACTIVITY_PAUSING = 10;
+    public static final int ACTIVITY_SHUTDOWN = 11;
+    public static final int LOAD_URI = 12;
+    public static final int SURFACE_CREATED = 13;
+    public static final int SURFACE_DESTROYED = 14;
+    public static final int GECKO_EVENT_SYNC = 15;
+    public static final int ACTIVITY_START = 17;
+    public static final int SAVE_STATE = 18;
+    public static final int BROADCAST = 19;
+
+    public static final int IME_COMPOSITION_END = 0;
+    public static final int IME_COMPOSITION_BEGIN = 1;
+    public static final int IME_SET_TEXT = 2;
+    public static final int IME_GET_TEXT = 3;
+    public static final int IME_DELETE_TEXT = 4;
+    public static final int IME_SET_SELECTION = 5;
+    public static final int IME_GET_SELECTION = 6;
+    public static final int IME_ADD_RANGE = 7;
+
+    public static final int IME_RANGE_CARETPOSITION = 1;
+    public static final int IME_RANGE_RAWINPUT = 2;
+    public static final int IME_RANGE_SELECTEDRAWTEXT = 3;
+    public static final int IME_RANGE_CONVERTEDTEXT = 4;
+    public static final int IME_RANGE_SELECTEDCONVERTEDTEXT = 5;
+
+    public static final int IME_RANGE_UNDERLINE = 1;
+    public static final int IME_RANGE_FORECOLOR = 2;
+    public static final int IME_RANGE_BACKCOLOR = 4;
+
+    public int mType;
+    public int mAction;
+    public long mTime;
+    public Point mP0, mP1;
+    public Rect mRect;
+    public double mX, mY, mZ;
+    public double mAlpha, mBeta, mGamma;
+
+    public int mMetaState, mFlags;
+    public int mKeyCode, mUnicodeChar;
+    public int mOffset, mCount;
+    public String mCharacters, mCharactersExtra;
+    public int mRangeType, mRangeStyles;
+    public int mRangeForeColor, mRangeBackColor;
+    public Location mLocation;
+    public Address  mAddress;
+
+    public int mNativeWindow;
+
+    public GeckoEvent() {
+        mType = NATIVE_POKE;
+    }
+
+    public GeckoEvent(int evType) {
+        mType = evType;
+    }
+
+    public GeckoEvent(KeyEvent k) {
+        mType = KEY_EVENT;
+        mAction = k.getAction();
+        mTime = k.getEventTime();
+        mMetaState = k.getMetaState();
+        mFlags = k.getFlags();
+        mKeyCode = k.getKeyCode();
+        mUnicodeChar = k.getUnicodeChar();
+        mCharacters = k.getCharacters();
+    }
+
+    public GeckoEvent(MotionEvent m) {
+        mType = MOTION_EVENT;
+        mAction = m.getAction();
+        mTime = m.getEventTime();
+        mMetaState = m.getMetaState();
+        mP0 = new Point((int)m.getX(0), (int)m.getY(0));
+        mCount = m.getPointerCount();
+        if (mCount > 1)
+            mP1 = new Point((int)m.getX(1), (int)m.getY(1));
+    }
+
+    public GeckoEvent(SensorEvent s) {
+
+        if (s.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
+            mType = ACCELERATION_EVENT;
+            mX = s.values[0];
+            mY = s.values[1];
+            mZ = s.values[2];
+        }
+        else {
+            mType = ORIENTATION_EVENT;
+            mAlpha = -s.values[0];
+            mBeta = -s.values[1];
+            mGamma = -s.values[2];
+            Log.i("GeckoEvent", "SensorEvent type = " + s.sensor.getType() + " " + s.sensor.getName() + " " + mAlpha + " " + mBeta + " " + mGamma );
+        }
+    }
+
+    public GeckoEvent(Location l, Address a) {
+        mType = LOCATION_EVENT;
+        mLocation = l;
+        mAddress  = a;
+    }
+
+    public GeckoEvent(int imeAction, int offset, int count) {
+        mType = IME_EVENT;
+        mAction = imeAction;
+        mOffset = offset;
+        mCount = count;
+    }
+
+    private void InitIMERange(int action, int offset, int count,
+                              int rangeType, int rangeStyles,
+                              int rangeForeColor, int rangeBackColor) {
+        mType = IME_EVENT;
+        mAction = action;
+        mOffset = offset;
+        mCount = count;
+        mRangeType = rangeType;
+        mRangeStyles = rangeStyles;
+        mRangeForeColor = rangeForeColor;
+        mRangeBackColor = rangeBackColor;
+        return;
+    }
+    
+    public GeckoEvent(int offset, int count,
+                      int rangeType, int rangeStyles,
+                      int rangeForeColor, int rangeBackColor, String text) {
+        InitIMERange(IME_SET_TEXT, offset, count, rangeType, rangeStyles,
+                     rangeForeColor, rangeBackColor);
+        mCharacters = text;
+    }
+
+    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) {
+        if (etype != DRAW) {
+            mType = INVALID;
+            return;
+        }
+
+        mType = etype;
+        mRect = dirty;
+    }
+
+    public GeckoEvent(int etype, int w, int h, int screenw, int screenh) {
+        if (etype != SIZE_CHANGED) {
+            mType = INVALID;
+            return;
+        }
+
+        mType = etype;
+
+        mP0 = new Point(w, h);
+        mP1 = new Point(screenw, screenh);
+    }
+
+    public GeckoEvent(String subject, String data) {
+        mType = BROADCAST;
+        mCharacters = subject;
+        mCharactersExtra = data;
+    }
+
+    public GeckoEvent(String uri) {
+        mType = LOAD_URI;
+        mCharacters = uri;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/embedding/android/GeckoInputConnection.java
@@ -0,0 +1,733 @@
+/* -*- 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):
+ *   Michael Wu <mwu@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 java.io.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+
+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.util.*;
+
+public class GeckoInputConnection
+    extends BaseInputConnection
+    implements TextWatcher
+{
+    private class ChangeNotification {
+        public String mText;
+        public int mStart;
+        public int mEnd;
+        public int mNewEnd;
+
+        ChangeNotification(String text, int start, int oldEnd, int newEnd) {
+            mText = text;
+            mStart = start;
+            mEnd = oldEnd;
+            mNewEnd = newEnd;
+        }
+
+        ChangeNotification(int start, int end) {
+            mText = null;
+            mStart = start;
+            mEnd = end;
+            mNewEnd = 0;
+        }
+    }
+
+    public GeckoInputConnection (View targetView) {
+        super(targetView, true);
+        mQueryResult = new SynchronousQueue<String>();
+    }
+
+    @Override
+    public boolean beginBatchEdit() {
+        //Log.d("GeckoAppJava", "IME: beginBatchEdit");
+        mBatchMode = true;
+        return true;
+    }
+
+    @Override
+    public boolean commitCompletion(CompletionInfo text) {
+        //Log.d("GeckoAppJava", "IME: commitCompletion");
+
+        return commitText(text.getText(), 1);
+    }
+
+    @Override
+    public boolean commitText(CharSequence text, int newCursorPosition) {
+        //Log.d("GeckoAppJava", "IME: commitText");
+
+        setComposingText(text, newCursorPosition);
+        finishComposingText();
+
+        return true;
+    }
+
+    @Override
+    public boolean deleteSurroundingText(int leftLength, int rightLength) {
+        //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) {
+            // Cancel current composition
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(0, 0, 0, 0, 0, 0, null));
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_COMPOSITION_END, 0, 0));
+        }
+
+        // Select text to be deleted
+        int delStart, delLen;
+        GeckoAppShell.sendEventToGecko(
+            new GeckoEvent(GeckoEvent.IME_GET_SELECTION, 0, 0));
+        try {
+            mQueryResult.take();
+        } catch (InterruptedException e) {
+            Log.e("GeckoAppJava", "IME: deleteSurroundingText interrupted", e);
+            return false;
+        }
+        delStart = mSelectionStart > leftLength ?
+                    mSelectionStart - leftLength : 0;
+        delLen = mSelectionStart + rightLength - delStart;
+        GeckoAppShell.sendEventToGecko(
+            new GeckoEvent(GeckoEvent.IME_SET_SELECTION, delStart, delLen));
+
+        // Restore composition / delete text
+        if (mComposing) {
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_COMPOSITION_BEGIN, 0, 0));
+            if (mComposingText.length() > 0) {
+                /* IME_SET_TEXT doesn't work well with empty strings */
+                GeckoAppShell.sendEventToGecko(
+                    new GeckoEvent(0, mComposingText.length(),
+                                   GeckoEvent.IME_RANGE_RAWINPUT,
+                                   GeckoEvent.IME_RANGE_UNDERLINE, 0, 0,
+                                   mComposingText.toString()));
+            }
+        } else {
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_DELETE_TEXT, 0, 0));
+        }
+        return true;
+    }
+
+    @Override
+    public boolean endBatchEdit() {
+        //Log.d("GeckoAppJava", "IME: endBatchEdit");
+
+        mBatchMode = false;
+
+        if (!mBatchChanges.isEmpty()) {
+            InputMethodManager imm = (InputMethodManager)
+                GeckoApp.surfaceView.getContext().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");
+
+        if (mComposing) {
+            // Set style to none
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(0, mComposingText.length(),
+                               GeckoEvent.IME_RANGE_RAWINPUT, 0, 0, 0,
+                               mComposingText));
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_COMPOSITION_END, 0, 0));
+            mComposing = false;
+            mComposingText = "";
+
+            if (!mBatchMode) {
+                // Make sure caret stays at the same position
+                GeckoAppShell.sendEventToGecko(
+                    new GeckoEvent(GeckoEvent.IME_SET_SELECTION,
+                                   mCompositionStart + mCompositionSelStart, 0));
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public int getCursorCapsMode(int reqModes) {
+        //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");
+
+        // 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();
+        } catch (InterruptedException e) {
+            Log.e("GeckoAppJava", "IME: performContextMenuAction interrupted", e);
+            return false;
+        }
+
+        switch (id) {
+            case R.id.selectAll:
+                setSelection(0, text.length());
+                break;
+            case R.id.cut:
+                // Fill the clipboard
+                GeckoAppShell.setClipboardText(text);
+                // If GET_TEXT returned an empty selection, we'll select everything
+                if (mSelectionLength <= 0)
+                    GeckoAppShell.sendEventToGecko(
+                        new GeckoEvent(GeckoEvent.IME_SET_SELECTION, 0, text.length()));
+                GeckoAppShell.sendEventToGecko(
+                    new GeckoEvent(GeckoEvent.IME_DELETE_TEXT, 0, 0));
+                break;
+            case R.id.paste:
+                commitText(GeckoAppShell.getClipboardText(), 1);
+                break;
+            case R.id.copy:
+                // If there is no selection set, we must be doing "Copy All",
+                // otherwise, we need to get the selection from Gecko
+                if (mSelectionLength > 0) {
+                    GeckoAppShell.sendEventToGecko(
+                        new GeckoEvent(GeckoEvent.IME_GET_SELECTION, 0, 0));
+                    try {
+                        text = mQueryResult.take();
+                    } catch (InterruptedException e) {
+                        Log.e("GeckoAppJava", "IME: performContextMenuAction interrupted", e);
+                        return false;
+                    }
+                }
+                GeckoAppShell.setClipboardText(text);
+                break;
+        }
+        return true;
+    }
+
+    @Override
+    public ExtractedText getExtractedText(ExtractedTextRequest req, int flags) {
+        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");
+
+        ExtractedText extract = new ExtractedText();
+        extract.flags = 0;
+        extract.partialStartOffset = -1;
+        extract.partialEndOffset = -1;
+
+        GeckoAppShell.sendEventToGecko(
+            new GeckoEvent(GeckoEvent.IME_GET_SELECTION, 0, 0));
+        try {
+            mQueryResult.take();
+        } catch (InterruptedException e) {
+            Log.e("GeckoAppJava", "IME: getExtractedText interrupted", e);
+            return null;
+        }
+        extract.selectionStart = mSelectionStart;
+        extract.selectionEnd = mSelectionStart + mSelectionLength;
+
+        // bug 617298 - IME_GET_TEXT sometimes gives the wrong result due to
+        // a stale cache. Use a set of three workarounds:
+        // 1. Sleep for 20 milliseconds and hope the child updates us with the new text.
+        //    Very evil and, consequentially, most effective.
+        try {
+            Thread.sleep(20);
+        } catch (InterruptedException e) {}
+
+        GeckoAppShell.sendEventToGecko(
+            new GeckoEvent(GeckoEvent.IME_GET_TEXT, 0, Integer.MAX_VALUE));
+        try {
+            extract.startOffset = 0;
+            extract.text = mQueryResult.take();
+
+            // 2. Make a guess about what the text actually is
+            if (mComposing && extract.selectionEnd > extract.text.length())
+                extract.text = extract.text.subSequence(0, Math.min(extract.text.length(), mCompositionStart)) + mComposingText;
+
+            // 3. If all else fails, make sure our selection indexes make sense
+            extract.selectionStart = Math.min(extract.selectionStart, extract.text.length());
+            extract.selectionEnd = Math.min(extract.selectionEnd, extract.text.length());
+
+            if ((flags & GET_EXTRACTED_TEXT_MONITOR) != 0)
+                mUpdateRequest = req;
+            return extract;
+
+        } 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");
+
+        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;
+        }
+
+        /* Compatible with both positive and negative length
+            (no need for separate code for getTextBeforeCursor) */
+        int textStart = mSelectionStart;
+        int textLength = length;
+
+        if (length < 0) {
+          textStart += length;
+          textLength = -length;
+          if (textStart < 0) {
+            textStart = 0;
+            textLength = mSelectionStart;
+          }
+        }
+
+        GeckoAppShell.sendEventToGecko(
+            new GeckoEvent(GeckoEvent.IME_GET_TEXT, textStart, textLength));
+        try {
+            return mQueryResult.take();
+        } 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");
+
+        return getTextAfterCursor(-length, flags);
+    }
+
+    @Override
+    public boolean setComposingText(CharSequence text, int newCursorPosition) {
+        //Log.d("GeckoAppJava", "IME: setComposingText");
+
+        // Set new composing text
+        mComposingText = text != null ? text.toString() : "";
+
+        if (!mComposing) {
+            if (mComposingText.length() == 0) {
+                // Some IMEs such as iWnn sometimes call with empty composing 
+                // text.  (See bug 664364)
+                // If composing text is empty, ignore this and don't start
+                // compositing.
+                return true;
+            }
+
+            // Get current selection
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_GET_SELECTION, 0, 0));
+            try {
+                mQueryResult.take();
+            } catch (InterruptedException e) {
+                Log.e("GeckoAppJava", "IME: setComposingText interrupted", e);
+                return false;
+            }
+            // Make sure we are in a composition
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_COMPOSITION_BEGIN, 0, 0));
+            mComposing = true;
+            mCompositionStart = mSelectionLength >= 0 ?
+                mSelectionStart : mSelectionStart + mSelectionLength;
+        }
+
+        // Set new selection
+        // New selection should be within the composition
+        mCompositionSelStart = newCursorPosition > 0 ? mComposingText.length() : 0;
+        mCompositionSelLen = 0;
+
+        // Handle composition text styles
+        if (text != null && text instanceof Spanned) {
+            Spanned span = (Spanned) text;
+            int spanStart = 0, spanEnd = 0;
+            boolean pastSelStart = false, pastSelEnd = false;
+
+            do {
+                int rangeType = GeckoEvent.IME_RANGE_CONVERTEDTEXT;
+                int rangeStyles = 0, rangeForeColor = 0, rangeBackColor = 0;
+
+                // Find next offset where there is a style transition
+                spanEnd = span.nextSpanTransition(spanStart + 1, text.length(),
+                    CharacterStyle.class);
+
+                // We need to count the selection as a transition
+                if (mCompositionSelLen >= 0) {
+                    if (!pastSelStart && spanEnd >= mCompositionSelStart) {
+                        spanEnd = mCompositionSelStart;
+                        pastSelStart = true;
+                    } else if (!pastSelEnd && spanEnd >=
+                            mCompositionSelStart + mCompositionSelLen) {
+                        spanEnd = mCompositionSelStart + mCompositionSelLen;
+                        pastSelEnd = true;
+                        rangeType = GeckoEvent.IME_RANGE_SELECTEDRAWTEXT;
+                    }
+                } else {
+                    if (!pastSelEnd && spanEnd >=
+                            mCompositionSelStart + mCompositionSelLen) {
+                        spanEnd = mCompositionSelStart + mCompositionSelLen;
+                        pastSelEnd = true;
+                    } else if (!pastSelStart &&
+                            spanEnd >= mCompositionSelStart) {
+                        spanEnd = mCompositionSelStart;
+                        pastSelStart = true;
+                        rangeType = GeckoEvent.IME_RANGE_SELECTEDRAWTEXT;
+                    }
+                }
+                // Empty range, continue
+                if (spanEnd <= spanStart)
+                    continue;
+
+                // Get and iterate through list of span objects within range
+                CharacterStyle styles[] = span.getSpans(
+                    spanStart, spanEnd, CharacterStyle.class);
+
+                for (CharacterStyle style : styles) {
+                    if (style instanceof UnderlineSpan) {
+                        // Text should be underlined
+                        rangeStyles |= GeckoEvent.IME_RANGE_UNDERLINE;
+
+                    } else if (style instanceof ForegroundColorSpan) {
+                        // Text should be of a different foreground color
+                        rangeStyles |= GeckoEvent.IME_RANGE_FORECOLOR;
+                        rangeForeColor =
+                            ((ForegroundColorSpan)style).getForegroundColor();
+
+                    } else if (style instanceof BackgroundColorSpan) {
+                        // Text should be of a different background color
+                        rangeStyles |= GeckoEvent.IME_RANGE_BACKCOLOR;
+                        rangeBackColor =
+                            ((BackgroundColorSpan)style).getBackgroundColor();
+                    }
+                }
+
+                // Add range to array, the actual styles are
+                //  applied when IME_SET_TEXT is sent
+                GeckoAppShell.sendEventToGecko(
+                    new GeckoEvent(spanStart, spanEnd - spanStart,
+                                   rangeType, rangeStyles,
+                                   rangeForeColor, rangeBackColor));
+
+                spanStart = spanEnd;
+            } while (spanStart < text.length());
+        } else {
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(0, text == null ? 0 : text.length(),
+                               GeckoEvent.IME_RANGE_RAWINPUT,
+                               GeckoEvent.IME_RANGE_UNDERLINE, 0, 0));
+        }
+
+        // Change composition (treating selection end as where the caret is)
+        GeckoAppShell.sendEventToGecko(
+            new GeckoEvent(mCompositionSelStart + mCompositionSelLen, 0,
+                           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 + ")");
+        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;
+        }
+
+        finishComposingText();
+
+        if (text == null && start < end) {
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_GET_TEXT, start, end - start));
+            try {
+                text = mQueryResult.take();
+            } catch (InterruptedException e) {
+                Log.e("GeckoAppJava", "IME: setComposingRegion interrupted", e);
+                return false;
+            }
+        }
+
+        GeckoAppShell.sendEventToGecko(
+            new GeckoEvent(GeckoEvent.IME_SET_SELECTION, start, end - start));
+
+        // 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");
+
+        if (mComposing) {
+            /* Translate to fake selection positions */
+            start -= mCompositionStart;
+            end -= mCompositionStart;
+
+            if (start < 0)
+                start = 0;
+            else if (start > mComposingText.length())
+                start = mComposingText.length();
+
+            if (end < 0)
+                end = 0;
+            else if (end > mComposingText.length())
+                end = mComposingText.length();
+
+            mCompositionSelStart = start;
+            mCompositionSelLen = end - start;
+        } else {
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_SET_SELECTION,
+                               start, end - start));
+        }
+        return true;
+    }
+
+    public boolean onKeyDel() {
+        // Some IMEs don't update us on deletions
+        // In that case we are not updated when a composition
+        // is destroyed, and Bad Things happen
+
+        if (!mComposing)
+            return false;
+
+        if (mComposingText.length() > 0) {
+            mComposingText = mComposingText.substring(0,
+                mComposingText.length() - 1);
+            if (mComposingText.length() > 0)
+                return false;
+        }
+
+        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));
+
+        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);
+
+        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.
+        mUpdateExtract.partialStartOffset = 0;
+        mUpdateExtract.partialEndOffset = oldEnd;
+
+        // 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);
+    }
+
+    public void notifySelectionChange(InputMethodManager imm,
+                                      int start, int end) {
+        // Log.d("GeckoAppJava", String.format("IME: notifySelectionChange: s=%d e=%d", start, end));
+        if (mBatchMode) {
+            mBatchChanges.add(new ChangeNotification(start, end));
+            return;
+        }
+
+        if (mComposing)
+            imm.updateSelection(GeckoApp.surfaceView,
+                mCompositionStart + mCompositionSelStart,
+                mCompositionStart + mCompositionSelStart + mCompositionSelLen,
+                mCompositionStart,
+                mCompositionStart + mComposingText.length());
+        else
+            imm.updateSelection(GeckoApp.surfaceView, 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, 
+                               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));
+
+        mNumPendingChanges++;
+        GeckoAppShell.sendEventToGecko(
+            new GeckoEvent(GeckoEvent.IME_SET_SELECTION, start, before));
+
+        if (count == 0) {
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_DELETE_TEXT, 0, 0));
+        } else {
+            // Start and stop composition to force UI updates.
+            finishComposingText();
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_COMPOSITION_BEGIN, 0, 0));
+
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(0, count,
+                               GeckoEvent.IME_RANGE_RAWINPUT, 0, 0, 0,
+                               s.subSequence(start, start + count).toString()));
+
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_COMPOSITION_END, 0, 0));
+
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_SET_SELECTION, start + count, 0));
+        }
+
+        // Block this thread until all pending events are processed
+        GeckoAppShell.geckoEventSync();
+    }
+
+    public void afterTextChanged(Editable s)
+    {
+    }
+
+    public void beforeTextChanged(CharSequence s, int start, int count, int after)
+    {
+    }
+
+    // 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;
+
+    boolean mBatchMode;
+    private CopyOnWriteArrayList<ChangeNotification> mBatchChanges =
+        new CopyOnWriteArrayList<ChangeNotification>();
+
+    ExtractedTextRequest mUpdateRequest;
+    final ExtractedText mUpdateExtract = new ExtractedText();
+
+    int mSelectionStart, mSelectionLength;
+    SynchronousQueue<String> mQueryResult;
+}
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/GeckoSmsManager.java
@@ -0,0 +1,108 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * 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.util.ArrayList;
+
+import android.util.Log;
+
+import android.content.Intent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+
+import android.os.Bundle;
+
+import android.telephony.SmsManager;
+import android.telephony.SmsMessage;
+
+public class GeckoSmsManager
+  extends BroadcastReceiver
+{
+  final static int kMaxMessageSize = 160;
+
+  @Override
+  public void onReceive(Context context, Intent intent) {
+    if (intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) {
+      // TODO: Try to find the receiver number to be able to populate
+      //       SmsMessage.receiver.
+      // TODO: Get the id and the date from the stock app saved message.
+      //       Using the stock app saved message require us to wait for it to
+      //       be saved which can lead to race conditions.
+
+      Bundle bundle = intent.getExtras();
+
+      if (bundle == null) {
+        return;
+      }
+
+      Object[] pdus = (Object[]) bundle.get("pdus");
+
+      for (int i=0; i<pdus.length; ++i) {
+        SmsMessage msg = SmsMessage.createFromPdu((byte[])pdus[i]);
+
+        GeckoAppShell.notifySmsReceived(msg.getDisplayOriginatingAddress(),
+                                        msg.getDisplayMessageBody(),
+                                        System.currentTimeMillis());
+      }
+    }
+  }
+
+  public static int getNumberOfMessagesForText(String aText) {
+    return SmsManager.getDefault().divideMessage(aText).size();
+  }
+
+  public static void send(String aNumber, String aMessage) {
+    /*
+     * TODO:
+     * This is a basic send method that doesn't handle errors, doesn't listen to
+     * sent and received messages. It's only calling the send method.
+     */
+    try {
+      SmsManager sm = SmsManager.getDefault();
+
+      if (aMessage.length() <= kMaxMessageSize) {
+        sm.sendTextMessage(aNumber, "", aMessage, null, null);
+      } else {
+        ArrayList<String> parts = sm.divideMessage(aMessage);
+        sm.sendMultipartTextMessage(aNumber, "", parts, null, null);
+      }
+    } catch (Exception e) {
+      Log.i("GeckoSmsManager", "Failed to send an SMS: ", e);
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/embedding/android/GeckoSurfaceView.java
@@ -0,0 +1,833 @@
+/* -*- 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);
+
+        getHolder().addCallback(this);
+        inputConnection = new GeckoInputConnection(this);
+        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();
+    }
+
+    void drawSplashScreen() {
+        this.drawSplashScreen(getHolder(), mWidth, mHeight);
+    }
+
+    void drawSplashScreen(SurfaceHolder holder, int width, int height) {
+        // No splash screen for Honeycomb or greater
+        if (Build.VERSION.SDK_INT >= 11) {
+            Log.i(LOG_FILE_NAME, "skipping splash screen");
+            return;
+        }
+
+        Canvas c = holder.lockCanvas();
+        if (c == null) {
+            Log.i(LOG_FILE_NAME, "canvas is null");
+            return;
+        }
+
+        Resources res = getResources();
+
+        File watchDir = new File(GeckoApp.sGREDir, "components");
+        if (watchDir.exists() == false) {
+            // Just show the simple splash screen for "new profile" startup
+            c.drawColor(res.getColor(R.color.splash_background));
+            Drawable drawable = res.getDrawable(R.drawable.splash);
+            int w = drawable.getIntrinsicWidth();
+            int h = drawable.getIntrinsicHeight();
+            int x = (width - w) / 2;
+            int y = (height - h) / 2 - 16;
+            drawable.setBounds(x, y, x + w, y + h);
+            drawable.draw(c);
+
+            Paint p = new Paint();
+            p.setTextAlign(Paint.Align.CENTER);
+            p.setTextSize(32f);
+            p.setAntiAlias(true);
+            p.setColor(res.getColor(R.color.splash_msgfont));
+            c.drawText(res.getString(R.string.splash_firstrun), width / 2, y + h + 16, p);
+        } else {
+            // Show the static UI for normal startup
+            DisplayMetrics metrics = new DisplayMetrics();
+            GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+    
+            // Default to DENSITY_HIGH sizes
+            int toolbarHeight = 80;
+            int faviconOffset = 25;
+            float urlHeight = 24f;
+            int urlOffsetX = 80;
+            int urlOffsetY = 48;
+            if (metrics.densityDpi == DisplayMetrics.DENSITY_MEDIUM) {
+                toolbarHeight = 53;
+                faviconOffset = 10;
+                urlHeight = 16f;
+                urlOffsetX = 53;
+                urlOffsetY = 32;
+            }
+    
+            c.drawColor(res.getColor(R.color.splash_content));
+            Drawable toolbar = res.getDrawable(Build.VERSION.SDK_INT > 8 ?
+                                               R.drawable.splash_v9 :
+                                               R.drawable.splash_v8);
+            toolbar.setBounds(0, 0, width, toolbarHeight);
+            toolbar.draw(c);
+    
+            // XUL/CSS always uses 32px width and height for favicon
+            Drawable favicon = res.getDrawable(R.drawable.favicon32);
+            favicon.setBounds(faviconOffset, faviconOffset, 32 + faviconOffset, 32 + faviconOffset);
+            favicon.draw(c);
+    
+            if (GeckoSurfaceView.mSplashURL != "") {
+                TextPaint p = new TextPaint();
+                p.setTextAlign(Paint.Align.LEFT);
+                p.setTextSize(urlHeight);
+                p.setAntiAlias(true);
+                p.setColor(res.getColor(R.color.splash_urlfont));
+                String url = TextUtils.ellipsize(GeckoSurfaceView.mSplashURL, p, width - urlOffsetX * 2, TextUtils.TruncateAt.END).toString();
+                c.drawText(url, urlOffsetX, urlOffsetY, p);
+            }
+        }
+        holder.unlockCanvasAndPost(c);
+    }
+
+    /*
+     * Called on main thread
+     */
+
+    public void draw(SurfaceHolder holder, ByteBuffer buffer) {
+        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) {
+        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) {
+
+        // 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;
+        }
+
+        if (mShowingSplashScreen)
+            drawSplashScreen(holder, width, height);
+
+        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;
+
+        mFormat = format;
+        mWidth = width;
+        mHeight = height;
+        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("GeckoSurfaceViewJava", "Synchronised draw object is null");
+            }
+        } else if (!mShowingSplashScreen) {
+            // Make sure a frame is drawn before we return
+            // otherwise we see artifacts or a black screen
+            GeckoAppShell.scheduleRedraw();
+            GeckoAppShell.geckoEventSync();
+        }
+    }
+
+    public void surfaceCreated(SurfaceHolder holder) {
+        Log.i(LOG_FILE_NAME, "surface created");
+        GeckoEvent e = new GeckoEvent(GeckoEvent.SURFACE_CREATED);
+        GeckoAppShell.sendEventToGecko(e);
+        if (mShowingSplashScreen)
+            drawSplashScreen();
+    }
+
+    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
+        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) {
+        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) {
+        GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
+        return true;
+    }
+
+    @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_BACK:
+                if (event.getRepeatCount() == 0) {
+                    event.startTracking();
+                    return true;
+                } else {
+                    return false;
+                }
+            case KeyEvent.KEYCODE_MENU:
+                if (event.getRepeatCount() == 0) {
+                    event.startTracking();
+                    break;
+                } else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
+                    break;
+                }
+                // Ignore repeats for KEYCODE_MENU; they confuse the widget code.
+                return false;
+            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:
+                if (!event.isTracking() || event.isCanceled())
+                    return false;
+                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;
+
+        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_BACK:
+                GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
+                return true;
+            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 mShowingSplashScreen = true;
+    static String  mSplashURL = "";
+
+    // 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;
+    KeyListener mKeyListener;
+    Editable mEditable;
+    Editable.Factory mEditableFactory;
+    int mIMEState;
+    String mIMETypeHint;
+    String mIMEActionHint;
+    boolean mIMELandscapeFS;
+
+    // Software rendering
+    Bitmap mSoftwareBitmap;
+    ByteBuffer mSoftwareBuffer;
+    Bitmap mSoftwareBufferCopy;
+
+    Geocoder mGeocoder;
+    Address  mLastGeoAddress;
+
+    final SynchronousQueue<Object> mSyncDraws = new SynchronousQueue<Object>();
+}
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/LauncherShortcuts.java.in
@@ -0,0 +1,261 @@
+/* -*- 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):
+ *   Vladimir Vukicevic <vladimir@pobox.com>
+ *   Wes Johnston <wjohnston@mozilla.com>
+ *   Mark Finkle <mfinkle@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 ***** */
+
+#filter substitution
+package @ANDROID_PACKAGE_NAME@;
+
+import java.io.*;
+import java.util.*;
+
+import org.json.*;
+
+import org.mozilla.gecko.*;
+
+import android.os.*;
+import android.content.*;
+import android.app.*;
+import android.text.*;
+import android.util.*;
+import android.widget.*;
+import android.database.sqlite.*;
+import android.database.*;
+import android.view.*;
+import android.net.Uri;
+import android.graphics.*;
+
+
+public class LauncherShortcuts extends Activity {
+
+    private ArrayList <HashMap<String, String>> mWebappsList;
+    private File mWebappsFolder;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        setContentView(R.layout.launch_app_list);
+
+        final Intent intent = getIntent();
+        final String action = intent.getAction();
+
+        if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
+            // Doing it as a background task, as it involves file access
+            new FetchWebApps().execute();
+        }
+    }
+
+    public void onListItemClick(int id) {
+        HashMap<String, String> map = mWebappsList.get(id);
+        
+        String uri = map.get("uri").toString();
+        String title = map.get("title").toString();
+        String appKey = map.get("appKey").toString();
+        String favicon = map.get("favicon").toString();
+        
+        File manifestFile = new File(mWebappsFolder, appKey + "/manifest.json");
+        
+        // Parse the contents into a string
+        String manifestJson = new String();
+        try {
+            BufferedReader in = new BufferedReader(new FileReader(manifestFile));
+            String line = new String();
+            
+            while ((line = in.readLine()) != null) {
+                manifestJson += line;
+            }
+        } catch (IOException e) { }
+        
+        try {
+            JSONObject manifest = (JSONObject) new JSONTokener(manifestJson).nextValue();
+            uri += manifest.getString("launch_path");
+        } catch (JSONException e) { }
+        
+        Intent shortcutintent = new Intent("org.mozilla.gecko.WEBAPP");
+        shortcutintent.setClass(this, App.class);
+        shortcutintent.putExtra("args", "--webapp=" + uri);
+
+        Intent intent = new Intent();
+        intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);
+        intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutintent);
+
+        DisplayMetrics dm = new DisplayMetrics();
+        getWindowManager().getDefaultDisplay().getMetrics(dm);
+        int size;
+        switch (dm.densityDpi) {
+            case DisplayMetrics.DENSITY_MEDIUM:
+                size = 48;
+                break;
+            case DisplayMetrics.DENSITY_HIGH:
+                size = 72;
+                break;
+            default:
+                size = 72;
+        }
+
+        Bitmap bitmap = BitmapFactory.decodeFile(favicon);
+        if (bitmap != null) {
+            Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, size, size, true);
+            intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, scaledBitmap);
+        }
+
+        // Now, return the result to the launcher
+        setResult(RESULT_OK, intent);
+        finish();
+    }
+    
+    private class FetchWebApps extends AsyncTask<Void, Void, Void> {
+        
+        @Override
+        protected Void doInBackground(Void... unused) {
+            mWebappsList = null;
+            
+            Context context = getApplicationContext();
+            
+            File home = new File(context.getFilesDir(), "mozilla");
+            if (!home.exists())
+                home = new File(context.getExternalFilesDir(null).getPath(), "mozilla");
+            
+            if (!home.exists())
+                return null;
+
+            File profile = null;
+            String[] files = home.list();
+            for (String file : files) {
+                if (file.endsWith(".default")) {
+                    profile = new File(home, file);
+                    break;
+                }
+            }
+
+            if (profile == null)
+                return null;
+
+            // Save the folder path to be used during click event
+            mWebappsFolder = new File(profile, "webapps");
+            if (!mWebappsFolder.exists())
+                return null;
+
+            File webapps = new File(mWebappsFolder, "webapps.json");
+            if (!webapps.exists())
+                return null;
+
+            // Parse the contents into a string
+            String webappsJson = new String();
+            try {
+                BufferedReader in = new BufferedReader(new FileReader(webapps));
+                String line = new String();
+                
+                while ((line = in.readLine()) != null) {
+                    webappsJson += line;
+                }
+            } catch (IOException e) { }
+
+            if (webappsJson.length() == 0)
+                return null;
+            
+            mWebappsList = new ArrayList<HashMap<String, String>>();
+
+            try {
+                JSONObject webApps = (JSONObject) new JSONTokener(webappsJson).nextValue();
+
+                Iterator<Object> appKeys = webApps.keys();
+                HashMap<String, String> map;
+
+                while (appKeys.hasNext()) {
+                    String appKey = appKeys.next().toString();
+                    JSONObject app = webApps.getJSONObject(appKey);
+                    
+                    map = new HashMap<String, String>();
+                    map.put("appKey", appKey);
+                    map.put("favicon", mWebappsFolder.getPath() + "/" + appKey + "/icon.png");
+                    map.put("title", app.getString("title"));
+                    map.put("uri", app.getString("appURI"));
+                    
+                    mWebappsList.add(map);
+                }
+                
+            } catch (JSONException e) {}
+            
+            return null;
+        }
+        
+        @Override
+        protected void onPostExecute(Void unused) {
+            if (mWebappsList != null) {
+                AlertDialog.Builder builder;
+                
+                if (android.os.Build.VERSION.SDK_INT >= 11) {
+                    builder = new AlertDialog.Builder(LauncherShortcuts.this, AlertDialog.THEME_HOLO_LIGHT);
+                } else {
+                    builder = new AlertDialog.Builder(LauncherShortcuts.this);
+                }
+                
+                builder.setTitle(R.string.launcher_shortcuts_title);
+                builder.setAdapter(new SimpleAdapter(
+                    LauncherShortcuts.this,
+                    mWebappsList,
+                    R.layout.launch_app_listitem,
+                    new String[] { "favicon", "title" },
+                    new int[] { R.id.favicon, R.id.title }
+                ), new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int id) {
+                        dialog.dismiss();
+                        onListItemClick(id);
+                        finish();
+                    }
+                });
+                
+                builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
+                    @Override
+                    public void onCancel(DialogInterface dialog) {
+                        dialog.dismiss();
+                        finish();
+                    }
+                });
+                
+                builder.create().show();
+            } else {
+                Toast.makeText(LauncherShortcuts.this, R.string.launcher_shortcuts_empty, Toast.LENGTH_LONG).show();
+                finish();
+            }
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/embedding/android/Makefile.in
@@ -0,0 +1,220 @@
+# ***** 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 the Mozilla browser.
+#
+# 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):
+#   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 *****
+
+DEPTH		= ../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/ipc/app/defs.mk
+
+DIRS = locales
+
+JAVAFILES = \
+  GeckoApp.java \
+  GeckoAppShell.java \
+  GeckoConnectivityReceiver.java \
+  GeckoEvent.java \
+  GeckoSurfaceView.java \
+  GeckoInputConnection.java \
+  AlertNotification.java \
+  SurfaceInfo.java \
+  GeckoBatteryManager.java \
+  GeckoSmsManager.java \
+  $(NULL)
+
+PROCESSEDJAVAFILES = \
+  App.java \
+  Restarter.java \
+  NotificationHandler.java \
+  LauncherShortcuts.java \
+  $(NULL)
+
+
+ifneq (,$(findstring -march=armv7,$(OS_CFLAGS)))
+MIN_CPU_VERSION=7
+else
+MIN_CPU_VERSION=5
+endif
+
+ifeq (,$(ANDROID_VERSION_CODE))
+ANDROID_VERSION_CODE=$(shell $(PYTHON) $(topsrcdir)/toolkit/xre/make-platformini.py --print-buildid | cut -c1-10)
+endif
+
+DEFINES += \
+  -DANDROID_PACKAGE_NAME=$(ANDROID_PACKAGE_NAME) \
+  -DMOZ_APP_DISPLAYNAME="$(MOZ_APP_DISPLAYNAME)" \
+  -DMOZ_APP_NAME=$(MOZ_APP_NAME) \
+  -DMOZ_APP_VERSION=$(MOZ_APP_VERSION) \
+  -DMOZ_CHILD_PROCESS_NAME=$(MOZ_CHILD_PROCESS_NAME) \
+  -DMOZ_MIN_CPU_VERSION=$(MIN_CPU_VERSION) \
+  -DMOZ_CRASHREPORTER=$(MOZ_CRASHREPORTER) \
+  -DANDROID_VERSION_CODE=$(ANDROID_VERSION_CODE) \
+  -DMOZILLA_OFFICIAL=$(MOZILLA_OFFICIAL) \
+  $(NULL)
+
+GARBAGE += \
+  AndroidManifest.xml  \
+  classes.dex  \
+  $(PROCESSEDJAVAFILES) \
+  gecko.ap_  \
+  res/values/strings.xml \
+  R.java \
+  $(NULL)
+
+GARBAGE_DIRS += classes res
+
+# Bug 567884 - Need a way to find appropriate icons during packaging
+ifeq ($(MOZ_APP_NAME),fennec)
+ICON_PATH = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/fennec_48x48.png
+ICON_PATH_HDPI = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/fennec_72x72.png
+
+# we released these builds to the public with shared IDs and need to keep them
+ifeq (org.mozilla.firefox,$(ANDROID_PACKAGE_NAME))
+DEFINES += -DMOZ_ANDROID_SHARED_ID="org.mozilla.firefox.sharedID"
+else ifeq (org.mozilla.firefox_beta,$(ANDROID_PACKAGE_NAME))
+DEFINES += -DMOZ_ANDROID_SHARED_ID="org.mozilla.firefox.sharedID"
+else ifeq (org.mozilla.fennec_aurora,$(ANDROID_PACKAGE_NAME))
+DEFINES += -DMOZ_ANDROID_SHARED_ID="org.mozilla.fennec.sharedID"
+else ifeq (org.mozilla.fennec,$(ANDROID_PACKAGE_NAME))
+DEFINES += -DMOZ_ANDROID_SHARED_ID="org.mozilla.fennec.sharedID"
+endif
+
+else
+ICON_PATH = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/icon48.png
+ICON_PATH_HDPI = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/icon64.png
+DEFINES += -DMOZ_ANDROID_SHARED_ID="$(ANDROID_PACKAGE_NAME).sharedID"
+endif
+
+RES_LAYOUT = \
+  res/layout/notification_progress.xml \
+  res/layout/notification_progress_text.xml \
+  res/layout/notification_icon_text.xml \
+  res/layout/launch_app_list.xml \
+  res/layout/launch_app_listitem.xml \
+  $(NULL)
+
+RES_VALUES = res/values/colors.xml res/values/themes.xml
+
+AB_rCD = $(shell echo $(AB_CD) | sed -e s/-/-r/)
+
+JAVA_CLASSPATH = $(ANDROID_SDK)/android.jar
+
+DEFAULT_BRANDPATH = $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales/en-US/brand.dtd
+DEFAULT_STRINGSPATH = locales/en-US/android_strings.dtd
+LOCALIZED_BRANDPATH = $(DEPTH)/dist/bin/chrome/$(AB_CD)/locale/branding/brand.dtd
+LOCALIZED_STRINGSPATH = $(DEPTH)/dist/bin/chrome/android-res/res/values-$(AB_CD)/android_strings.dtd
+
+ifdef MOZ_CRASHREPORTER
+PROCESSEDJAVAFILES += CrashReporter.java
+MOZ_ANDROID_DRAWABLES += embedding/android/resources/drawable/crash_reporter.png
+RES_LAYOUT += res/layout/crash_reporter.xml
+endif
+
+MOZ_ANDROID_DRAWABLES += embedding/android/resources/drawable/desktop_notification.png
+
+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)
+LOCALIZED_STRINGS_XML = res/values-$(AB_rCD)/strings.xml
+endif
+
+# Override the Java settings with some specific android settings
+include $(topsrcdir)/config/android-common.mk
+
+# Note that we're going to set up a dependency directly between embed_android.dex and the java files
+# Instead of on the .class files, since more than one .class file might be produced per .java file
+classes.dex: $(JAVAFILES) $(PROCESSEDJAVAFILES) R.java
+	$(NSINSTALL) -D classes
+	$(JAVAC) $(JAVAC_FLAGS) -d classes  $(addprefix $(srcdir)/,$(JAVAFILES)) $(PROCESSEDJAVAFILES) R.java
+	$(DX) --dex --output=$@ classes
+
+AndroidManifest.xml $(PROCESSEDJAVAFILES): % : %.in
+	$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
+             $(AUTOMATION_PPARGS) $(DEFINES) $(ACDEFINES) $< > $@
+
+res/drawable/icon.png: $(MOZ_APP_ICON)
+	$(NSINSTALL) -D res/drawable
+	cp $(ICON_PATH) $@
+
+res/drawable-hdpi/icon.png: $(MOZ_APP_ICON)
+	$(NSINSTALL) -D res/drawable-hdpi
+	cp $(ICON_PATH_HDPI) $@
+
+RES_DRAWABLE = $(addprefix res/drawable/,$(notdir $(MOZ_ANDROID_DRAWABLES)))
+
+$(RES_DRAWABLE): $(addprefix $(topsrcdir)/,$(MOZ_ANDROID_DRAWABLES))
+	$(NSINSTALL) -D res/drawable
+	$(NSINSTALL) $^ res/drawable/
+
+$(RES_LAYOUT): $(subst res/,$(srcdir)/resources/,$(RES_LAYOUT))
+	$(NSINSTALL) -D res/layout
+	$(NSINSTALL) $(srcdir)/resources/layout/* res/layout/
+
+$(RES_VALUES): $(subst res/,$(srcdir)/resources/,$(RES_VALUES))
+	$(NSINSTALL) -D res/values
+	$(NSINSTALL) $(srcdir)/resources/values/* res/values/
+
+R.java: $(MOZ_APP_ICON) $(RES_LAYOUT) $(RES_DRAWABLE) $(RES_VALUES) res/drawable/icon.png res/drawable-hdpi/icon.png res/values/strings.xml AndroidManifest.xml
+	$(AAPT) package -f -M AndroidManifest.xml -I $(ANDROID_SDK)/android.jar -S res -J . --custom-package org.mozilla.gecko
+
+gecko.ap_: AndroidManifest.xml res/drawable/icon.png res/drawable-hdpi/icon.png $(RES_LAYOUT) $(RES_DRAWABLE) $(RES_VALUES) res/values/strings.xml FORCE
+	$(AAPT) package -f -M AndroidManifest.xml -I $(ANDROID_SDK)/android.jar  -S res -F $@
+
+res/values/strings.xml: $(DEFAULT_BRANDPATH) $(DEFAULT_STRINGSPATH)
+	mkdir -p res/values
+	$(PYTHON) $(topsrcdir)/config/Preprocessor.py $(DEFINES) \
+	  -DBRANDPATH="$(DEFAULT_BRANDPATH)" \
+	  -DSTRINGSPATH="$(DEFAULT_STRINGSPATH)" \
+	  $(srcdir)/strings.xml.in \
+	  > $@
+
+res/values-$(AB_rCD)/strings.xml: $(LOCALIZED_BRANDPATH) $(LOCALIZED_STRINGSPATH)
+	mkdir -p res/values-$(AB_rCD)
+	$(PYTHON) $(topsrcdir)/config/Preprocessor.py $(DEFINES) \
+	  -DBRANDPATH="$(call core_abspath,$(LOCALIZED_BRANDPATH))" \
+	  -DSTRINGSPATH="$(call core_abspath,$(LOCALIZED_STRINGSPATH))" \
+	  $(srcdir)/strings.xml.in \
+	  > $@
+
+chrome:: $(LOCALIZED_STRINGS_XML)
+
+libs:: classes.dex
+	$(INSTALL) classes.dex $(FINAL_TARGET)
new file mode 100644
--- /dev/null
+++ b/embedding/android/NotificationHandler.java.in
@@ -0,0 +1,104 @@
+/* -*- 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 Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Alex Pakhotin <alexp@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 ***** */
+
+#filter substitution
+package @ANDROID_PACKAGE_NAME@;
+
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+import android.net.Uri;
+
+public class NotificationHandler
+    extends BroadcastReceiver
+{
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (intent != null)
+            handleIntent(context, intent);
+    }
+
+    protected void handleIntent(Context context, Intent notificationIntent) {
+        String action = notificationIntent.getAction();
+        String alertName = "";
+        String alertCookie = "";
+        Uri data = notificationIntent.getData();
+        if (data != null) {
+            alertName = data.getSchemeSpecificPart();
+            alertCookie = data.getFragment();
+            if (alertCookie == null)
+                alertCookie = "";
+        }
+
+        Log.i("GeckoAppJava", "NotificationHandler.handleIntent\n" +
+              "- action = '" + action + "'\n" +
+              "- alertName = '" + alertName + "'\n" +
+              "- alertCookie = '" + alertCookie + "'");
+
+        int notificationID = alertName.hashCode();
+
+        Log.i("GeckoAppJava", "Handle notification ID " + notificationID);
+
+        if (App.mAppContext != null) {
+            // This should call the observer, if any
+            App.mAppContext.handleNotification(action, alertName, alertCookie);
+        } else {
+            // The app is not running, just cancel this notification
+            NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+            notificationManager.cancel(notificationID);
+        }
+
+        if (App.ACTION_ALERT_CLICK.equals(action)) {
+            // Start or bring to front the main activity
+            Intent appIntent = new Intent(Intent.ACTION_MAIN);
+            appIntent.setClassName(context, "@ANDROID_PACKAGE_NAME@.App");
+            appIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            appIntent.putExtra("args", "-alert " + alertName + (alertCookie.length() > 0 ? "#" + alertCookie : ""));
+            try {
+                Log.i("GeckoAppJava", "startActivity with intent: Action='" + appIntent.getAction() + "'" +
+                      ", args='" + appIntent.getStringExtra("args") + "'" );
+                context.startActivity(appIntent);
+            } catch (ActivityNotFoundException e) {
+                Log.e("GeckoAppJava", "NotificationHandler Exception: ", e);
+            }
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/embedding/android/Restarter.java.in
@@ -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) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Brad Lassey <blassey@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 ***** */
+
+#filter substitution
+package @ANDROID_PACKAGE_NAME@;
+
+import android.app.*;
+import android.content.*;
+import android.util.*;
+import android.os.*;
+import java.io.*;
+import org.mozilla.gecko.GeckoAppShell;
+
+public class Restarter extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i("Restarter", "trying to restart @MOZ_APP_NAME@");
+        try {
+            int countdown = 40;
+            while (GeckoAppShell.checkForGeckoProcs() &&  --countdown > 0) {
+                // Wait for the old process to die before we continue
+                try {
+                    Thread.currentThread().sleep(100);
+                } catch (InterruptedException ie) {}
+            }
+            
+            if (countdown <= 0) {
+                // if the countdown expired, something is hung
+                GeckoAppShell.killAnyZombies();
+                countdown = 10;
+                // wait for the kill to take effect
+                while (GeckoAppShell.checkForGeckoProcs() &&  --countdown > 0) {
+                    try {
+                        Thread.currentThread().sleep(100);
+                    } catch (InterruptedException ie) {}
+                }
+            }
+        } catch (Exception e) {
+            Log.i("Restarter", e.toString());
+        }
+        try {
+            String action = "android.intent.action.MAIN";
+            Intent intent = new Intent(action);
+            intent.setClassName("@ANDROID_PACKAGE_NAME@",
+                                "@ANDROID_PACKAGE_NAME@.App");
+            Bundle b = getIntent().getExtras();
+            if (b != null)
+                intent.putExtras(b);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            Log.i("GeckoAppJava", intent.toString());
+            startActivity(intent);
+        } catch (Exception e) {
+            Log.i("Restarter", e.toString());
+        }
+        // Give the new process time to start before we die
+        GeckoAppShell.waitForAnotherGeckoProc();
+        System.exit(0);
+    }
+};
new file mode 100644
--- /dev/null
+++ b/embedding/android/SurfaceInfo.java
@@ -0,0 +1,7 @@
+package org.mozilla.gecko;
+
+public class SurfaceInfo {
+    public int format;
+    public int width;
+    public int height;
+}
new file mode 100644
--- /dev/null
+++ b/embedding/android/locales/Makefile.in
@@ -0,0 +1,48 @@
+# ***** 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
+# the Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Michael Wu <mwu@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 *****
+
+DEPTH     = ../../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+relativesrcdir = embedding/android/locales
+
+include $(DEPTH)/config/autoconf.mk
+
+DEFINES += -DAB_CD=$(AB_CD)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/embedding/android/locales/en-US/android_strings.dtd
@@ -0,0 +1,21 @@
+
+<!ENTITY  splash_firstrun "Setting up &brandShortName;\u2026">
+
+<!ENTITY  no_space_to_start_error "There is not enough space available for &brandShortName; to start.">
+<!ENTITY  error_loading_file "An error occurred when trying to load files required to run &brandShortName;">
+
+<!ENTITY  crash_reporter_title "&brandShortName; Crash Reporter">
+<!ENTITY  crash_message "&brandShortName; has crashed. Your tabs should be listed on the &brandShortName; Start page when you restart.">
+<!ENTITY  crash_help_message "Please help us fix this problem!">
+<!ENTITY  crash_send_report_message "Send Mozilla a crash report">
+<!ENTITY  crash_include_url "Include page address">
+<!ENTITY  crash_close_label "Close">
+<!ENTITY  crash_restart_label "Restart &brandShortName;">
+<!ENTITY  sending_crash_report "Sending crash report\u2026">
+<!ENTITY  exit_label "Exit">
+<!ENTITY  continue_label "Continue">
+
+<!ENTITY  launcher_shortcuts_title "&brandShortName; Web Apps">
+<!ENTITY  launcher_shortcuts_empty "No web apps were found">
+
+<!ENTITY choose_file "Choose File">
new file mode 100644
--- /dev/null
+++ b/embedding/android/locales/jar.mn
@@ -0,0 +1,4 @@
+#filter substitution
+
+android-res.jar:
+  res/values-@AB_CD@/android_strings.dtd    (%android_strings.dtd)
new file mode 100644
--- /dev/null
+++ b/embedding/android/locales/l10n.ini
@@ -0,0 +1,5 @@
+[general]
+depth = ../../..
+
+[compare]
+dirs = embedding/android
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5804e08a772e010f0c986460898fa18f321c9311
GIT binary patch
literal 2860
zc$@(&3)A$8P)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H13aLp%K~!jg?V4F^9oKose`lGw3ojQ@qC{FEMcbmhN@)|ff$da{
zVY^6M)aXmm#Vt^@MS#Yrg8&8k)H;q`7)`Uai2Ih8E<n)*D2hBJNYEvXn>GpHSU@YO
zEGd$rHj0}l-fd>i_w`}sUS3j^MKx%p0RD)BduHa`GvELGzwMk$qpJKMi@n<le82J@
zfV(a40l3@ZdoO<dqrdth${&e@{dY^DTZwc2s)p>NU;5IY{eAEKE`V2Gebu6T@v%ow
z3=QwQ=k#ak+Lc4o(`VlVb_XywHy2ya&~wi|k1+;oE!J9`bKiGgRS^+F2#Co4s-GkY
zXV3lT;hn&?nGnT`PM<z~N2_Cu>Gnkx+jE^ytJ}u(-rGweb<T0(#0f+tyeojtWNDi6
z%BMbc2N3@6#Cy+cpZp}iu3)zTkYYfRBs7~%nwy*5XVzLUV2s5WgE0mdc{B(Hn3|g6
z=;S1dlC%@DEJf7NYBe}>_AF^TK~Yd}&hq4wPaz?Yq$ydJkt7LemXWktq-jc$BqT|~
zjT<*m)!YojZ2)u!iU81THnG+g%0{5F1;83`hLB}|VPI&8xD=ztAR-uJ7#@F^)8G0!
zSqP*d0|ijV$M2`FzecmsKu{!v{2kV!#X^b*SqR(ox-A2p0i91DMObIC))AFTs0577
zudOvi<uV~nFvhZf<N$G{N>r&)9RysA^<~NjCmD_Vsjsil+FVB^@W|tj;Y%?-%@9?B
zDjqNrh|-iGg3U6lHC=r~gx%a_2LM4;an5zk#u|$?2ImZ!fKde%R1BcG2{{-XK1g+N
zgfwljxxPxPxkhDZl*QRIREPFc9oWylLzDFTHj&FsPl_m`MayRdW58I0wOe}R49MOX
z=m2oOCnQD@NItQ#IjAU#uxdE^&<{}^IKcYV1%jruHtS?r#)bJAlE!rm8SQ$Va&3^O
zUidjAX>sHF5&#AYlLR#mli%1f(?XwHnb5XULS!cZ-)%mMaNgzp&Sw;HaPm>A)iP_B
zXOJv6r;cW;t1Ec#@v$c^#T*$Op}w++0)4|{s43inLTPJyl(z*HrmK(2Z2;WFfOFmE
zJ3x9s4IH?SxKtrDRtXYFL(1X9hx3>%%0tH=qQ8HDAOFygaq{H*7#|-eNXEv>MSNT$
zt_<YwEuz!T{Lx`(&kXcBuQ7(WT&7g5Q0l8v?yFL+RVml{DA#J#_6@WD$P|suD+~+|
zVd4l^in+LSg=164`O8253;ynJ{+74D{T-fp<{9cqi%NeVHi|HjXLDnTfkWf?N(CR6
zaB&G6$GB32k0V^{@sZ!QpnC^Ud;t|S2$}^{Kr%tHfMyw`N<Xa|E2KBBQ!7_EdiV%Q
zvyFG2pM3s#%H=WuaUAo)M}CUq_aCF(Y%wxCOtn;^wYfp_#yZBvBuSezX_2KVX_B`r
z$;h%SSF-EG?;1wuP#s29^Q2WEx7J~u#d(W!w(HcT>Hr~Y;i8Cv!2y2jH(%l#Z~Y5P
ztE+tM<1h2RlP8&&m|*eZMZS0DKltFuA7)}=oF9GSNBH#TKg(LZPH5G!zFdUJBG!xM
zxk0t-A6*96BLf`(*5%6E0!Y^<yrWzjKte*bQeky%jh8<0GRw=$eC*eLgEwA(o%_bd
z`1I#K%izEOZ~X2ny!_HjG#U*)@jJiG!i5V22_)?XE-vSBUIp#^?MPhj#y}Uq95+Xy
zUZt(?DDQDDA}-ZXNhwvzjE#=*Ctvtue)$()1mNiS1i$i2FXAK5i$DJhOdJ^p;Aem4
z1-|&XFYv$v_amxg?H0zzdBs^Iv@JFyk3v2A$SzT6*F4~Te!JXP#rm9uxLUz`hmT4e
zm^gv+o<qkzK(aD}tCaZDzy2#8o|@vPpZkgKeC46RJRBDC-FLpnKY!~jqDloDml!<o
zAR@wnsrR!q`wkjeT)9MWmdH6=2-G(=3w>_q19PCm+ZhOHii&`BXgMRaQ#1=K&A!dh
z!AaBw;xuFF(q(3+XNj$W0ze3XKJN)3>%L-seva>a``@WmYqXjT+RX)wv8*jEkhI$n
zN3<JFBxFcXLQy^EVRTPXNVYLxjKLNO#u$sWdBJ5>32Boks$i{SePe?VQfBAQcjua&
zJI{grBh1Y&Y^~A40uqF^^*nDF=V{e%5Yhw*8LEOUN(f_3H!tKqxqB(Gdl)SMbRy1H
zD@qGz$=aKgYs0uGw_)!+&z$;jcdqH#Ssr`jVb1;cbobt=r=F%%t`I~~<8h@Lt;Tf(
z#aLSuid(H?e%|}NF(9Jbin-oNF{W6Z;yR=xja5p6qeu`=J^dk`{@|1R-e>-RrR8hP
zoS)_KM<3_x^f}fyHhAOnpW*n_6d!r+S%NC1fl=z~S5VEi7N*$B=Jl58R-?H-dgo3Y
zmCIP~h$D}$l+Yp)8DnwY({8U5R|l~}BYf>&zsadnr}*aE-(_Zg7Uv?K_`nmKo|)$3
zuf4+6ON%`B?1%ZcbN@hXYz#4$G^^t(6^sNz5RCKqYQSaz8v-Fsy8zNu_O>4|J;hT<
zQwV`$k3NiXHm|%yn4g>H$l);*!?mS3`bQ=)QI#)$<*P&r*H_lC*7ApMzKNtQHa0i$
zYWT|6|B-rQh<(Eit}I-@W*I>Ong$2~jTK2!LdcLbAjO8pdrz9Cdjqh|gitVWetL$u
zT1C?g=PU-p;>An2N*OCcedRpW!9$!`yiTQm6elf=1}ejov{vWAMZ`Ad%)&aY>n$3q
zs|<}8R+|=S=DW9|j?T+#C9mQNan5xU+#Q(^#gyYm$FV*_5iZRyGV#Ds1cO1jwzLdD
zdvh7d8o0h8qVgb7sfx3X%J4X}>S)rWu{uZEZV>`x2KJ3PmM`YVCwagr6fD5>0^k--
zynRzIHv<4=abc0DTES$2Aj0LvOS$8fOZ1i}A#Iaxu7F)5@s8TaaT?1rv_im(5<)-=
zHQRMX+kugBM+g=P0@e~z(4dG0vLtZz;^i&_ckIMAis<XBG5O$RH?8+jgE59{%d3d7
zs5MxV7hG)(n4q+;o&yb}3PA-yQ3GNPilLo2)ETT%j5DY)fJePz#Dd9BLcI5TV_+wM
z&`#J48_b-YCMuWFkmYqzapLC7B|?&7ox?bbDX2DpwF2Z$XbqSkSP_B<gaFnMS_zlW
zg!}|92pU<^dJB<2$nu63dEiz)VDcj~d+gY;_~`u;sA?C$%C%)Wj<vPp?xBEfzQ)$T
zTZwLc6!U5CGu?IcHU><-XH_lG_@Y&>UcEXxF?j@I4Ap9tfuVtXGw&Uys6wr^xZ+5~
zIfwBsKeceh>4mj96BHZuLQAFRY)7=HJ_QMaNI-*hSqPG!R)i4H5W3ZHZXsfT8qhGt
zh^mSQD!^E~-F|=PC4GH;y!B7t=&tBZYudBYyFa@<2|KzsG0^GmvL_w|E&^?1jC!C9
z988kr5fMVYQFjXq3*1X0A~c&#Bxnmb2wVd)K(@upxw-RaUjO~q9#PeM@t2!$Vd28p
zcHT6=0B|300(j6`+ZRPq>Hk~1+lZ!Vx*;O}1-wIHLO?J;1XO{2z#uz+Gq^WI$z$?$
zU=?TpqJ>u*^0L|$trIZsHR*r|^n_9K-oF~$WqA+4-IgDC0RID)+0%@d7E09s0000<
KMNUMnLSTY|v0hLB
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2c557bf46d1e57590cd0ace752e5b26756e2865f
GIT binary patch
literal 1843
zc$|Gz3s4hR6io#|<tu{Uj<aqMKZX2BLL?{z0#SoNLIng(*etMyWaDNv2?e#Zs1yYi
zQLNgkVDX1U(L_W<bOgVpT2Vw>6bwqCK>-!2PP<XDozCE!*?sTcIp@Cf?mIi18x<Mu
zGG_7^5{cv@iU^G%W*^%#%7J(v<aZt;rb&2M94<jqaGe~3Nx=#<83sfuc`6(O%N2$d
zSKvSr$qq!Mad@0~ArC@T6uAvU(W^8h+X$Si*T^9yj04GVDx&6tJ=L`!fGGIjVt+AB
ztP#LzNJIt(OEMy*P=*o;P=IrTfIvNuD4>FIIiOditF=5mAADDrN33mWDhRxT;7UID
zUR0bo3J{<e4ER%Meh`gK1K4y5oyp>GIKBXbMyF9}3@V-BN9XXE3?7XJd>A0H8>UF%
z#e@n!>_tR;Fb&5wJStVE(@}Ix3W}vt=>Y)&wi*nEAA#`G8q~O4@2A$1hbn}^S_nfl
zID)DHTP#mTSK@pS96EwZ^RcX2`=L!l!>D?>hDxW<sQ&?q#h-?%R3Fh=JO)nrG+HY)
zXkcm#tVLI15OHxSWE)w-6JW3$M=>dirVn*7Dh<U^Z5pZp1QLHhEQb)aEq;d=i+Lio
z7MH6bSQN?!i3|ln6g+=n080=|^ABSO)9Lh3A(z7q3lK0_G$Jct3OPgEP!w9Jg4OsC
zSMe{GJuKH|h4*Y2Lsr2GA%?1ecSq(S!)sv<ujM^gF}xPmuv{u3hWfvY9!e4Ju`NH^
zmIyu?A664?#|T>&8t)VkUrU)NG+3%{=`OBb00n!lZ`?$0vm5Du4m|GRG-W~EeD~vC
zUaX0}r?jGJ>5#nAn3FT$wqbL<V^CC`BYXU+JIRH<LP1-JWMe`18wYjqLhC}Sf9`3c
zqr=&b-8<2rl^sjTdm?|;c}~cZ44P!>=W?0sd|`z5YX0201t-G)o<M(d=;+ZzbouhX
z44Ij`PuOd#5^Hzu+LgM~BQ`cRIzB#Ll>2*YYiDQYmB!s;8P0ZjZ;kdxjvR54N~M~a
zmm3<wMRgOMV;3(Tr*)_~d)9uNY-&NnHHVs-n&(gM-d&uKl=Qa$f>NnGX*REqNPA)Q
z>2h5P=hq36U(3{#%VY?G0uvn_9Sx5jKYhGb4oZr@a&o;pFu-k14{Ai}NYk^uzG(3A
z@!>wOT90`|Cce6M9{76R+D3C_B{cfUsD)orq&_odR6Q}7OxVNT?44eSWB0RXo7rsk
z)Bb)|dy;Q+YwIF1natvHi=lDO4pr8Uj+F6<(IJM_t0VjR`aJV4F|sbF*Bfh3pWa(p
z$?P4>JZUt}Bjm@wlt?6=uIoqhB_p?67WMlXE}y8aZJe<>Gqbe5UU;~?+@k4@dt|q!
zea(Oi@0XlSzG-1+PM!QS<Yn`X8!kB^(+*cuJaK>KamIXfz570-O=rG;LzR({aj?AH
zujHF;H_MAG^qF~rRXx4EjHcVS6(O>Y(k7Z}pVO4)1WP41$i-AsBeqy9nY!74VcrC`
zapj1P?RAfL7rG^FtB$pH+Ov(PuBJA%1jNpYloieSQFR7u-P#wwq-ZcCATaQ{4&iL<
zSnHDRH0FT5dUH`(S=nH0edkCIcP5kRT`_&#tt|}-+P2qiZTra;!8!!VO<1zz%9Vut
zkkOMSPu^ct<kkE7t!Z!6rtYK*xAJ&2fpb!LczA-dWOmX1t}Z3KAwNHVAl`ssTW5W)
z4f57`dqlUG%;r8H-)vz?^hJ-2dsN8r3}d@KP(5ddv3PXZefQD>2XbC<j?ce$PdW2`
z&Zs~7TkPB}wwujn)0hxE`=vu3x9b=<Xg~E%PNS&t_PLE*)la9z&0bY?S6ib_?x}6&
z#QnCZX>8oAWqM0hUv_TMh=>Q(-??lEWcOeu*A>D&3iBeAu~TX-jgxzFx?W^-TCJ2r
z_FY!1D~H9pzBDN*8r<^GvTSzUx|_1+pOsZs7Fv6HF3zV3gYGspg=J-Bm0Z7Zu42I9
w5(#p1bL-l%V~6%Cx%2M{m)1<`t{~Zww5cgSI1haLz3n$93X2Rq79z|32mAQ#7ytkO
new file mode 100644
--- /dev/null
+++ b/embedding/android/resources/layout/crash_reporter.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent"
+              android:layout_height="fill_parent"
+              android:orientation="vertical"
+              android:padding="10px" >
+  <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:padding="10px"
+            android:textStyle="bold"
+            android:text="@string/crash_message"/>
+  <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:padding="10px"
+            android:text="@string/crash_help_message"/>
+  <CheckBox android:id="@+id/send_report"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:checked="true"
+            android:text="@string/crash_send_report_message" />
+  <CheckBox android:id="@+id/include_url"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/crash_include_url" />
+  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:padding="10px"
+                android:gravity="center_horizontal" >
+    <Button android:id="@+id/close"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginRight="10px"
+            android:minWidth="120sp"
+	    android:onClick="onCloseClick"
+            android:text="@string/crash_close_label" />
+    <Button android:id="@+id/restart"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="10px"
+            android:minWidth="120sp"
+	    android:onClick="onRestartClick"
+            android:text="@string/crash_restart_label" />
+  </LinearLayout>
+</LinearLayout>
new file mode 100644
--- /dev/null
+++ b/embedding/android/resources/layout/launch_app_list.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+         android:layout_width="match_parent"
+         android:layout_height="match_parent"
+         android:padding="3dip"
+         android:orientation="vertical"
+         android:windowIsFloating="true">
+ </LinearLayout>
new file mode 100644
--- /dev/null
+++ b/embedding/android/resources/layout/launch_app_listitem.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:paddingLeft="16dip"
+    android:paddingRight="16dip"
+    android:orientation="horizontal"
+    android:gravity="left">
+    <ImageView
+        android:id="@+id/favicon"
+        android:layout_width="48dip"
+        android:layout_height="48dip"
+        android:layout_marginRight="12dip"
+        android:layout_gravity="center_vertical"
+        android:adjustViewBounds="true"
+        android:scaleType="fitCenter"/>
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center_vertical"
+        android:textAppearance="?android:attr/textAppearanceLargeInverse"
+        android:ellipsize="marquee"
+        android:fadingEdge="horizontal"/>
+ </LinearLayout>
new file mode 100644
--- /dev/null
+++ b/embedding/android/resources/layout/notification_icon_text.xml
@@ -0,0 +1,34 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:orientation="vertical"
+        android:paddingLeft="5dp"
+        >
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        >
+        <ImageView android:id="@+id/notificationImage"
+            android:layout_width="25dp"
+            android:layout_height="25dp"
+            android:scaleType="fitCenter" />
+        <TextView android:id="@+id/notificationTitle"
+            android:textAppearance="@android:style/TextAppearance.StatusBar.EventContent.Title"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+            android:paddingLeft="4dp"
+            />
+    </LinearLayout>
+    <TextView android:id="@+id/notificationText"
+          android:textAppearance="@android:style/TextAppearance.StatusBar.EventContent"
+	      android:layout_width="fill_parent"
+	      android:layout_height="wrap_content"
+	      android:paddingLeft="4dp"
+	      />
+</LinearLayout>
new file mode 100644
--- /dev/null
+++ b/embedding/android/resources/layout/notification_progress.xml
@@ -0,0 +1,53 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:orientation="vertical"
+        android:paddingTop="7dp"
+        android:paddingLeft="5dp"
+        >
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        >
+        <ImageView android:id="@+id/notificationImage"
+            android:layout_width="25dp"
+            android:layout_height="25dp"
+            android:scaleType="fitCenter" />
+        <TextView android:id="@+id/notificationTitle"
+            android:textAppearance="@android:style/TextAppearance.StatusBar.EventContent.Title"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+            android:paddingLeft="10dp"
+            />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        >
+        <TextView android:id="@+id/notificationText"
+            android:textAppearance="@android:style/TextAppearance.StatusBar.EventContent"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingLeft="3dp"
+            />
+
+        <ProgressBar android:id="@+id/notificationProgressbar"
+            style="?android:attr/progressBarStyleHorizontal"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="1dip"
+            android:layout_marginBottom="1dip"
+            android:layout_marginLeft="4dip"
+            android:layout_marginRight="10dip"
+            android:layout_centerHorizontal="true" />
+    </LinearLayout>
+
+</LinearLayout>
new file mode 100644
--- /dev/null
+++ b/embedding/android/resources/layout/notification_progress_text.xml
@@ -0,0 +1,46 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:orientation="vertical"
+        android:paddingLeft="5dp"
+        >
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        >
+        <ImageView android:id="@+id/notificationImage"
+            android:layout_width="25dp"
+            android:layout_height="25dp"
+            android:scaleType="fitCenter" />
+        <TextView android:id="@+id/notificationTitle"
+            android:textAppearance="@android:style/TextAppearance.StatusBar.EventContent.Title"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+            android:paddingLeft="4dp"
+            />
+    </LinearLayout>
+
+    <ProgressBar android:id="@+id/notificationProgressbar"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="16dip"
+        android:layout_marginTop="1dip"
+        android:layout_marginBottom="1dip"
+        android:layout_marginLeft="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_centerHorizontal="true" />
+
+    <TextView android:id="@+id/notificationText"
+        android:textAppearance="@android:style/TextAppearance.StatusBar.EventContent"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:paddingLeft="4dp"
+        />
+
+</LinearLayout>
new file mode 100644
--- /dev/null
+++ b/embedding/android/resources/values/colors.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <color name="splash_background">#000000</color>
+  <color name="splash_msgfont">#ffffff</color>
+  <color name="splash_urlfont">#000000</color>
+  <color name="splash_content">#ffffff</color>
+</resources>
+
new file mode 100644
--- /dev/null
+++ b/embedding/android/resources/values/themes.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="GreyTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <item name="android:windowBackground">@color/splash_background</item>
+    </style>
+</resources>
new file mode 100644
--- /dev/null
+++ b/embedding/android/strings.xml.in
@@ -0,0 +1,27 @@
+#filter substitution
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE resources [
+#includesubst @BRANDPATH@
+#includesubst @STRINGSPATH@
+]>
+<resources>
+  <string name="splash_firstrun">&splash_firstrun;</string>
+  <string name="no_space_to_start_error">&no_space_to_start_error;</string>
+  <string name="error_loading_file">&error_loading_file;</string>
+
+  <string name="crash_reporter_title">&crash_reporter_title;</string>
+  <string name="crash_message">&crash_message;</string>
+  <string name="crash_help_message">&crash_help_message;</string>
+  <string name="crash_send_report_message">&crash_send_report_message;</string>
+  <string name="crash_include_url">&crash_include_url;</string>
+  <string name="crash_close_label">&crash_close_label;</string>
+  <string name="crash_restart_label">&crash_restart_label;</string>
+  <string name="sending_crash_report">&sending_crash_report;</string>
+  <string name="exit_label">&exit_label;</string>
+  <string name="continue_label">&continue_label;</string>
+
+  <string name="launcher_shortcuts_title">&launcher_shortcuts_title;</string>
+  <string name="launcher_shortcuts_empty">&launcher_shortcuts_empty;</string>
+  
+  <string name="choose_file">&choose_file;</string>
+</resources>