Bug 786380 - Implement new Java-based updater for Android r=cpeterson,mfinkle

--HG--
extra : rebase_source : 167db7e145d5e0cfa8c74e6e85570c6096538f64
This commit is contained in:
James Willcox 2012-08-31 09:31:29 -04:00
parent fba649bee9
commit 4bf23f013d
16 changed files with 845 additions and 501 deletions

View File

@ -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

View File

@ -148,6 +148,12 @@
</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>
@ -224,6 +230,13 @@
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>

View File

@ -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();

View File

@ -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 {

View File

@ -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));
}
}
}

View File

@ -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

View File

@ -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();

View File

@ -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;
}
}
}

View File

@ -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));
}
}

View File

@ -224,3 +224,27 @@ just addresses the organization to follow, e.g. "This site is run by " -->
<!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.">

View File

@ -210,4 +210,29 @@
<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>

View File

@ -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");

View File

@ -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

View File

@ -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

View File

@ -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]);

View File

@ -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