mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 786380 - Implement new Java-based updater for Android r=cpeterson,mfinkle
--HG-- extra : rebase_source : 167db7e145d5e0cfa8c74e6e85570c6096538f64
This commit is contained in:
parent
fba649bee9
commit
4bf23f013d
@ -490,31 +490,11 @@ pref("app.update.timerMinimumDelay", 30); // seconds
|
|||||||
|
|
||||||
#ifdef MOZ_UPDATER
|
#ifdef MOZ_UPDATER
|
||||||
/* prefs used specifically for updating the app */
|
/* prefs used specifically for updating the app */
|
||||||
pref("app.update.enabled", true);
|
pref("app.update.enabled", false);
|
||||||
pref("app.update.auto", false);
|
|
||||||
pref("app.update.channel", "@MOZ_UPDATE_CHANNEL@");
|
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
|
// If you are looking for app.update.url, we no longer use it.
|
||||||
pref("app.update.interval", 86400);
|
// See mobile/android/base/UpdateServiceHelper.java.in
|
||||||
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
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// replace newlines with spaces on paste into single-line text boxes
|
// replace newlines with spaces on paste into single-line text boxes
|
||||||
|
@ -148,6 +148,12 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</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"
|
<receiver android:name="org.mozilla.gecko.GeckoMessageReceiver"
|
||||||
android:permission="@ANDROID_PACKAGE_NAME@.permissions.PASSWORD_PROVIDER">
|
android:permission="@ANDROID_PACKAGE_NAME@.permissions.PASSWORD_PROVIDER">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@ -224,6 +230,13 @@
|
|||||||
android:authorities="@ANDROID_PACKAGE_NAME@.db.tabs"
|
android:authorities="@ANDROID_PACKAGE_NAME@.db.tabs"
|
||||||
android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
|
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
|
#include ../sync/manifests/SyncAndroidManifest_services.xml.in
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@ import org.mozilla.gecko.util.GeckoBackgroundThread;
|
|||||||
import org.mozilla.gecko.util.GeckoEventListener;
|
import org.mozilla.gecko.util.GeckoEventListener;
|
||||||
import org.mozilla.gecko.util.GeckoEventResponder;
|
import org.mozilla.gecko.util.GeckoEventResponder;
|
||||||
import org.mozilla.gecko.GeckoAccessibility;
|
import org.mozilla.gecko.GeckoAccessibility;
|
||||||
|
import org.mozilla.gecko.updater.UpdateServiceHelper;
|
||||||
|
import org.mozilla.gecko.updater.UpdateService;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
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_DEBUG = "org.mozilla.gecko.DEBUG";
|
||||||
public static final String ACTION_BOOKMARK = "org.mozilla.gecko.BOOKMARK";
|
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_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 ACTION_INIT_PW = "org.mozilla.gecko.INIT_PW";
|
||||||
public static final String SAVED_STATE_TITLE = "title";
|
public static final String SAVED_STATE_TITLE = "title";
|
||||||
public static final String SAVED_STATE_IN_BACKGROUND = "inBackground";
|
public static final String SAVED_STATE_IN_BACKGROUND = "inBackground";
|
||||||
@ -910,8 +911,6 @@ abstract public class GeckoApp
|
|||||||
String host = message.getString("host");
|
String host = message.getString("host");
|
||||||
JSONArray permissions = message.getJSONArray("permissions");
|
JSONArray permissions = message.getJSONArray("permissions");
|
||||||
showSiteSettingsDialog(host, permissions);
|
showSiteSettingsDialog(host, permissions);
|
||||||
} else if (event.equals("Update:Restart")) {
|
|
||||||
doRestart("org.mozilla.gecko.restart_update");
|
|
||||||
} else if (event.equals("Tab:ViewportMetadata")) {
|
} else if (event.equals("Tab:ViewportMetadata")) {
|
||||||
int tabId = message.getInt("tabID");
|
int tabId = message.getInt("tabID");
|
||||||
Tab tab = Tabs.getInstance().getTab(tabId);
|
Tab tab = Tabs.getInstance().getTab(tabId);
|
||||||
@ -1000,6 +999,8 @@ abstract public class GeckoApp
|
|||||||
GeckoAppShell.shareImage(src, type);
|
GeckoAppShell.shareImage(src, type);
|
||||||
} else if (event.equals("Sanitize:ClearHistory")) {
|
} else if (event.equals("Sanitize:ClearHistory")) {
|
||||||
handleClearHistory();
|
handleClearHistory();
|
||||||
|
} else if (event.equals("Update:Check")) {
|
||||||
|
startService(new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE, null, this, UpdateService.class));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
|
Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
|
||||||
@ -1487,11 +1488,6 @@ abstract public class GeckoApp
|
|||||||
|
|
||||||
BrowserDB.initialize(getProfile().getName());
|
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 passedUri = null;
|
||||||
String uri = getURIFromIntent(intent);
|
String uri = getURIFromIntent(intent);
|
||||||
if (uri != null && uri.length() > 0) {
|
if (uri != null && uri.length() > 0) {
|
||||||
@ -1586,7 +1582,6 @@ abstract public class GeckoApp
|
|||||||
registerEventListener("ToggleChrome:Show");
|
registerEventListener("ToggleChrome:Show");
|
||||||
registerEventListener("ToggleChrome:Focus");
|
registerEventListener("ToggleChrome:Focus");
|
||||||
registerEventListener("Permissions:Data");
|
registerEventListener("Permissions:Data");
|
||||||
registerEventListener("Update:Restart");
|
|
||||||
registerEventListener("Tab:HasTouchListener");
|
registerEventListener("Tab:HasTouchListener");
|
||||||
registerEventListener("Tab:ViewportMetadata");
|
registerEventListener("Tab:ViewportMetadata");
|
||||||
registerEventListener("Session:StatePurged");
|
registerEventListener("Session:StatePurged");
|
||||||
@ -1601,6 +1596,7 @@ abstract public class GeckoApp
|
|||||||
registerEventListener("Share:Text");
|
registerEventListener("Share:Text");
|
||||||
registerEventListener("Share:Image");
|
registerEventListener("Share:Image");
|
||||||
registerEventListener("Sanitize:ClearHistory");
|
registerEventListener("Sanitize:ClearHistory");
|
||||||
|
registerEventListener("Update:Check");
|
||||||
|
|
||||||
if (SmsManager.getInstance() != null) {
|
if (SmsManager.getInstance() != null) {
|
||||||
SmsManager.getInstance().start();
|
SmsManager.getInstance().start();
|
||||||
@ -1621,6 +1617,8 @@ abstract public class GeckoApp
|
|||||||
GeckoNetworkManager.getInstance().init();
|
GeckoNetworkManager.getInstance().init();
|
||||||
GeckoNetworkManager.getInstance().start();
|
GeckoNetworkManager.getInstance().start();
|
||||||
|
|
||||||
|
UpdateServiceHelper.registerForUpdates(this);
|
||||||
|
|
||||||
GeckoScreenOrientationListener.getInstance().start();
|
GeckoScreenOrientationListener.getInstance().start();
|
||||||
|
|
||||||
final GeckoApp self = this;
|
final GeckoApp self = this;
|
||||||
@ -2029,7 +2027,6 @@ abstract public class GeckoApp
|
|||||||
unregisterEventListener("ToggleChrome:Show");
|
unregisterEventListener("ToggleChrome:Show");
|
||||||
unregisterEventListener("ToggleChrome:Focus");
|
unregisterEventListener("ToggleChrome:Focus");
|
||||||
unregisterEventListener("Permissions:Data");
|
unregisterEventListener("Permissions:Data");
|
||||||
unregisterEventListener("Update:Restart");
|
|
||||||
unregisterEventListener("Tab:HasTouchListener");
|
unregisterEventListener("Tab:HasTouchListener");
|
||||||
unregisterEventListener("Tab:ViewportMetadata");
|
unregisterEventListener("Tab:ViewportMetadata");
|
||||||
unregisterEventListener("Session:StatePurged");
|
unregisterEventListener("Session:StatePurged");
|
||||||
@ -2044,6 +2041,7 @@ abstract public class GeckoApp
|
|||||||
unregisterEventListener("Share:Text");
|
unregisterEventListener("Share:Text");
|
||||||
unregisterEventListener("Share:Image");
|
unregisterEventListener("Share:Image");
|
||||||
unregisterEventListener("Sanitize:ClearHistory");
|
unregisterEventListener("Sanitize:ClearHistory");
|
||||||
|
unregisterEventListener("Update:Check");
|
||||||
|
|
||||||
deleteTempFiles();
|
deleteTempFiles();
|
||||||
|
|
||||||
@ -2214,76 +2212,6 @@ abstract public class GeckoApp
|
|||||||
GeckoAppShell.handleNotification(action, alertName, alertCookie);
|
GeckoAppShell.handleNotification(action, alertName, alertCookie);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkAndLaunchUpdate() {
|
|
||||||
Log.i(LOGTAG, "Checking for an update");
|
|
||||||
|
|
||||||
int statusCode = 8; // UNEXPECTED_ERROR
|
|
||||||
File baseUpdateDir = null;
|
|
||||||
if (Build.VERSION.SDK_INT >= 8)
|
|
||||||
baseUpdateDir = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
|
|
||||||
else
|
|
||||||
baseUpdateDir = new File(Environment.getExternalStorageDirectory().getPath(), "download");
|
|
||||||
|
|
||||||
File updateDir = new File(new File(baseUpdateDir, "updates"),"0");
|
|
||||||
|
|
||||||
File updateFile = new File(updateDir, "update.apk");
|
|
||||||
File statusFile = new File(updateDir, "update.status");
|
|
||||||
|
|
||||||
if (!statusFile.exists() || !readUpdateStatus(statusFile).equals("pending"))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!updateFile.exists())
|
|
||||||
return;
|
|
||||||
|
|
||||||
Log.i(LOGTAG, "Update is available!");
|
|
||||||
|
|
||||||
// Launch APK
|
|
||||||
File updateFileToRun = new File(updateDir, getPackageName() + "-update.apk");
|
|
||||||
try {
|
|
||||||
if (updateFile.renameTo(updateFileToRun)) {
|
|
||||||
String amCmd = "/system/bin/am start -a android.intent.action.VIEW " +
|
|
||||||
"-n com.android.packageinstaller/.PackageInstallerActivity -d file://" +
|
|
||||||
updateFileToRun.getPath();
|
|
||||||
Log.i(LOGTAG, amCmd);
|
|
||||||
Runtime.getRuntime().exec(amCmd);
|
|
||||||
statusCode = 0; // OK
|
|
||||||
} else {
|
|
||||||
Log.i(LOGTAG, "Cannot rename the update file!");
|
|
||||||
statusCode = 7; // WRITE_ERROR
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.i(LOGTAG, "error launching installer to update", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the status file
|
|
||||||
String status = statusCode == 0 ? "succeeded\n" : "failed: "+ statusCode + "\n";
|
|
||||||
|
|
||||||
OutputStream outStream;
|
|
||||||
try {
|
|
||||||
byte[] buf = status.getBytes("UTF-8");
|
|
||||||
outStream = new FileOutputStream(statusFile);
|
|
||||||
outStream.write(buf, 0, buf.length);
|
|
||||||
outStream.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.i(LOGTAG, "error writing status file", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (statusCode == 0)
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String readUpdateStatus(File statusFile) {
|
|
||||||
String status = "";
|
|
||||||
try {
|
|
||||||
BufferedReader reader = new BufferedReader(new FileReader(statusFile));
|
|
||||||
status = reader.readLine();
|
|
||||||
reader.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.i(LOGTAG, "error reading update status", e);
|
|
||||||
}
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkMigrateProfile() {
|
private void checkMigrateProfile() {
|
||||||
final File profileDir = getProfile().getDir();
|
final File profileDir = getProfile().getDir();
|
||||||
final long currentTime = SystemClock.uptimeMillis();
|
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() {
|
private void connectGeckoLayerClient() {
|
||||||
mLayerView.getLayerClient().notifyGeckoReady();
|
mLayerView.getLayerClient().notifyGeckoReady();
|
||||||
|
|
||||||
|
@ -2297,6 +2297,11 @@ public class GeckoAppShell
|
|||||||
public static void unregisterSurfaceTextureFrameListener(Object surfaceTexture) {
|
public static void unregisterSurfaceTextureFrameListener(Object surfaceTexture) {
|
||||||
((SurfaceTexture)surfaceTexture).setOnFrameAvailableListener(null);
|
((SurfaceTexture)surfaceTexture).setOnFrameAvailableListener(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void notifyCheckUpdateResult(boolean result) {
|
||||||
|
if (GeckoApp.mAppContext != null)
|
||||||
|
GeckoApp.mAppContext.notifyCheckUpdateResult(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScreenshotHandler implements Runnable {
|
class ScreenshotHandler implements Runnable {
|
||||||
|
22
mobile/android/base/GeckoUpdateReceiver.java
Normal file
22
mobile/android/base/GeckoUpdateReceiver.java
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -159,6 +159,8 @@ FENNEC_JAVA_FILES = \
|
|||||||
ui/SubdocumentScrollHelper.java \
|
ui/SubdocumentScrollHelper.java \
|
||||||
GeckoNetworkManager.java \
|
GeckoNetworkManager.java \
|
||||||
GeckoScreenOrientationListener.java \
|
GeckoScreenOrientationListener.java \
|
||||||
|
UpdateService.java \
|
||||||
|
GeckoUpdateReceiver.java \
|
||||||
$(MOZGLUE_JAVA_FILES) \
|
$(MOZGLUE_JAVA_FILES) \
|
||||||
$(UTIL_JAVA_FILES) \
|
$(UTIL_JAVA_FILES) \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
@ -183,6 +185,7 @@ FENNEC_PP_JAVA_FILES = \
|
|||||||
db/TabsProvider.java \
|
db/TabsProvider.java \
|
||||||
db/GeckoProvider.java \
|
db/GeckoProvider.java \
|
||||||
SmsManager.java \
|
SmsManager.java \
|
||||||
|
UpdateServiceHelper.java \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
FENNEC_PP_XML_FILES = \
|
FENNEC_PP_XML_FILES = \
|
||||||
@ -207,6 +210,8 @@ else
|
|||||||
MIN_CPU_VERSION=5
|
MIN_CPU_VERSION=5
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
MOZ_APP_BUILDID=$(shell cat $(DEPTH)/config/buildid)
|
||||||
|
|
||||||
ifeq (,$(ANDROID_VERSION_CODE))
|
ifeq (,$(ANDROID_VERSION_CODE))
|
||||||
ifeq ($(MIN_CPU_VERSION),7)
|
ifeq ($(MIN_CPU_VERSION),7)
|
||||||
ANDROID_VERSION_CODE=$(shell cat $(DEPTH)/config/buildid | cut -c1-10)
|
ANDROID_VERSION_CODE=$(shell cat $(DEPTH)/config/buildid | cut -c1-10)
|
||||||
@ -231,6 +236,10 @@ DEFINES += \
|
|||||||
-DANDROID_VERSION_CODE=$(ANDROID_VERSION_CODE) \
|
-DANDROID_VERSION_CODE=$(ANDROID_VERSION_CODE) \
|
||||||
-DMOZILLA_OFFICIAL=$(MOZILLA_OFFICIAL) \
|
-DMOZILLA_OFFICIAL=$(MOZILLA_OFFICIAL) \
|
||||||
-DUA_BUILDID=$(UA_BUILDID) \
|
-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)
|
$(NULL)
|
||||||
|
|
||||||
ifdef MOZ_LINKER_EXTRACT
|
ifdef MOZ_LINKER_EXTRACT
|
||||||
|
@ -43,10 +43,7 @@ public class Restarter extends Activity {
|
|||||||
Log.i(LOGTAG, e.toString());
|
Log.i(LOGTAG, e.toString());
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
String action = "org.mozilla.gecko.restart_update".equals(getIntent().getAction()) ?
|
Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||||
App.ACTION_UPDATE : Intent.ACTION_MAIN;
|
|
||||||
|
|
||||||
Intent intent = new Intent(action);
|
|
||||||
intent.setClassName("@ANDROID_PACKAGE_NAME@",
|
intent.setClassName("@ANDROID_PACKAGE_NAME@",
|
||||||
"@ANDROID_PACKAGE_NAME@.App");
|
"@ANDROID_PACKAGE_NAME@.App");
|
||||||
Bundle b = getIntent().getExtras();
|
Bundle b = getIntent().getExtras();
|
||||||
|
638
mobile/android/base/UpdateService.java
Normal file
638
mobile/android/base/UpdateService.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
79
mobile/android/base/UpdateServiceHelper.java.in
Normal file
79
mobile/android/base/UpdateServiceHelper.java.in
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
@ -224,3 +224,27 @@ just addresses the organization to follow, e.g. "This site is run by " -->
|
|||||||
<!ENTITY bookmarkhistory_import_wait "Please wait...">
|
<!ENTITY bookmarkhistory_import_wait "Please wait...">
|
||||||
|
|
||||||
<!ENTITY webapp_generic_name "App">
|
<!ENTITY webapp_generic_name "App">
|
||||||
|
|
||||||
|
<!-- Updater notifications -->
|
||||||
|
<!ENTITY updater_start_title "&brandShortName;">
|
||||||
|
<!ENTITY updater_start_ticker "&brandShortName; update available…">
|
||||||
|
<!ENTITY updater_start_select "Select to download update.">
|
||||||
|
|
||||||
|
<!ENTITY updater_downloading_title "Downloading &brandShortName;">
|
||||||
|
<!ENTITY updater_downloading_ticker "Downloading &brandShortName; update…">
|
||||||
|
<!ENTITY updater_downloading_ticker_failed "Failed to download &brandShortName; update…">
|
||||||
|
<!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…">
|
||||||
|
<!ENTITY updater_apply_select "Select to apply downloaded update.">
|
||||||
|
|
||||||
|
<!ENTITY updater_installing_title "&brandShortName;">
|
||||||
|
<!ENTITY updater_installing_ticker "Updating &brandShortName;…">
|
||||||
|
<!ENTITY updater_installing_ticker_success "Successfully updated &brandShortName;">
|
||||||
|
<!ENTITY updater_installing_ticker_fail "Failed to update &brandShortName;">
|
||||||
|
<!ENTITY updater_installing_text "Installing update…">
|
||||||
|
<!ENTITY updater_installing_text_success "Succesfully updated.">
|
||||||
|
<!ENTITY updater_installing_text_fail "Failed to install update.">
|
||||||
|
@ -210,4 +210,29 @@
|
|||||||
<string name="bookmarkhistory_import_wait">&bookmarkhistory_import_wait;</string>
|
<string name="bookmarkhistory_import_wait">&bookmarkhistory_import_wait;</string>
|
||||||
|
|
||||||
<string name="webapp_generic_name">&webapp_generic_name;</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>
|
</resources>
|
||||||
|
@ -101,80 +101,29 @@
|
|||||||
isChecking: false,
|
isChecking: false,
|
||||||
update: null,
|
update: null,
|
||||||
|
|
||||||
get updateEnabled() {
|
init: function() {
|
||||||
try {
|
Services.obs.addObserver(this, "Update:CheckResult", false);
|
||||||
return Services.prefs.getBoolPref("app.update.enabled");
|
|
||||||
}
|
|
||||||
catch (e) { }
|
|
||||||
return true; // Mobile default is true
|
|
||||||
},
|
},
|
||||||
|
|
||||||
startUpdate: function() {
|
observe: function(aSubject, aTopic, aData) {
|
||||||
if (!this.update)
|
if (aTopic == "Update:CheckResult") {
|
||||||
this.update = this.um.activeUpdate;
|
showUpdateMessage(aData == "true");
|
||||||
|
}
|
||||||
this.aus.pauseDownload();
|
},
|
||||||
|
|
||||||
let updateTimerCallback = this.aus.QueryInterface(Ci.nsITimerCallback);
|
|
||||||
updateTimerCallback.notify(null);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
XPCOMUtils.defineLazyServiceGetter(Updater, "aus",
|
Updater.init();
|
||||||
"@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";
|
|
||||||
|
|
||||||
function checkForUpdates() {
|
function checkForUpdates() {
|
||||||
Updater.isChecking = true;
|
Updater.isChecking = true;
|
||||||
showCheckingMessage();
|
showCheckingMessage();
|
||||||
|
|
||||||
Updater.checker.checkForUpdates(UpdateCheckListener, true);
|
let bridge = Cc["@mozilla.org/android/bridge;1"].getService(Ci.nsIAndroidBridge);
|
||||||
|
bridge.handleGeckoMessage(JSON.stringify({
|
||||||
|
gecko: {
|
||||||
|
type: "Update:Check",
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
let updateLink = document.getElementById("updateLink");
|
let updateLink = document.getElementById("updateLink");
|
||||||
|
@ -43,8 +43,4 @@ ifdef MOZ_SAFE_BROWSING
|
|||||||
EXTRA_COMPONENTS += SafeBrowsing.js
|
EXTRA_COMPONENTS += SafeBrowsing.js
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifdef MOZ_UPDATER
|
|
||||||
EXTRA_COMPONENTS += UpdatePrompt.js
|
|
||||||
endif
|
|
||||||
|
|
||||||
include $(topsrcdir)/config/rules.mk
|
include $(topsrcdir)/config/rules.mk
|
||||||
|
@ -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
|
category app-startup SafeBrowsing service,@mozilla.org/safebrowsing/application;1
|
||||||
#endif
|
#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
|
|
||||||
|
|
||||||
|
@ -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]);
|
|
@ -516,9 +516,6 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DLL_SUFFIX@
|
|||||||
#ifdef MOZ_SAFE_BROWSING
|
#ifdef MOZ_SAFE_BROWSING
|
||||||
@BINPATH@/components/SafeBrowsing.js
|
@BINPATH@/components/SafeBrowsing.js
|
||||||
#endif
|
#endif
|
||||||
#ifdef MOZ_UPDATER
|
|
||||||
@BINPATH@/components/UpdatePrompt.js
|
|
||||||
#endif
|
|
||||||
@BINPATH@/components/XPIDialogService.js
|
@BINPATH@/components/XPIDialogService.js
|
||||||
@BINPATH@/components/browsercomps.xpt
|
@BINPATH@/components/browsercomps.xpt
|
||||||
@BINPATH@/extensions/feedback@mobile.mozilla.org.xpi
|
@BINPATH@/extensions/feedback@mobile.mozilla.org.xpi
|
||||||
|
Loading…
Reference in New Issue
Block a user