Bug 786380 - Implement new Java-based updater for Android r=cpeterson,mfinkle
☠☠ backed out by c8cacc4cd63e ☠ ☠
authorJames Willcox <jwillcox@mozilla.com>
Fri, 31 Aug 2012 09:31:29 -0400
changeset 104001 e1c78a682928ff80de1547009975dcf850a8bda3
parent 104000 5071c1a3c394a73175f645f9aef9a057dd1f31c5
child 104002 2cfd76ed711c93e5221ace62d7e279b77ac8836a
push id23392
push userryanvm@gmail.com
push dateSat, 01 Sep 2012 01:35:58 +0000
treeherderautoland@a21fd4d085ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpeterson, mfinkle
bugs786380
milestone18.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 786380 - Implement new Java-based updater for Android r=cpeterson,mfinkle
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
@@ -143,16 +143,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"
@@ -219,16 +225,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=":updater">
+        </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";
 
@@ -905,18 +906,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;
@@ -995,16 +994,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);
@@ -1482,21 +1483,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");
@@ -1581,31 +1577,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);
 
@@ -1616,16 +1612,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
@@ -2024,31 +2022,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)
@@ -2209,86 +2207,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() {
@@ -2561,16 +2489,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
@@ -2292,16 +2292,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
@@ -154,16 +154,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
 
@@ -178,16 +180,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 \
@@ -202,16 +205,18 @@ FENNEC_PP_XML_FILES = \
 
 
 ifneq (,$(findstring -march=armv7,$(OS_CFLAGS)))
 MIN_CPU_VERSION=7
 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
@@ -226,16 +231,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