diff --git a/mobile/android/app/mobile.js b/mobile/android/app/mobile.js
index 8550a42505e..9ddcb983863 100644
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -490,31 +490,11 @@ 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
diff --git a/mobile/android/base/AndroidManifest.xml.in b/mobile/android/base/AndroidManifest.xml.in
index dd2e0a843b8..7b756d9e171 100644
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -148,6 +148,12 @@
+
+
+
+
+
+
@@ -224,6 +230,13 @@
android:authorities="@ANDROID_PACKAGE_NAME@.db.tabs"
android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
+
+
+
+
#include ../sync/manifests/SyncAndroidManifest_services.xml.in
diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java
index ad10c243ce5..174527e4476 100644
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -16,6 +16,8 @@ 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;
@@ -127,7 +129,6 @@ abstract public class GeckoApp
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";
@@ -910,8 +911,6 @@ abstract public class GeckoApp
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);
@@ -1000,6 +999,8 @@ abstract public class GeckoApp
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);
@@ -1487,11 +1488,6 @@ abstract public class GeckoApp
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) {
@@ -1586,7 +1582,6 @@ abstract public class GeckoApp
registerEventListener("ToggleChrome:Show");
registerEventListener("ToggleChrome:Focus");
registerEventListener("Permissions:Data");
- registerEventListener("Update:Restart");
registerEventListener("Tab:HasTouchListener");
registerEventListener("Tab:ViewportMetadata");
registerEventListener("Session:StatePurged");
@@ -1601,6 +1596,7 @@ abstract public class GeckoApp
registerEventListener("Share:Text");
registerEventListener("Share:Image");
registerEventListener("Sanitize:ClearHistory");
+ registerEventListener("Update:Check");
if (SmsManager.getInstance() != null) {
SmsManager.getInstance().start();
@@ -1621,6 +1617,8 @@ abstract public class GeckoApp
GeckoNetworkManager.getInstance().init();
GeckoNetworkManager.getInstance().start();
+ UpdateServiceHelper.registerForUpdates(this);
+
GeckoScreenOrientationListener.getInstance().start();
final GeckoApp self = this;
@@ -2029,7 +2027,6 @@ abstract public class GeckoApp
unregisterEventListener("ToggleChrome:Show");
unregisterEventListener("ToggleChrome:Focus");
unregisterEventListener("Permissions:Data");
- unregisterEventListener("Update:Restart");
unregisterEventListener("Tab:HasTouchListener");
unregisterEventListener("Tab:ViewportMetadata");
unregisterEventListener("Session:StatePurged");
@@ -2044,6 +2041,7 @@ abstract public class GeckoApp
unregisterEventListener("Share:Text");
unregisterEventListener("Share:Image");
unregisterEventListener("Sanitize:ClearHistory");
+ unregisterEventListener("Update:Check");
deleteTempFiles();
@@ -2214,76 +2212,6 @@ abstract public class GeckoApp
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();
@@ -2566,6 +2494,10 @@ abstract public class GeckoApp
}
}
+ public void notifyCheckUpdateResult(boolean result) {
+ GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Update:CheckResult", result ? "true" : "false"));
+ }
+
private void connectGeckoLayerClient() {
mLayerView.getLayerClient().notifyGeckoReady();
diff --git a/mobile/android/base/GeckoAppShell.java b/mobile/android/base/GeckoAppShell.java
index d2da8ef30e0..1c55e7b9e4e 100644
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -2297,6 +2297,11 @@ public class GeckoAppShell
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 {
diff --git a/mobile/android/base/GeckoUpdateReceiver.java b/mobile/android/base/GeckoUpdateReceiver.java
new file mode 100644
index 00000000000..a232ebb8b0f
--- /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));
+ }
+ }
+}
diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in
index 6fd8ca39d86..3402492ba75 100644
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -159,6 +159,8 @@ FENNEC_JAVA_FILES = \
ui/SubdocumentScrollHelper.java \
GeckoNetworkManager.java \
GeckoScreenOrientationListener.java \
+ UpdateService.java \
+ GeckoUpdateReceiver.java \
$(MOZGLUE_JAVA_FILES) \
$(UTIL_JAVA_FILES) \
$(NULL)
@@ -183,6 +185,7 @@ FENNEC_PP_JAVA_FILES = \
db/TabsProvider.java \
db/GeckoProvider.java \
SmsManager.java \
+ UpdateServiceHelper.java \
$(NULL)
FENNEC_PP_XML_FILES = \
@@ -207,6 +210,8 @@ 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)
@@ -231,6 +236,10 @@ DEFINES += \
-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
diff --git a/mobile/android/base/Restarter.java.in b/mobile/android/base/Restarter.java.in
index cb6c7d76479..ea14a59ad72 100644
--- a/mobile/android/base/Restarter.java.in
+++ b/mobile/android/base/Restarter.java.in
@@ -43,10 +43,7 @@ public class Restarter extends Activity {
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();
diff --git a/mobile/android/base/UpdateService.java b/mobile/android/base/UpdateService.java
new file mode 100644
index 00000000000..66d226b3989
--- /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;
+ }
+ }
+}
diff --git a/mobile/android/base/UpdateServiceHelper.java.in b/mobile/android/base/UpdateServiceHelper.java.in
new file mode 100644
index 00000000000..01018126bef
--- /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));
+ }
+}
diff --git a/mobile/android/base/locales/en-US/android_strings.dtd b/mobile/android/base/locales/en-US/android_strings.dtd
index 9c4267d9a54..c2a46cb4ee2 100644
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -224,3 +224,27 @@ just addresses the organization to follow, e.g. "This site is run by " -->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in
index 7b0151b14e8..54f038b0dd5 100644
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -210,4 +210,29 @@
&bookmarkhistory_import_wait;
&webapp_generic_name;
+
+
+ &updater_start_title;
+ &updater_start_ticker;
+ &updater_start_select;
+
+ &updater_downloading_title;
+ &updater_downloading_ticker;
+ &updater_downloading_ticker_failed;
+ &updater_downloading_select;
+ &updater_downloading_retry;
+ &updater_downloading_willapply;
+
+ &updater_apply_title;
+ &updater_apply_ticker;
+ &updater_apply_select;
+
+ &updater_installing_title;
+ &updater_installing_ticker;
+ &updater_installing_ticker_success;
+ &updater_installing_ticker_fail;
+ &updater_installing_text;
+ &updater_installing_text_success;
+ &updater_installing_text_fail;
+
diff --git a/mobile/android/chrome/content/about.xhtml b/mobile/android/chrome/content/about.xhtml
index f1955ec1b52..d84fac8c1b7 100644
--- a/mobile/android/chrome/content/about.xhtml
+++ b/mobile/android/chrome/content/about.xhtml
@@ -101,80 +101,29 @@
isChecking: false,
update: null,
- get updateEnabled() {
- try {
- return Services.prefs.getBoolPref("app.update.enabled");
- }
- catch (e) { }
- return true; // Mobile default is true
+ init: function() {
+ Services.obs.addObserver(this, "Update:CheckResult", false);
},
- startUpdate: function() {
- if (!this.update)
- this.update = this.um.activeUpdate;
-
- this.aus.pauseDownload();
-
- let updateTimerCallback = this.aus.QueryInterface(Ci.nsITimerCallback);
- updateTimerCallback.notify(null);
- }
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "Update:CheckResult") {
+ showUpdateMessage(aData == "true");
+ }
+ },
};
- 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) {
- },
-
- 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;
- }
-
- 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");
diff --git a/mobile/android/components/Makefile.in b/mobile/android/components/Makefile.in
index dcb91864c17..4b5155b3acc 100644
--- a/mobile/android/components/Makefile.in
+++ b/mobile/android/components/Makefile.in
@@ -43,8 +43,4 @@ ifdef MOZ_SAFE_BROWSING
EXTRA_COMPONENTS += SafeBrowsing.js
endif
-ifdef MOZ_UPDATER
-EXTRA_COMPONENTS += UpdatePrompt.js
-endif
-
include $(topsrcdir)/config/rules.mk
diff --git a/mobile/android/components/MobileComponents.manifest b/mobile/android/components/MobileComponents.manifest
index 1f4260f382d..7318e385a67 100644
--- a/mobile/android/components/MobileComponents.manifest
+++ b/mobile/android/components/MobileComponents.manifest
@@ -96,9 +96,3 @@ contract @mozilla.org/safebrowsing/application;1 {aadaed90-6c03-42d0-924a-fc6119
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
-
diff --git a/mobile/android/components/UpdatePrompt.js b/mobile/android/components/UpdatePrompt.js
deleted file mode 100644
index 12338794829..00000000000
--- 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]);
diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in
index d713dddadd0..aef140e31f9 100644
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -516,9 +516,6 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DLL_SUFFIX@
#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