Bug 786380 - Implement new Java-based updater for Android r=cpeterson,mfinkle a=akeybl
authorJames Willcox <jwillcox@mozilla.com>
Fri, 31 Aug 2012 09:31:29 -0400
changeset 109791 25a31e8d9ae8d9cc802469bacb3eca580968a690
parent 109790 1f82974dc08c381e63b73db36b045c5ef5e6cdf2
child 109792 a62178800150870dc2a54dba35bdf8427e9efce5
push id214
push userakeybl@mozilla.com
push dateWed, 14 Nov 2012 20:38:59 +0000
treeherdermozilla-release@c8b08ec8e1aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpeterson, mfinkle, akeybl
bugs786380
milestone17.0a2
Bug 786380 - Implement new Java-based updater for Android r=cpeterson,mfinkle a=akeybl
mobile/android/app/mobile.js
mobile/android/base/AndroidManifest.xml.in
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
mobile/android/base/GeckoUpdateReceiver.java
mobile/android/base/Makefile.in
mobile/android/base/Restarter.java.in
mobile/android/base/UpdateService.java
mobile/android/base/UpdateServiceHelper.java.in
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/strings.xml.in
mobile/android/chrome/content/about.xhtml
mobile/android/components/Makefile.in
mobile/android/components/MobileComponents.manifest
mobile/android/components/UpdatePrompt.js
mobile/android/installer/package-manifest.in
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -485,41 +485,21 @@ pref("browser.search.param.yahoo-fr-ja",
 #endif
 
 /* prefs used by the update timer system (including blocklist pings) */
 pref("app.update.timerFirstInterval", 30000); // milliseconds
 pref("app.update.timerMinimumDelay", 30); // seconds
 
 #ifdef MOZ_UPDATER
 /* prefs used specifically for updating the app */
-pref("app.update.enabled", true);
-pref("app.update.auto", false);
+pref("app.update.enabled", false);
 pref("app.update.channel", "@MOZ_UPDATE_CHANNEL@");
-pref("app.update.mode", 1);
-pref("app.update.silent", false);
-#ifdef MOZ_PKG_SPECIAL
-pref("app.update.url", "https://aus2.mozilla.org/update/4/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%-@MOZ_PKG_SPECIAL@/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/%PLATFORM_VERSION%/update.xml");
-#else
-pref("app.update.url", "https://aus2.mozilla.org/update/4/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/%PLATFORM_VERSION%/update.xml");
-#endif
-pref("app.update.promptWaitTime", 43200);
-pref("app.update.idletime", 60);
-pref("app.update.showInstalledUI", false);
-pref("app.update.incompatible.mode", 0);
-pref("app.update.download.backgroundInterval", 0);
 
-#ifdef MOZ_OFFICIAL_BRANDING
-pref("app.update.interval", 86400);
-pref("app.update.url.manual", "http://www.mozilla.com/%LOCALE%/m/");
-pref("app.update.url.details", "http://www.mozilla.com/%LOCALE%/mobile/releases/");
-#else
-pref("app.update.interval", 3600);
-pref("app.update.url.manual", "http://www.mozilla.com/%LOCALE%/mobile/");
-pref("app.update.url.details", "http://www.mozilla.com/%LOCALE%/mobile/");
-#endif
+// If you are looking for app.update.url, we no longer use it.
+// See mobile/android/base/UpdateServiceHelper.java.in
 #endif
 
 // replace newlines with spaces on paste into single-line text boxes
 pref("editor.singleLine.pasteNewlines", 2);
 
 // threshold where a tap becomes a drag, in 1/240" reference pixels
 // The names of the preferences are to be in sync with nsEventStateManager.cpp
 pref("ui.dragThresholdX", 25);
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -148,16 +148,22 @@
 
         <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>
 
+        <receiver android:name="org.mozilla.gecko.GeckoUpdateReceiver">
+            <intent-filter>
+                <action android:name="@ANDROID_PACKAGE_NAME@.CHECK_UPDATE_RESULT" />
+            </intent-filter>
+        </receiver>
+
         <receiver android:name="org.mozilla.gecko.GeckoMessageReceiver"
                   android:permission="@ANDROID_PACKAGE_NAME@.permissions.PASSWORD_PROVIDER">
             <intent-filter>
                   <action android:name="org.mozilla.gecko.INIT_PW"></action>
             </intent-filter>
         </receiver>
 
         <activity android:name="Restarter"
@@ -224,16 +230,23 @@
                   android:authorities="@ANDROID_PACKAGE_NAME@.db.formhistory"
                   android:permission="@ANDROID_PACKAGE_NAME@.permissions.FORMHISTORY_PROVIDER"
                   android:protectionLevel="signature"/>
 
         <provider android:name="@ANDROID_PACKAGE_NAME@.db.TabsProvider"
                   android:authorities="@ANDROID_PACKAGE_NAME@.db.tabs"
                   android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
 
+        <service
+            android:exported="false"
+            android:name="org.mozilla.gecko.updater.UpdateService"
+            android:process="@MANGLED_ANDROID_PACKAGE_NAME@.UpdateService">
+        </service>
+
+
 #include ../sync/manifests/SyncAndroidManifest_services.xml.in
     </application>
 
     <permission android:name="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"
                 android:protectionLevel="signature"/>
 
     <permission android:name="@ANDROID_PACKAGE_NAME@.permissions.PASSWORD_PROVIDER"
                 android:protectionLevel="signature"/>
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -11,16 +11,18 @@ import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.PluginLayer;
 import org.mozilla.gecko.gfx.PointUtils;
 import org.mozilla.gecko.ui.PanZoomController;
 import org.mozilla.gecko.util.GeckoAsyncTask;
 import org.mozilla.gecko.util.GeckoBackgroundThread;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.GeckoEventResponder;
 import org.mozilla.gecko.GeckoAccessibility;
+import org.mozilla.gecko.updater.UpdateServiceHelper;
+import org.mozilla.gecko.updater.UpdateService;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.Context;
@@ -122,17 +124,16 @@ abstract public class 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_ALERT_CALLBACK = "org.mozilla.gecko.ACTION_ALERT_CALLBACK";
     public static final String ACTION_WEBAPP_PREFIX = "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 final String ACTION_LOAD          = "org.mozilla.gecko.LOAD";
-    public static final String ACTION_UPDATE        = "org.mozilla.gecko.UPDATE";
     public static final String ACTION_INIT_PW       = "org.mozilla.gecko.INIT_PW";
     public static final String SAVED_STATE_TITLE         = "title";
     public static final String SAVED_STATE_IN_BACKGROUND = "inBackground";
 
     public static final String PREFS_NAME          = "GeckoApp";
     public static final String PREFS_OOM_EXCEPTION = "OOMException";
     public static final String PREFS_WAS_STOPPED   = "wasStopped";
 
@@ -909,18 +910,16 @@ abstract public class GeckoApp
             } else if (event.equals("DOMFullScreen:Start")) {
                 mDOMFullScreen = true;
             } else if (event.equals("DOMFullScreen:Stop")) {
                 mDOMFullScreen = false;
             } else if (event.equals("Permissions:Data")) {
                 String host = message.getString("host");
                 JSONArray permissions = message.getJSONArray("permissions");
                 showSiteSettingsDialog(host, permissions);
-            } else if (event.equals("Update:Restart")) {
-                doRestart("org.mozilla.gecko.restart_update");
             } else if (event.equals("Tab:ViewportMetadata")) {
                 int tabId = message.getInt("tabID");
                 Tab tab = Tabs.getInstance().getTab(tabId);
                 if (tab == null)
                     return;
                 tab.setZoomConstraints(new ZoomConstraints(message));
                 // Sync up the layer view and the tab if the tab is currently displayed.
                 LayerView layerView = mLayerView;
@@ -999,16 +998,18 @@ abstract public class GeckoApp
                 String text = message.getString("text");
                 GeckoAppShell.openUriExternal(text, "text/plain", "", "", Intent.ACTION_SEND, "");
             } else if (event.equals("Share:Image")) {
                 String src = message.getString("url");
                 String type = message.getString("mime");
                 GeckoAppShell.shareImage(src, type);
             } else if (event.equals("Sanitize:ClearHistory")) {
                 handleClearHistory();
+            } else if (event.equals("Update:Check")) {
+                startService(new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE, null, this, UpdateService.class));
             }
         } catch (Exception e) {
             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
     public String getResponse() {
         Log.i(LOGTAG, "Return " + mCurrentResponse);
@@ -1486,21 +1487,16 @@ abstract public class GeckoApp
             }
             if (profileName != null || profilePath != null) {
                 mProfile = GeckoProfile.get(this, profileName, profilePath);
             }
         }
 
         BrowserDB.initialize(getProfile().getName());
 
-        if (ACTION_UPDATE.equals(action) || args != null && args.contains("-alert update-app")) {
-            Log.i(LOGTAG,"onCreate: Update request");
-            checkAndLaunchUpdate();
-        }
-
         String passedUri = null;
         String uri = getURIFromIntent(intent);
         if (uri != null && uri.length() > 0) {
             passedUri = uri;
         }
 
         if (mRestoreMode == GeckoAppShell.RESTORE_NONE && getProfile().shouldRestoreSession()) {
             Log.i(LOGTAG, "Restoring crash");
@@ -1582,31 +1578,31 @@ abstract public class GeckoApp
         registerEventListener("Gecko:Ready");
         registerEventListener("Toast:Show");
         registerEventListener("DOMFullScreen:Start");
         registerEventListener("DOMFullScreen:Stop");
         registerEventListener("ToggleChrome:Hide");
         registerEventListener("ToggleChrome:Show");
         registerEventListener("ToggleChrome:Focus");
         registerEventListener("Permissions:Data");
-        registerEventListener("Update:Restart");
         registerEventListener("Tab:HasTouchListener");
         registerEventListener("Tab:ViewportMetadata");
         registerEventListener("Session:StatePurged");
         registerEventListener("Bookmark:Insert");
         registerEventListener("Accessibility:Event");
         registerEventListener("Accessibility:Ready");
         registerEventListener("Shortcut:Remove");
         registerEventListener("WebApps:Open");
         registerEventListener("WebApps:Install");
         registerEventListener("WebApps:Uninstall");
         registerEventListener("DesktopMode:Changed");
         registerEventListener("Share:Text");
         registerEventListener("Share:Image");
         registerEventListener("Sanitize:ClearHistory");
+        registerEventListener("Update:Check");
 
         if (SmsManager.getInstance() != null) {
           SmsManager.getInstance().start();
         }
 
         mBatteryReceiver = new GeckoBatteryManager();
         mBatteryReceiver.registerFor(mAppContext);
 
@@ -1617,16 +1613,18 @@ abstract public class GeckoApp
 
         mTextSelection = new TextSelection((TextSelectionHandle) findViewById(R.id.start_handle),
                                            (TextSelectionHandle) findViewById(R.id.end_handle),
                                            GeckoAppShell.getEventDispatcher());
 
         GeckoNetworkManager.getInstance().init();
         GeckoNetworkManager.getInstance().start();
 
+        UpdateServiceHelper.registerForUpdates(this);
+
         GeckoScreenOrientationListener.getInstance().start();
 
         final GeckoApp self = this;
 
         GeckoAppShell.getHandler().postDelayed(new Runnable() {
             public void run() {
                 Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - pre checkLaunchState");
                 // Sync settings need Gecko to be loaded, so
@@ -2003,31 +2001,31 @@ abstract public class GeckoApp
         unregisterEventListener("Gecko:Ready");
         unregisterEventListener("Toast:Show");
         unregisterEventListener("DOMFullScreen:Start");
         unregisterEventListener("DOMFullScreen:Stop");
         unregisterEventListener("ToggleChrome:Hide");
         unregisterEventListener("ToggleChrome:Show");
         unregisterEventListener("ToggleChrome:Focus");
         unregisterEventListener("Permissions:Data");
-        unregisterEventListener("Update:Restart");
         unregisterEventListener("Tab:HasTouchListener");
         unregisterEventListener("Tab:ViewportMetadata");
         unregisterEventListener("Session:StatePurged");
         unregisterEventListener("Bookmark:Insert");
         unregisterEventListener("Accessibility:Event");
         unregisterEventListener("Accessibility:Ready");
         unregisterEventListener("Shortcut:Remove");
         unregisterEventListener("WebApps:Open");
         unregisterEventListener("WebApps:Install");
         unregisterEventListener("WebApps:Uninstall");
         unregisterEventListener("DesktopMode:Changed");
         unregisterEventListener("Share:Text");
         unregisterEventListener("Share:Image");
         unregisterEventListener("Sanitize:ClearHistory");
+        unregisterEventListener("Update:Check");
 
         deleteTempFiles();
 
         if (mLayerView != null)
             mLayerView.destroy();
         if (mDoorHangerPopup != null)
             mDoorHangerPopup.destroy();
         if (mFormAssistPopup != null)
@@ -2188,86 +2186,16 @@ abstract public class GeckoApp
         // 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(LOGTAG, "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(LOGTAG, "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(LOGTAG, amCmd);
-                Runtime.getRuntime().exec(amCmd);
-                statusCode = 0; // OK
-            } else {
-                Log.i(LOGTAG, "Cannot rename the update file!");
-                statusCode = 7; // WRITE_ERROR
-            }
-        } catch (Exception e) {
-            Log.i(LOGTAG, "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(LOGTAG, "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(LOGTAG, "error reading update status", e);
-        }
-        return status;
-    }
-
     private void checkMigrateProfile() {
         final File profileDir = getProfile().getDir();
         final long currentTime = SystemClock.uptimeMillis();
 
         if (profileDir != null) {
             final GeckoApp app = GeckoApp.mAppContext;
 
             GeckoAppShell.getHandler().post(new Runnable() {
@@ -2540,16 +2468,20 @@ abstract public class GeckoApp
             wl.acquire();
             mWakeLocks.put(topic, wl);
         } else if (!state.equals("locked-foreground") && wl != null) {
             wl.release();
             mWakeLocks.remove(topic);
         }
     }
 
+    public void notifyCheckUpdateResult(boolean result) {
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Update:CheckResult", result ? "true" : "false"));
+    }
+
     private void connectGeckoLayerClient() {
         mLayerView.getLayerClient().notifyGeckoReady();
 
         mLayerView.getTouchEventHandler().setOnTouchListener(new ContentTouchListener() {
             private PointF initialPoint = null;
 
             @Override
             public boolean onTouch(View view, MotionEvent event) {
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -2297,16 +2297,21 @@ public class GeckoAppShell
                 GeckoAppShell.onSurfaceTextureFrameAvailable(surfaceTexture, id);
             }
         });
     }
 
     public static void unregisterSurfaceTextureFrameListener(Object surfaceTexture) {
         ((SurfaceTexture)surfaceTexture).setOnFrameAvailableListener(null);
     }
+
+    public static void notifyCheckUpdateResult(boolean result) {
+        if (GeckoApp.mAppContext != null)
+            GeckoApp.mAppContext.notifyCheckUpdateResult(result);
+    }
 }
 
 class ScreenshotHandler implements Runnable {
     public static final int SCREENSHOT_THUMBNAIL = 0;
     public static final int SCREENSHOT_CHECKERBOARD = 1;
 
     private static final String LOGTAG = "GeckoScreenshotHandler";
     private static final int BYTES_FOR_16BPP = 2;
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/GeckoUpdateReceiver.java
@@ -0,0 +1,22 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko;
+
+import org.mozilla.gecko.updater.UpdateServiceHelper;
+
+import android.content.*;
+import android.net.*;
+
+public class GeckoUpdateReceiver
+    extends BroadcastReceiver
+{
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (UpdateServiceHelper.ACTION_CHECK_UPDATE_RESULT.equals(intent.getAction())) {
+            GeckoAppShell.notifyCheckUpdateResult(intent.getBooleanExtra("result", false));
+        }
+    }
+}
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -155,16 +155,18 @@ FENNEC_JAVA_FILES = \
   gfx/VirtualLayer.java \
   ui/Axis.java \
   ui/PanZoomController.java \
   ui/PanZoomTarget.java \
   ui/SimpleScaleGestureDetector.java \
   ui/SubdocumentScrollHelper.java \
   GeckoNetworkManager.java \
   GeckoScreenOrientationListener.java \
+  UpdateService.java \
+  GeckoUpdateReceiver.java \
   $(MOZGLUE_JAVA_FILES) \
   $(UTIL_JAVA_FILES) \
   $(NULL)
 
 ifdef MOZ_WEBSMS_BACKEND
 FENNEC_JAVA_FILES += GeckoSmsManager.java
 endif
 
@@ -179,16 +181,17 @@ FENNEC_PP_JAVA_FILES = \
   Restarter.java \
   db/BrowserContract.java \
   db/BrowserProvider.java \
   db/PasswordsProvider.java \
   db/FormHistoryProvider.java \
   db/TabsProvider.java \
   db/GeckoProvider.java \
   SmsManager.java \
+  UpdateServiceHelper.java \
   $(NULL)
 
 FENNEC_PP_XML_FILES = \
   res/layout/abouthome_content.xml \
   res/layout-xlarge-land-v11/abouthome_content.xml \
   res/layout/gecko_app.xml \
   res/layout-xlarge-v11/gecko_app.xml \
   res/layout/text_selection_handles.xml \
@@ -204,16 +207,18 @@ FENNEC_PP_XML_FILES = \
 
 ifneq (,$(findstring -march=armv7,$(OS_CFLAGS)))
 MIN_CPU_VERSION=7
 DEFINES += -DARMV7_ONLY=1
 else
 MIN_CPU_VERSION=5
 endif
 
+MOZ_APP_BUILDID=$(shell cat $(DEPTH)/config/buildid)
+
 ifeq (,$(ANDROID_VERSION_CODE))
 ifeq ($(MIN_CPU_VERSION),7)
 ANDROID_VERSION_CODE=$(shell cat $(DEPTH)/config/buildid | cut -c1-10)
 else
 # decrement the version code by 1 for armv6 builds so armv7 builds will win any compatability ties
 ANDROID_VERSION_CODE=$(shell echo $$((`cat $(DEPTH)/config/buildid | cut -c1-10` - 1)))
 endif
 endif
@@ -228,16 +233,20 @@ DEFINES += \
   -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) \
   -DUA_BUILDID=$(UA_BUILDID) \
+  -DMOZ_APP_BASENAME=$(MOZ_APP_BASENAME) \
+  -DMOZ_APP_BUILDID=$(MOZ_APP_BUILDID) \
+  -DMOZ_APP_ABI=$(TARGET_XPCOM_ABI) \
+  -DMOZ_UPDATER=$(MOZ_UPDATER) \
   $(NULL)
 
 ifdef MOZ_LINKER_EXTRACT
 DEFINES += -DMOZ_LINKER_EXTRACT=1
 endif
 
 GARBAGE += \
   AndroidManifest.xml  \
--- a/mobile/android/base/Restarter.java.in
+++ b/mobile/android/base/Restarter.java.in
@@ -38,20 +38,17 @@ public class Restarter extends Activity 
                         Thread.sleep(100);
                     } catch (InterruptedException ie) {}
                 }
             }
         } catch (Exception e) {
             Log.i(LOGTAG, e.toString());
         }
         try {
-            String action = "org.mozilla.gecko.restart_update".equals(getIntent().getAction()) ?
-                            App.ACTION_UPDATE : Intent.ACTION_MAIN;
-
-            Intent intent = new Intent(action);
+            Intent intent = new Intent(Intent.ACTION_MAIN);
             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);
             intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
             Log.i(LOGTAG, intent.toString());
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/UpdateService.java
@@ -0,0 +1,638 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.updater;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.GeckoApp;
+
+import org.mozilla.apache.commons.codec.binary.Hex;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import android.app.AlarmManager;
+import android.app.IntentService;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.IBinder;
+
+import android.util.Log;
+
+import android.widget.RemoteViews;
+
+import java.net.URL;
+import java.net.URLConnection;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import java.security.MessageDigest;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.Random;
+import java.util.TimeZone;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+
+public class UpdateService extends IntentService {
+    private static final int BUFSIZE = 8192;
+    private static final int NOTIFICATION_ID = 0x3e40ddbd;
+
+    private static final String LOGTAG = "UpdateService";
+
+    private static final int INTERVAL_LONG = 86400000; // in milliseconds
+    private static final int INTERVAL_SHORT = 14400000; // again, in milliseconds
+    private static final int INTERVAL_RETRY = 3600000;
+
+    private static final String PREFS_NAME = "UpdateService";
+    private static final String KEY_LAST_BUILDID = "UpdateService.lastBuildID";
+    private static final String KEY_LAST_HASH_FUNCTION = "UpdateService.lastHashFunction";
+    private static final String KEY_LAST_HASH_VALUE = "UpdateService.lastHashValue";
+    private static final String KEY_LAST_ATTEMPT_DATE = "UpdateService.lastAttemptDate";
+
+    private SharedPreferences mPrefs;
+
+    private NotificationManager mNotificationManager;
+    private ConnectivityManager mConnectivityManager;
+
+    private boolean mDownloading;
+    private boolean mApplyImmediately;
+
+    public UpdateService() {
+        super("updater");
+    }
+
+    @Override
+    public void onCreate () {
+        super.onCreate();
+        
+        mPrefs = getSharedPreferences(PREFS_NAME, 0);
+        mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
+        mConnectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
+    }
+
+    @Override
+    public synchronized int onStartCommand (Intent intent, int flags, int startId) {
+        // If we are busy doing a download, the new Intent here would normally be queued for
+        // execution once that is done. In this case, however, we want to flip the boolean
+        // while that is running, so handle that now.
+        if (mDownloading && UpdateServiceHelper.ACTION_APPLY_UPDATE.equals(intent.getAction())) {
+            Log.i(LOGTAG, "will apply update when download finished");
+
+            mApplyImmediately = true;
+            showDownloadNotification();
+        } else {
+            super.onStartCommand(intent, flags, startId);
+        }
+
+        return Service.START_REDELIVER_INTENT;
+    }
+
+    @Override
+    protected void onHandleIntent (Intent intent) {
+        if (UpdateServiceHelper.ACTION_REGISTER_FOR_UPDATES.equals(intent.getAction())) {
+            registerForUpdates(false);
+        } if (UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE.equals(intent.getAction())) {
+            startUpdate(intent.getIntExtra(UpdateServiceHelper.EXTRA_UPDATE_FLAGS_NAME, 0));
+        } else if (UpdateServiceHelper.ACTION_APPLY_UPDATE.equals(intent.getAction())) {
+            applyUpdate(intent.getStringExtra(UpdateServiceHelper.EXTRA_PACKAGE_PATH_NAME));
+        }
+    }
+
+    private static boolean hasFlag(int flags, int flag) {
+        return (flags & flag) == flag;
+    }
+
+    private void sendCheckUpdateResult(boolean result) {
+        Intent resultIntent = new Intent(UpdateServiceHelper.ACTION_CHECK_UPDATE_RESULT);
+        resultIntent.putExtra("result", result);
+        sendBroadcast(resultIntent);
+    }
+
+    private int getUpdateInterval(boolean isRetry) {
+        int interval;
+        if (isRetry) {
+            interval = INTERVAL_RETRY;
+        } else if (UpdateServiceHelper.UPDATE_CHANNEL.equals("nightly") ||
+                   UpdateServiceHelper.UPDATE_CHANNEL.equals("aurora")) {
+            interval = INTERVAL_SHORT;
+        } else {
+            interval = INTERVAL_LONG;
+        }
+
+        return interval;
+    }
+
+    private void registerForUpdates(boolean isRetry) {
+        Calendar lastAttempt = getLastAttemptDate();
+        Calendar now = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
+
+        int interval = getUpdateInterval(isRetry);
+
+        if (lastAttempt == null || (now.getTimeInMillis() - lastAttempt.getTimeInMillis()) > interval) {
+            // We've either never attempted an update, or we are passed the desired
+            // time. Start an update now.
+            Log.i(LOGTAG, "no update has ever been attempted, checking now");
+            startUpdate(0);
+            return;
+        }
+
+        AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+        if (manager == null)
+            return;
+
+        PendingIntent pending = PendingIntent.getService(this, 0, new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE, null, this, UpdateService.class), 0);
+        manager.cancel(pending);
+
+        lastAttempt.setTimeInMillis(lastAttempt.getTimeInMillis() + interval);
+        Log.i(LOGTAG, "next update will be at: " + lastAttempt.getTime());
+
+        manager.set(AlarmManager.RTC_WAKEUP, lastAttempt.getTimeInMillis(), pending);
+    }
+
+    private void startUpdate(int flags) {
+        setLastAttemptDate();
+
+        NetworkInfo netInfo = mConnectivityManager.getActiveNetworkInfo();
+        if (netInfo == null || !netInfo.isConnected()) {
+            Log.i(LOGTAG, "not connected to the network");
+            registerForUpdates(true);
+            sendCheckUpdateResult(false);
+            return;
+        }
+
+        registerForUpdates(false);
+
+        UpdateInfo info = findUpdate(hasFlag(flags, UpdateServiceHelper.FLAG_REINSTALL));
+        boolean haveUpdate = (info != null);
+        sendCheckUpdateResult(haveUpdate);
+
+        if (!haveUpdate) {
+            Log.i(LOGTAG, "no update available");
+            return;
+        }
+
+        Log.i(LOGTAG, "update available, buildID = " + info.buildID);
+        
+        int connectionType = netInfo.getType();
+        if (!hasFlag(flags, UpdateServiceHelper.FLAG_FORCE_DOWNLOAD) &&
+            connectionType != ConnectivityManager.TYPE_WIFI &&
+            connectionType != ConnectivityManager.TYPE_ETHERNET) {
+            Log.i(LOGTAG, "not connected via wifi or ethernet");
+
+            // We aren't autodownloading here, so prompt to start the update
+            Notification notification = new Notification(R.drawable.icon, getResources().getString(R.string.updater_start_ticker), System.currentTimeMillis());
+
+            Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE);
+            notificationIntent.setClass(this, UpdateService.class);
+            notificationIntent.putExtra(UpdateServiceHelper.EXTRA_UPDATE_FLAGS_NAME, UpdateServiceHelper.FLAG_FORCE_DOWNLOAD);
+
+            PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+            notification.flags = Notification.FLAG_AUTO_CANCEL;
+
+            notification.setLatestEventInfo(this, getResources().getString(R.string.updater_start_title),
+                                            getResources().getString(R.string.updater_start_select),
+                                            contentIntent);
+
+            mNotificationManager.notify(NOTIFICATION_ID, notification);
+
+            return;
+        }
+
+        File pkg = downloadUpdatePackage(info, hasFlag(flags, UpdateServiceHelper.FLAG_OVERWRITE_EXISTING));
+        if (pkg == null)
+            return;
+
+        Log.i(LOGTAG, "have update package at " + pkg);
+
+        saveUpdateInfo(info);
+
+        // If we have root, we always apply the update immediately because it happens in the background
+        if (mApplyImmediately || checkRoot()) {
+            applyUpdate(pkg);
+        } else {
+            // Prompt to apply the update
+            Notification notification = new Notification(R.drawable.icon, getResources().getString(R.string.updater_apply_ticker), System.currentTimeMillis());
+
+            Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_APPLY_UPDATE);
+            notificationIntent.setClass(this, UpdateService.class);
+            notificationIntent.putExtra(UpdateServiceHelper.EXTRA_PACKAGE_PATH_NAME, pkg.getAbsolutePath());
+
+            PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+            notification.flags = Notification.FLAG_AUTO_CANCEL;
+
+            notification.setLatestEventInfo(this, getResources().getString(R.string.updater_apply_title),
+                                            getResources().getString(R.string.updater_apply_select),
+                                            contentIntent);
+
+            mNotificationManager.notify(NOTIFICATION_ID, notification);
+        }
+    }
+
+    private UpdateInfo findUpdate(boolean force) {
+        try {
+            URL url = UpdateServiceHelper.getUpdateUrl(force);
+
+            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+            Document dom = builder.parse(url.openConnection().getInputStream());
+
+            NodeList nodes = dom.getElementsByTagName("update");
+            if (nodes == null || nodes.getLength() == 0)
+                return null;
+
+            Node updateNode = nodes.item(0);
+            Node buildIdNode = updateNode.getAttributes().getNamedItem("buildID");
+            if (buildIdNode == null)
+                return null;
+
+            nodes = dom.getElementsByTagName("patch");
+            if (nodes == null || nodes.getLength() == 0)
+                return null;
+
+            Node patchNode = nodes.item(0);
+            Node urlNode = patchNode.getAttributes().getNamedItem("URL");
+            Node hashFunctionNode = patchNode.getAttributes().getNamedItem("hashFunction");
+            Node hashValueNode = patchNode.getAttributes().getNamedItem("hashValue");
+            Node sizeNode = patchNode.getAttributes().getNamedItem("size");
+
+            if (urlNode == null || hashFunctionNode == null ||
+                hashValueNode == null || sizeNode == null) {
+                return null;
+            }   
+
+            // Fill in UpdateInfo from the XML data
+            UpdateInfo info = new UpdateInfo();
+            info.url = new URL(urlNode.getTextContent());
+            info.buildID = buildIdNode.getTextContent();
+            info.hashFunction = hashFunctionNode.getTextContent();
+            info.hashValue = hashValueNode.getTextContent();
+
+            try {
+                info.size = Integer.parseInt(sizeNode.getTextContent());
+            } catch (NumberFormatException e) {
+                Log.e(LOGTAG, "Failed to find APK size: ", e);
+                return null;
+            }
+
+            // Make sure we have all the stuff we need to apply the update
+            if (!info.isValid()) {
+                Log.e(LOGTAG, "missing some required update information, have: " + info);
+                return null;
+            }
+
+            return info;
+        } catch (Exception e) {
+            Log.e(LOGTAG, "failed to check for update: ", e);
+            return null;
+        }
+    }
+
+    private MessageDigest createMessageDigest(String hashFunction) {
+        String javaHashFunction = null;
+
+        if ("sha512".equals(hashFunction)) {
+            javaHashFunction = "SHA-512";
+        } else {
+            Log.e(LOGTAG, "Unhandled hash function: " + hashFunction);
+            return null;
+        }
+
+        try {
+            return MessageDigest.getInstance(javaHashFunction);
+        } catch (java.security.NoSuchAlgorithmException e) {
+            Log.e(LOGTAG, "Couldn't find algorithm " + javaHashFunction, e);
+            return null;
+        }
+    }
+
+    private void showDownloadNotification() {
+        showDownloadNotification(null);
+    }
+
+    private void showDownloadNotification(File downloadFile) {
+        Notification notification = new Notification(android.R.drawable.stat_sys_download, getResources().getString(R.string.updater_downloading_ticker), System.currentTimeMillis());
+
+        Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_APPLY_UPDATE);
+        notificationIntent.setClass(this, UpdateService.class);
+
+        if (downloadFile != null)
+            notificationIntent.putExtra(UpdateServiceHelper.EXTRA_PACKAGE_PATH_NAME, downloadFile.getAbsolutePath());
+
+        PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+        notification.setLatestEventInfo(this, getResources().getString(R.string.updater_downloading_title),
+                                        mApplyImmediately ? getResources().getString(R.string.updater_downloading_willapply) :
+                                            getResources().getString(R.string.updater_downloading_select),
+                                        contentIntent);
+        
+        mNotificationManager.notify(NOTIFICATION_ID, notification);
+    }
+
+    private void showDownloadFailure() {
+        Notification notification = new Notification(android.R.drawable.stat_sys_warning, getResources().getString(R.string.updater_downloading_ticker_failed), System.currentTimeMillis());
+
+        Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE);
+        notificationIntent.setClass(this, UpdateService.class);
+
+        PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+        notification.setLatestEventInfo(this, getResources().getString(R.string.updater_downloading_title),
+                                        getResources().getString(R.string.updater_downloading_retry),
+                                        contentIntent);
+        
+        mNotificationManager.notify(NOTIFICATION_ID, notification);
+    }
+
+    private File downloadUpdatePackage(UpdateInfo info, boolean overwriteExisting) {
+        File downloadFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), new File(info.url.getFile()).getName());
+
+        if (!overwriteExisting && info.buildID.equals(getLastBuildID()) && downloadFile.exists()) {
+            // The last saved buildID is the same as the one for the current update. We also have a file
+            // already downloaded, so it's probably the package we want. Verify it to be sure and just
+            // return that if it matches.
+
+            if (verifyDownloadedPackage(downloadFile)) {
+                Log.i(LOGTAG, "using existing update package");
+                return downloadFile;
+            } else {
+                // Didn't match, so we're going to download a new one.
+                downloadFile.delete();
+            }
+        }
+
+        Log.i(LOGTAG, "downloading update package");
+
+        OutputStream output = null;
+        InputStream input = null;
+
+        mDownloading = true;
+        showDownloadNotification(downloadFile);
+
+        try {
+            URLConnection conn = info.url.openConnection();
+            int length = conn.getContentLength();
+
+            output = new BufferedOutputStream(new FileOutputStream(downloadFile));
+            input = new BufferedInputStream(conn.getInputStream());
+
+            byte[] buf = new byte[BUFSIZE];
+            int len = 0;
+
+            int bytesRead = 0;
+            float lastPercent = 0.0f;
+
+            while ((len = input.read(buf, 0, BUFSIZE)) > 0) {
+                output.write(buf, 0, len);
+                bytesRead += len;
+            }
+
+            Log.i(LOGTAG, "completed update download!");
+
+            mNotificationManager.cancel(NOTIFICATION_ID);
+
+            return downloadFile;
+        } catch (Exception e) {
+            downloadFile.delete();
+            showDownloadFailure();
+
+            Log.e(LOGTAG, "failed to download update: ", e);
+            return null;
+        } finally {
+            try {
+                if (input != null)
+                    input.close();
+            } catch (java.io.IOException e) {}
+
+            try {
+                if (output != null)
+                    output.close();
+            } catch (java.io.IOException e) {}
+
+            mDownloading = false;
+        }
+    }
+
+    private boolean verifyDownloadedPackage(File updateFile) {
+        MessageDigest digest = createMessageDigest(getLastHashFunction());
+        if (digest == null)
+            return false;
+
+        InputStream input = null;
+
+        try {
+            input = new BufferedInputStream(new FileInputStream(updateFile));
+
+            byte[] buf = new byte[BUFSIZE];
+            int len;
+            while ((len = input.read(buf, 0, BUFSIZE)) > 0) {
+                digest.update(buf, 0, len);
+            }
+        } catch (java.io.IOException e) {
+            Log.e(LOGTAG, "Failed to verify update package: ", e);
+            return false;
+        } finally {
+            try {
+                if (input != null)
+                    input.close();
+            } catch(java.io.IOException e) {}
+        }
+
+        String hex = Hex.encodeHexString(digest.digest());
+        if (!hex.equals(getLastHashValue())) {
+            Log.e(LOGTAG, "Package hash does not match");
+            return false;
+        }
+
+        return true;
+    }
+
+    private void applyUpdate(String updatePath) {
+        applyUpdate(new File(updatePath));
+    }
+
+    private void applyUpdate(File updateFile) {
+        mApplyImmediately = false;
+
+        if (!updateFile.exists())
+            return;
+
+        Log.i(LOGTAG, "Verifying package: " + updateFile);
+
+        if (!verifyDownloadedPackage(updateFile)) {
+            Log.e(LOGTAG, "Not installing update, failed verification");
+            return;
+        }
+
+        if (checkRoot())
+            applyUpdateWithRoot(updateFile);
+        else
+            applyUpdateWithActivity(updateFile);
+    }
+
+    private void applyUpdateWithRoot(File updateFile) {
+        mNotificationManager.cancel(NOTIFICATION_ID);
+
+        Notification notification = new Notification(R.drawable.icon, getResources().getString(R.string.updater_installing_ticker), System.currentTimeMillis());
+        notification.flags = Notification.FLAG_NO_CLEAR;
+
+        Intent notificationIntent = new Intent("org.mozilla.gecko.ACTION_NOOP");
+        notificationIntent.setClass(this, UpdateService.class);
+
+        PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, 0);
+        notification.flags = Notification.FLAG_NO_CLEAR;
+
+        notification.setLatestEventInfo(this, getResources().getString(R.string.updater_installing_title),
+                                        getResources().getString(R.string.updater_installing_text),
+                                        contentIntent);
+
+        mNotificationManager.notify(NOTIFICATION_ID, notification);
+
+        int result = runAsRoot("pm install " + updateFile.getAbsolutePath());
+        Log.i(LOGTAG, "install result = " + result);
+
+        updateFile.delete();
+
+        int tickerText = result == 0 ? R.string.updater_installing_ticker_success : R.string.updater_installing_ticker_fail;
+        int contentText = result == 0 ? R.string.updater_installing_text_success : R.string.updater_installing_text_fail;
+
+        mNotificationManager.cancel(NOTIFICATION_ID);
+
+        notification = new Notification(R.drawable.icon, getResources().getString(tickerText), System.currentTimeMillis());
+        notification.flags = Notification.FLAG_NO_CLEAR;
+
+        notificationIntent = new Intent(Intent.ACTION_MAIN);
+        notificationIntent.setClassName(getPackageName(), getPackageName() + ".App");
+        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
+        notification.flags = Notification.FLAG_NO_CLEAR;
+
+        notification.setLatestEventInfo(this, getResources().getString(R.string.updater_installing_title),
+                                        getResources().getString(result == 0 ? R.string.updater_installing_text_success : R.string.updater_installing_text_fail),
+                                        contentIntent);
+
+        mNotificationManager.notify(NOTIFICATION_ID, notification);
+
+
+        notification.setLatestEventInfo(this, getResources().getString(R.string.updater_installing_title),
+                                        getResources().getString(result == 0 ? R.string.updater_installing_text_success : R.string.updater_installing_text_fail),
+                                        contentIntent);
+        notification.flags = Notification.FLAG_AUTO_CANCEL;
+        mNotificationManager.notify(NOTIFICATION_ID, notification);
+    }
+
+    private void applyUpdateWithActivity(File updateFile) {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.setDataAndType(Uri.fromFile(updateFile), "application/vnd.android.package-archive");
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        startActivity(intent);
+    }
+
+    private String getLastBuildID() {
+        return mPrefs.getString(KEY_LAST_BUILDID, null);
+    }
+
+    private String getLastHashFunction() {
+        return mPrefs.getString(KEY_LAST_HASH_FUNCTION, null);
+    }
+
+    private String getLastHashValue() {
+        return mPrefs.getString(KEY_LAST_HASH_VALUE, null);
+    }
+
+    private Calendar getLastAttemptDate() {
+        long lastAttempt = mPrefs.getLong(KEY_LAST_ATTEMPT_DATE, -1);
+        if (lastAttempt < 0)
+            return null;
+
+        GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
+        cal.setTimeInMillis(lastAttempt);
+        return cal;
+    }
+
+    private void setLastAttemptDate() {
+        SharedPreferences.Editor editor = mPrefs.edit();
+        editor.putLong(KEY_LAST_ATTEMPT_DATE, System.currentTimeMillis());
+        editor.commit();
+    }
+
+    private void saveUpdateInfo(UpdateInfo info) {
+        SharedPreferences.Editor editor = mPrefs.edit();
+        editor.putString(KEY_LAST_BUILDID, info.buildID);
+        editor.putString(KEY_LAST_HASH_FUNCTION, info.hashFunction);
+        editor.putString(KEY_LAST_HASH_VALUE, info.hashValue);
+        editor.commit();
+    }
+
+    private int runAsRoot(String command) {
+        Process p = null;
+        try {
+            p = Runtime.getRuntime().exec("su");
+            OutputStream output = p.getOutputStream();
+            output.write(command.getBytes());
+            output.write(new String("; exit\n").getBytes());
+            output.flush();
+            p.waitFor();
+
+            return p.exitValue();
+        } catch (Exception e) {
+            return -1;
+        } finally {
+            if (p != null)
+                p.destroy();
+        }
+    }
+
+    private boolean checkRoot() {
+        return runAsRoot("echo woooooo") == 0;
+    }
+
+    private class UpdateInfo {
+        public URL url;
+        public String buildID;
+        public String hashFunction;
+        public String hashValue;
+        public int size;
+
+        private boolean isNonEmpty(String s) {
+            return s != null && s.length() > 0;
+        }
+
+        public boolean isValid() {
+            return url != null && isNonEmpty(buildID) &&
+                isNonEmpty(hashFunction) && isNonEmpty(hashValue) && size > 0;
+        }
+
+        @Override
+        public String toString() {
+            return "url = " + url + ", buildID = " + buildID + ", hashFunction = " + hashFunction + ", hashValue = " + hashValue + ", size = " + size;
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/UpdateServiceHelper.java.in
@@ -0,0 +1,79 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#filter substitution
+
+package org.mozilla.gecko.updater;
+
+import android.content.Context;
+import android.content.Intent;
+
+import android.os.Build;
+
+import android.util.Log;
+
+import java.net.URL;
+
+import java.util.Locale;
+
+public class UpdateServiceHelper {
+    public static final String ACTION_REGISTER_FOR_UPDATES = "@ANDROID_PACKAGE_NAME@.REGISTER_FOR_UPDATES";
+    public static final String ACTION_UNREGISTER_FOR_UPDATES = "@ANDROID_PACKAGE_NAME@.UNREGISTER_FOR_UPDATES";
+    public static final String ACTION_CHECK_FOR_UPDATE = "@ANDROID_PACKAGE_NAME@.CHECK_FOR_UPDATE";
+    public static final String ACTION_CHECK_UPDATE_RESULT = "@ANDROID_PACKAGE_NAME@.CHECK_UPDATE_RESULT";
+    public static final String ACTION_APPLY_UPDATE = "@ANDROID_PACKAGE_NAME@.APPLY_UPDATE";
+
+    // Flags for ACTION_CHECK_FOR_UPDATE
+    public static final int FLAG_FORCE_DOWNLOAD = 1;
+    public static final int FLAG_OVERWRITE_EXISTING = 1 << 1;
+    public static final int FLAG_REINSTALL = 1 << 2;
+    public static final int FLAG_RETRY = 1 << 3;
+
+    // Name of the Intent extra that holds the flags for ACTION_CHECK_FOR_UPDATE
+    public static final String EXTRA_UPDATE_FLAGS_NAME = "updateFlags";
+
+    // Name of the Intent extra that holds the APK path, used with ACTION_APPLY_UPDATE
+    public static final String EXTRA_PACKAGE_PATH_NAME = "packagePath";
+
+    public static final String UPDATE_CHANNEL = "@MOZ_UPDATE_CHANNEL@";
+
+    private static final String LOGTAG = "UpdateServiceHelper";
+    private static final String BUILDID = "@MOZ_APP_BUILDID@";
+
+#ifdef MOZ_PKG_SPECIAL
+    private static final String UPDATE_URL = "https://aus2.mozilla.org/update/4/@MOZ_APP_BASENAME@/@MOZ_APP_VERSION@/%BUILDID%/Android_@MOZ_APP_ABI@-@MOZ_PKG_SPECIAL@/%LOCALE%/@MOZ_UPDATE_CHANNEL@/%OS_VERSION%/default/default/@MOZ_APP_VERSION@/update.xml";
+#else
+    private static final String UPDATE_URL = "https://aus2.mozilla.org/update/4/@MOZ_APP_BASENAME@/@MOZ_APP_VERSION@/%BUILDID%/Android_@MOZ_APP_ABI@/%LOCALE%/@MOZ_UPDATE_CHANNEL@/%OS_VERSION%/default/default/@MOZ_APP_VERSION@/update.xml";  
+#endif
+    
+    public static URL getUpdateUrl(boolean force) {
+        Locale locale = Locale.getDefault();
+        String url = UPDATE_URL.replace("%LOCALE%", locale.getLanguage() + "-" + locale.getCountry()).
+            replace("%OS_VERSION%", Build.VERSION.RELEASE).
+            replace("%BUILDID%", force ? "0" : BUILDID);
+
+        try {
+            return new URL(url);
+        } catch (java.net.MalformedURLException e) {
+            Log.e(LOGTAG, "Failed to create update url: ", e);
+            return null;
+        }
+    }
+
+    public static boolean isUpdaterEnabled() {
+#ifdef MOZ_UPDATER
+        return true;
+#else
+        return false;
+#endif
+    }
+
+    public static void registerForUpdates(Context context) {
+        if (!isUpdaterEnabled())
+            return;
+
+        context.startService(new Intent(UpdateServiceHelper.ACTION_REGISTER_FOR_UPDATES, null, context, UpdateService.class));
+    }
+}
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -219,8 +219,32 @@ just addresses the organization to follo
                                       from Android">
 <!ENTITY bookmarkhistory_import_bookmarks "Importing bookmarks
                                            from Android">
 <!ENTITY bookmarkhistory_import_history "Importing history
                                          from Android">
 <!ENTITY bookmarkhistory_import_wait "Please wait...">
 
 <!ENTITY webapp_generic_name "App">
+
+<!-- Updater notifications -->
+<!ENTITY updater_start_title "&brandShortName;">
+<!ENTITY updater_start_ticker "&brandShortName; update available&#8230;">
+<!ENTITY updater_start_select "Select to download update.">
+
+<!ENTITY updater_downloading_title "Downloading &brandShortName;">
+<!ENTITY updater_downloading_ticker "Downloading &brandShortName; update&#8230;">
+<!ENTITY updater_downloading_ticker_failed "Failed to download &brandShortName; update&#8230;">
+<!ENTITY updater_downloading_select "Select to apply update when complete.">
+<!ENTITY updater_downloading_retry "Select to retry update download.">
+<!ENTITY updater_downloading_willapply "Waiting for download to complete.">
+
+<!ENTITY updater_apply_title "&brandShortName;">
+<!ENTITY updater_apply_ticker "&brandShortName; update available&#8230;">
+<!ENTITY updater_apply_select "Select to apply downloaded update.">
+
+<!ENTITY updater_installing_title "&brandShortName;">
+<!ENTITY updater_installing_ticker "Updating &brandShortName;&#8230;">
+<!ENTITY updater_installing_ticker_success "Successfully updated &brandShortName;">
+<!ENTITY updater_installing_ticker_fail "Failed to update &brandShortName;">
+<!ENTITY updater_installing_text "Installing update&#8230;">
+<!ENTITY updater_installing_text_success "Succesfully updated.">
+<!ENTITY updater_installing_text_fail "Failed to install update.">
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -205,9 +205,34 @@
   <!-- Bookmark import/export -->
   <string name="bookmarkhistory_button_import">&bookmarkhistory_button_import;</string>
   <string name="bookmarkhistory_import_both">&bookmarkhistory_import_both;</string>
   <string name="bookmarkhistory_import_bookmarks">&bookmarkhistory_import_bookmarks;</string>
   <string name="bookmarkhistory_import_history">&bookmarkhistory_import_history;</string>
   <string name="bookmarkhistory_import_wait">&bookmarkhistory_import_wait;</string>
 
   <string name="webapp_generic_name">&webapp_generic_name;</string>
+
+  <!-- Updater notifications -->
+  <string name="updater_start_title">&updater_start_title;</string>
+  <string name="updater_start_ticker">&updater_start_ticker;</string>
+  <string name="updater_start_select">&updater_start_select;</string>
+
+  <string name="updater_downloading_title">&updater_downloading_title;</string>
+  <string name="updater_downloading_ticker">&updater_downloading_ticker;</string>
+  <string name="updater_downloading_ticker_failed">&updater_downloading_ticker_failed;</string>
+  <string name="updater_downloading_select">&updater_downloading_select;</string>
+  <string name="updater_downloading_retry">&updater_downloading_retry;</string>
+  <string name="updater_downloading_willapply">&updater_downloading_willapply;</string>
+  
+  <string name="updater_apply_title">&updater_apply_title;</string>
+  <string name="updater_apply_ticker">&updater_apply_ticker;</string>
+  <string name="updater_apply_select">&updater_apply_select;</string>
+
+  <string name="updater_installing_title">&updater_installing_title;</string>
+  <string name="updater_installing_ticker">&updater_installing_ticker;</string>
+  <string name="updater_installing_ticker_success">&updater_installing_ticker_success;</string>
+  <string name="updater_installing_ticker_fail">&updater_installing_ticker_fail;</string>
+  <string name="updater_installing_text">&updater_installing_text;</string>
+  <string name="updater_installing_text_success">&updater_installing_text_success;</string>
+  <string name="updater_installing_text_fail">&updater_installing_text_fail;</string>
+
 </resources>
--- a/mobile/android/chrome/content/about.xhtml
+++ b/mobile/android/chrome/content/about.xhtml
@@ -96,90 +96,39 @@
         });
       } catch (ex) {}
 
 #ifdef MOZ_UPDATER
       let Updater = {
         isChecking: false,
         update: null,
 
-        get updateEnabled() {
-          try {
-            return Services.prefs.getBoolPref("app.update.enabled");
-          }
-          catch (e) { }
-          return true; // Mobile default is true
-        },
-
-        startUpdate: function() {
-          if (!this.update)
-            this.update = this.um.activeUpdate;
-
-          this.aus.pauseDownload();
-
-          let updateTimerCallback = this.aus.QueryInterface(Ci.nsITimerCallback);
-          updateTimerCallback.notify(null);
-        }
-      };
-
-      XPCOMUtils.defineLazyServiceGetter(Updater, "aus",
-                                         "@mozilla.org/updates/update-service;1",
-                                         "nsIApplicationUpdateService");
-      XPCOMUtils.defineLazyServiceGetter(Updater, "checker",
-                                         "@mozilla.org/updates/update-checker;1",
-                                         "nsIUpdateChecker");
-      XPCOMUtils.defineLazyServiceGetter(Updater, "um",
-                                         "@mozilla.org/updates/update-manager;1",
-                                         "nsIUpdateManager");
-
-      let UpdateCheckListener = {
-        onProgress: function(aRequest, aPosition, aTotalSize) {
+        init: function() {
+          Services.obs.addObserver(this, "Update:CheckResult", false);
         },
 
-        onCheckComplete: function(aRequest, aUpdates, aUpdateCount) {
-          Updater.isChecking = false;
-
-          Updater.update = Updater.aus.selectUpdate(aUpdates, aUpdates.length);
-          if (!Updater.update || !Updater.aus.canApplyUpdates) {
-            showUpdateMessage(false);
-            return;
-          }
-
-          if (!Updater.update.appVersion || Services.vc.compare(Updater.update.appVersion, Services.appinfo.version) < 0) {
-            showUpdateMessage(false);
-            return;
+        observe: function(aSubject, aTopic, aData) {
+          if (aTopic == "Update:CheckResult") {
+            showUpdateMessage(aData == "true");
           }
-
-          showUpdateMessage(true);
-          Updater.startUpdate();
         },
-
-        onError: function(aRequest, aUpdate) {
-          // Errors in the update check are treated as no updates found. If the
-          // update check fails repeatedly without a success the user will be
-          // notified with the normal app update user interface so this is safe.
-          Updater.isChecking = false;
-          showUpdateMessage(false);
-        },
-
-        QueryInterface: function(aIID) {
-          if (!aIID.equals(Ci.nsIUpdateCheckListener) && !aIID.equals(Ci.nsISupports))
-            throw Cr.NS_ERROR_NO_INTERFACE;
-          return this;
-        }
       };
 
-      if (!Updater.updateEnabled)
-        document.getElementById("updateBox").style.display = "none";
+      Updater.init();
 
       function checkForUpdates() {
         Updater.isChecking = true;
         showCheckingMessage();
 
-        Updater.checker.checkForUpdates(UpdateCheckListener, true);
+        let bridge = Cc["@mozilla.org/android/bridge;1"].getService(Ci.nsIAndroidBridge);
+        bridge.handleGeckoMessage(JSON.stringify({
+          gecko: {
+            type: "Update:Check",
+          }
+        }));
       }
 
       let updateLink = document.getElementById("updateLink");
       let checkingSpan = document.getElementById("update-message-checking");
       let noneSpan = document.getElementById("update-message-none");
       let foundSpan = document.getElementById("update-message-found");
 
       function showCheckingMessage() {
--- a/mobile/android/components/Makefile.in
+++ b/mobile/android/components/Makefile.in
@@ -38,13 +38,9 @@ EXTRA_COMPONENTS = \
         LoginManagerPrompter.js \
         BlocklistPrompt.js \
         $(NULL)
 
 ifdef MOZ_SAFE_BROWSING
 EXTRA_COMPONENTS += SafeBrowsing.js
 endif
 
-ifdef MOZ_UPDATER
-EXTRA_COMPONENTS += UpdatePrompt.js
-endif
-
 include $(topsrcdir)/config/rules.mk
--- a/mobile/android/components/MobileComponents.manifest
+++ b/mobile/android/components/MobileComponents.manifest
@@ -91,14 +91,8 @@ contract @mozilla.org/addons/blocklist-p
 
 #ifdef MOZ_SAFE_BROWSING
 # SafeBrowsing.js
 component {aadaed90-6c03-42d0-924a-fc61198ff283} SafeBrowsing.js
 contract @mozilla.org/safebrowsing/application;1 {aadaed90-6c03-42d0-924a-fc61198ff283}
 category app-startup SafeBrowsing service,@mozilla.org/safebrowsing/application;1
 #endif
 
-#ifdef MOZ_UPDATER
-# UpdatePrompt.js
-component {88b3eb21-d072-4e3b-886d-f89d8c49fe59} UpdatePrompt.js
-contract @mozilla.org/updates/update-prompt;1 {88b3eb21-d072-4e3b-886d-f89d8c49fe59}
-#endif
-
deleted file mode 100644
--- a/mobile/android/components/UpdatePrompt.js
+++ /dev/null
@@ -1,316 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-const UPDATE_NOTIFICATION_NAME = "update-app";
-const UPDATE_NOTIFICATION_ICON = "drawable://alert_download";
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-
-XPCOMUtils.defineLazyGetter(this, "gUpdateBundle", function aus_gUpdateBundle() {
-  return Services.strings.createBundle("chrome://mozapps/locale/update/updates.properties");
-});
-
-XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function aus_gBrandBundle() {
-  return Services.strings.createBundle("chrome://branding/locale/brand.properties");
-});
-
-XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function aus_gBrowserBundle() {
-  return Services.strings.createBundle("chrome://browser/locale/browser.properties");
-});
-
-XPCOMUtils.defineLazyGetter(this, "AddonManager", function() {
-  Cu.import("resource://gre/modules/AddonManager.jsm");
-  return AddonManager;
-});
-
-XPCOMUtils.defineLazyGetter(this, "LocaleRepository", function() {
-  Cu.import("resource://gre/modules/LocaleRepository.jsm");
-  return LocaleRepository;
-});
-
-function getPref(func, preference, defaultValue) {
-  try {
-    return Services.prefs[func](preference);
-  } catch (e) {}
-  return defaultValue;
-}
-
-function sendMessageToJava(aMsg) {
-  let data = Cc["@mozilla.org/android/bridge;1"].getService(Ci.nsIAndroidBridge).handleGeckoMessage(JSON.stringify(aMsg));
-  return JSON.parse(data);
-}
-
-// -----------------------------------------------------------------------
-// Update Prompt
-// -----------------------------------------------------------------------
-
-function UpdatePrompt() { }
-
-UpdatePrompt.prototype = {
-  classID: Components.ID("{88b3eb21-d072-4e3b-886d-f89d8c49fe59}"),
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePrompt, Ci.nsIRequestObserver, Ci.nsIProgressEventSink]),
-
-  get _enabled() {
-    return !getPref("getBoolPref", "app.update.silent", false);
-  },
-
-  _showNotification: function UP__showNotif(aUpdate, aTitle, aText, aImageUrl, aMode) {
-    let observer = {
-      updatePrompt: this,
-      observe: function (aSubject, aTopic, aData) {
-        switch (aTopic) {
-          case "alertclickcallback":
-            this.updatePrompt._handleUpdate(aUpdate, aMode);
-            break;
-        }
-      }
-    };
-
-    let notifier = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
-    notifier.showAlertNotification(aImageUrl, aTitle, aText, true, "", observer, UPDATE_NOTIFICATION_NAME);
-  },
-
-  _handleUpdate: function UP__handleUpdate(aUpdate, aMode) {
-    if (aMode == "available") {
-      let window = Services.wm.getMostRecentWindow("navigator:browser");
-      let title = gUpdateBundle.GetStringFromName("updatesfound_" + aUpdate.type + ".title");
-      let brandName = gBrandBundle.GetStringFromName("brandShortName");
-
-      // Unconditionally use the "major" type here as for now it is always a new version
-      // without additional description required for a minor update message
-      let message = gUpdateBundle.formatStringFromName("intro_major", [brandName, aUpdate.displayVersion], 2);
-      let button0 = gUpdateBundle.GetStringFromName("okButton");
-      let button1 = gUpdateBundle.GetStringFromName("askLaterButton");
-      let prompt = Services.prompt;
-      let flags = prompt.BUTTON_POS_0 * prompt.BUTTON_TITLE_IS_STRING + prompt.BUTTON_POS_1 * prompt.BUTTON_TITLE_IS_STRING;
-
-      let download = (prompt.confirmEx(window, title, message, flags, button0, button1, null, null, {value: false}) == 0);
-      if (download) {
-        // Start downloading the update in the background
-        let aus = Cc["@mozilla.org/updates/update-service;1"].getService(Ci.nsIApplicationUpdateService);
-        if (aus.downloadUpdate(aUpdate, true) != "failed") {
-          let title = gBrowserBundle.formatStringFromName("alertDownloadsStart", [aUpdate.name], 1);
-          this._showNotification(aUpdate, title, "", UPDATE_NOTIFICATION_ICON, "download");
-
-          // Add this UI as a listener for active downloads
-          aus.addDownloadListener(this);
-        }
-      }
-    } else if(aMode == "downloaded") {
-      // Notify all windows that an application quit has been requested
-      let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
-      Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
-
-      // If nothing aborted, restart the app
-      if (cancelQuit.data == false) {
-        sendMessageToJava({
-          gecko: {
-            type: "Update:Restart"
-          }
-        });
-      }
-    }
-  },
-
-  _updateDownloadProgress: function UP__updateDownloadProgress(aProgress, aTotal) {
-    let alertsService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
-    let progressListener = alertsService.QueryInterface(Ci.nsIAlertsProgressListener);
-    if (progressListener)
-      progressListener.onProgress(UPDATE_NOTIFICATION_NAME, aProgress, aTotal);
-  },
-
-  // -------------------------
-  // nsIUpdatePrompt interface
-  // -------------------------
-
-  // Right now this is used only to check for updates in progress
-  checkForUpdates: function UP_checkForUpdates() {
-    if (!this._enabled)
-      return;
-
-    let aus = Cc["@mozilla.org/updates/update-service;1"].getService(Ci.nsIApplicationUpdateService);
-    if (!aus.isDownloading)
-      return;
-
-    let updateManager = Cc["@mozilla.org/updates/update-manager;1"].getService(Ci.nsIUpdateManager);
-
-    let updateName = updateManager.activeUpdate ? updateManager.activeUpdate.name : gBrandBundle.GetStringFromName("brandShortName");
-    let title = gBrowserBundle.formatStringFromName("alertDownloadsStart", [updateName], 1);
-
-    this._showNotification(updateManager.activeUpdate, title, "", UPDATE_NOTIFICATION_ICON, "downloading");
-
-    aus.removeDownloadListener(this); // just in case it's already added
-    aus.addDownloadListener(this);
-  },
-
-  showUpdateAvailable: function UP_showUpdateAvailable(aUpdate) {
-    if (!this._enabled)
-      return;
-
-    const PREF_APP_UPDATE_SKIPNOTIFICATION = "app.update.skipNotification";
-
-    if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_SKIPNOTIFICATION) &&
-        Services.prefs.getBoolPref(PREF_APP_UPDATE_SKIPNOTIFICATION)) {
-      Services.prefs.setBoolPref(PREF_APP_UPDATE_SKIPNOTIFICATION, false);
-
-      // Notification was already displayed and clicked, so jump to the next step:
-      // ask the user about downloading update
-      this._handleUpdate(aUpdate, "available");
-      return;
-    }
-
-    let stringsPrefix = "updateAvailable_" + aUpdate.type + ".";
-    let title = gUpdateBundle.formatStringFromName(stringsPrefix + "title", [aUpdate.name], 1);
-    let text = gUpdateBundle.GetStringFromName(stringsPrefix + "text");
-    let imageUrl = "";
-    this._showNotification(aUpdate, title, text, imageUrl, "available");
-  },
-
-  showUpdateDownloaded: function UP_showUpdateDownloaded(aUpdate, aBackground) {
-    if (!this._enabled)
-      return;
-
-    // uninstall all installed locales
-    AddonManager.getAddonsByTypes(["locale"], (function (aAddons) {
-      if (aAddons.length > 0) {
-        let listener = this.getAddonListener(aUpdate, this);
-        AddonManager.addAddonListener(listener);  
-        aAddons.forEach(function(aAddon) {
-          listener._uninstalling.push(aAddon.id);
-          aAddon.uninstall();
-        }, this);
-      } else {
-        this._showDownloadedNotification(aUpdate);
-      }
-    }).bind(this));
-  },
-
-  _showDownloadedNotification: function UP_showDlNotification(aUpdate) {
-    let stringsPrefix = "updateDownloaded_" + aUpdate.type + ".";
-    let title = gUpdateBundle.formatStringFromName(stringsPrefix + "title", [aUpdate.name], 1);
-    let text = gUpdateBundle.GetStringFromName(stringsPrefix + "text");
-    let imageUrl = "";
-    this._showNotification(aUpdate, title, text, imageUrl, "downloaded");
-  },
-
-  _uninstalling: [],
-  _installing: [],
-  _currentUpdate: null,
-
-  _reinstallLocales: function UP_reinstallLocales(aUpdate, aListener, aPending) {
-    LocaleRepository.getLocales((function(aLocales) {
-      aLocales.forEach(function(aLocale, aIndex, aArray) {
-        let index = aPending.indexOf(aLocale.addon.id);
-        if (index > -1) {
-          aListener._installing.push(aLocale.addon.id);
-          aLocale.addon.install.install();
-        }
-      }, this);
-      // store the buildid of these locales so that we can disable locales when the
-      // user updates through a non-updater channel
-      Services.prefs.setCharPref("extensions.compatability.locales.buildid", aUpdate.buildID);
-    }).bind(this), { buildID: aUpdate.buildID });
-  },
-
-  showUpdateInstalled: function UP_showUpdateInstalled() {
-    if (!this._enabled || !getPref("getBoolPref", "app.update.showInstalledUI", false))
-      return;
-
-    let title = gBrandBundle.GetStringFromName("brandShortName");
-    let text = gUpdateBundle.GetStringFromName("installSuccess");
-    let imageUrl = "";
-    this._showNotification(aUpdate, title, text, imageUrl, "installed");
-  },
-
-  showUpdateError: function UP_showUpdateError(aUpdate) {
-    if (!this._enabled)
-      return;
-
-    if (aUpdate.state == "failed") {
-      var title = gBrandBundle.GetStringFromName("brandShortName");
-      let text = gUpdateBundle.GetStringFromName("updaterIOErrorTitle");
-      let imageUrl = "";
-      this._showNotification(aUpdate, title, text, imageUrl, "error");
-    }
-  },
-
-  showUpdateHistory: function UP_showUpdateHistory(aParent) {
-    // NOT IMPL
-  },
-  
-  // ----------------------------
-  // nsIRequestObserver interface
-  // ----------------------------
-  
-  // When the data transfer begins
-  onStartRequest: function(request, context) {
-    // NOT IMPL
-  },
-
-  // When the data transfer ends
-  onStopRequest: function(request, context, status) {
-    let alertsService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
-    let progressListener = alertsService.QueryInterface(Ci.nsIAlertsProgressListener);
-    if (progressListener)
-      progressListener.onCancel(UPDATE_NOTIFICATION_NAME);
-
-
-    let aus = Cc["@mozilla.org/updates/update-service;1"].getService(Ci.nsIApplicationUpdateService);
-    aus.removeDownloadListener(this);
-  },
-
-  // ------------------------------
-  // nsIProgressEventSink interface
-  // ------------------------------
-  
-  // When new data has been downloaded
-  onProgress: function(request, context, progress, maxProgress) {
-    this._updateDownloadProgress(progress, maxProgress);
-  },
-
-  // When we have new status text
-  onStatus: function(request, context, status, statusText) {
-    // NOT IMPL
-  },
-
-  // -------------------------------
-  // AddonListener
-  // -------------------------------
-  getAddonListener: function(aUpdate, aUpdatePrompt) {
-    return {
-      _installing: [],
-      _uninstalling: [],
-      onInstalling: function(aAddon, aNeedsRestart) {
-        let index = this._installing.indexOf(aAddon.id);
-        if (index > -1)
-          this._installing.splice(index, 1);
-    
-        if (this._installing.length == 0) {
-          aUpdatePrompt._showDownloadedNotification(aUpdate);
-          AddonManager.removeAddonListener(this);
-        }
-      },
-    
-      onUninstalling: function(aAddon, aNeedsRestart) {
-        let pending = [];
-        let index = this._uninstalling.indexOf(aAddon.id);
-        if (index > -1) {
-          pending.push(aAddon.id);
-          this._uninstalling.splice(index, 1);
-        }
-        if (this._uninstalling.length == 0)
-          aUpdatePrompt._reinstallLocales(aUpdate, this, pending);
-      }
-    }
-  }
-
-};
-
-const NSGetFactory = XPCOMUtils.generateNSGetFactory([UpdatePrompt]);
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -511,14 +511,11 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DL
 @BINPATH@/components/MobileComponents.manifest
 @BINPATH@/components/MobileComponents.xpt
 @BINPATH@/components/PromptService.js
 @BINPATH@/components/SessionStore.js
 @BINPATH@/components/Sidebar.js
 #ifdef MOZ_SAFE_BROWSING
 @BINPATH@/components/SafeBrowsing.js
 #endif
-#ifdef MOZ_UPDATER
-@BINPATH@/components/UpdatePrompt.js
-#endif
 @BINPATH@/components/XPIDialogService.js
 @BINPATH@/components/browsercomps.xpt
 @BINPATH@/extensions/feedback@mobile.mozilla.org.xpi