From ce0533c704fb9db497409c2846a05d61073548e2 Mon Sep 17 00:00:00 2001 From: Margaret Leibovic Date: Fri, 1 Feb 2013 15:45:33 -0800 Subject: [PATCH] Bug 834681 - Add support for basic distribution modifications. r=mfinkle --- mobile/android/base/BrowserApp.java | 2 + mobile/android/base/Distribution.java | 92 ++++++++++++++ mobile/android/base/Makefile.in | 1 + mobile/android/chrome/content/browser.js | 121 ++++++++++++++++--- mobile/android/installer/package-manifest.in | 1 + toolkit/mozapps/installer/packager.mk | 1 + 6 files changed, 199 insertions(+), 19 deletions(-) create mode 100644 mobile/android/base/Distribution.java diff --git a/mobile/android/base/BrowserApp.java b/mobile/android/base/BrowserApp.java index cfca24733f27..056b2d2d3216 100644 --- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -239,6 +239,8 @@ abstract public class BrowserApp extends GeckoApp registerEventListener("Feedback:MaybeLater"); registerEventListener("Dex:Load"); registerEventListener("Telemetry:Gather"); + + Distribution.init(this); } @Override diff --git a/mobile/android/base/Distribution.java b/mobile/android/base/Distribution.java new file mode 100644 index 000000000000..0e6b509bd0f2 --- /dev/null +++ b/mobile/android/base/Distribution.java @@ -0,0 +1,92 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * ***** BEGIN LICENSE BLOCK ***** + * + * 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/. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.gecko; + +import org.mozilla.gecko.util.GeckoBackgroundThread; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import java.lang.Exception; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public final class Distribution { + private static final String LOGTAG = "Distribution"; + + /** + * Initializes distribution if it hasn't already been initalized. + */ + public static void init(final Activity activity) { + // Read/write preferences and files on the background thread. + GeckoBackgroundThread.getHandler().post(new Runnable() { + public void run() { + // Bail if we've already initialized the distribution. + SharedPreferences settings = activity.getPreferences(Activity.MODE_PRIVATE); + String keyName = activity.getPackageName() + ".distribution_initialized"; + if (settings.getBoolean(keyName, false)) + return; + + settings.edit().putBoolean(keyName, true).commit(); + + try { + copyFiles(activity); + } catch (IOException e) { + Log.e(LOGTAG, "Error copying distribution files", e); + } + } + }); + } + + /** + * Copies the /distribution folder out of the APK and into the app's data directory. + */ + private static void copyFiles(Activity activity) throws IOException { + File applicationPackage = new File(activity.getPackageResourcePath()); + ZipFile zip = new ZipFile(applicationPackage); + + Enumeration zipEntries = zip.entries(); + while (zipEntries.hasMoreElements()) { + ZipEntry fileEntry = zipEntries.nextElement(); + String name = fileEntry.getName(); + + if (!name.startsWith("distribution/")) + continue; + + File dataDir = new File(activity.getApplicationInfo().dataDir); + File outFile = new File(dataDir, name); + + File dir = outFile.getParentFile(); + if (!dir.exists()) + dir.mkdirs(); + + InputStream fileStream = zip.getInputStream(fileEntry); + OutputStream outStream = new FileOutputStream(outFile); + + int b; + while ((b = fileStream.read()) != -1) + outStream.write(b); + + fileStream.close(); + outStream.close(); + outFile.setLastModified(fileEntry.getTime()); + } + + zip.close(); + } +} diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index fca115ab16a0..8c4fe693b589 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -65,6 +65,7 @@ FENNEC_JAVA_FILES = \ db/BrowserDB.java \ db/LocalBrowserDB.java \ db/DBUtils.java \ + Distribution.java \ DoorHanger.java \ DoorHangerPopup.java \ Favicons.java \ diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index a94dc13f08b1..c362db7b9d50 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -8135,37 +8135,25 @@ var Distribution = { init: function dc_init() { Services.obs.addObserver(this, "Distribution:Set", false); + Services.obs.addObserver(this, "prefservice:after-app-defaults", false); + + // Reload the default prefs so we can observe "prefservice:after-app-defaults" + Services.prefs.QueryInterface(Ci.nsIObserver).observe(null, "reload-default-prefs", null); // Look for file outside the APK: // /data/data/org.mozilla.fennec/distribution.json this._file = Services.dirsvc.get("XCurProcD", Ci.nsIFile); this._file.append("distribution.json"); - if (this._file.exists()) { - let channel = NetUtil.newChannel(this._file); - channel.contentType = "application/json"; - NetUtil.asyncFetch(channel, function(aStream, aResult) { - if (!Components.isSuccessCode(aResult)) { - Cu.reportError("Distribution: Could not read from distribution.json file"); - return; - } - - let raw = NetUtil.readInputStreamToString(aStream, aStream.available(), { charset : "UTF-8" }) || ""; - aStream.close(); - - try { - this.update(JSON.parse(raw)); - } catch (ex) { - Cu.reportError("Distribution: Could not parse JSON: " + ex); - } - }.bind(this)); - } + this.readJSON(this._file, this.update); }, uninit: function dc_uninit() { Services.obs.removeObserver(this, "Distribution:Set"); + Services.obs.removeObserver(this, "prefservice:after-app-defaults"); }, observe: function dc_observe(aSubject, aTopic, aData) { + // This event is only used for campaign tracking if (aTopic == "Distribution:Set") { // Update the prefs for this session try { @@ -8185,6 +8173,8 @@ var Distribution = { // Asynchronously copy the data to the file. let istream = converter.convertToInputStream(aData); NetUtil.asyncCopy(istream, ostream, function(rc) { }); + } else if (aTopic == "prefservice:after-app-defaults") { + this.getPrefs(); } }, @@ -8193,6 +8183,99 @@ var Distribution = { let defaults = Services.prefs.getDefaultBranch(null); defaults.setCharPref("distribution.id", aData.id); defaults.setCharPref("distribution.version", aData.version); + }, + + getPrefs: function dc_getPrefs() { + // Look for preferences file outside the APK: + // /data/data/org.mozilla.fennec/distribution/preferences.json + let file = Services.dirsvc.get("XCurProcD", Ci.nsIFile); + file.append("distribution"); + file.append("preferences.json"); + + this.readJSON(file, this.applyPrefs); + }, + + applyPrefs: function dc_applyPrefs(aData) { + // Check for required Global preferences + let global = aData["Global"]; + if (!(global && global["id"] && global["version"] && global["about"])) { + Cu.reportError("Distribution: missing or incomplete Global preferences"); + return; + } + + // Force the distribution preferences on the default branch + let defaults = Services.prefs.getDefaultBranch(null); + defaults.setCharPref("distribution.id", global["id"]); + defaults.setCharPref("distribution.version", global["version"]); + + let locale = Services.prefs.getCharPref("general.useragent.locale"); + let aboutString = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString); + aboutString.data = global["about." + locale] || global["about"]; + defaults.setComplexValue("distribution.about", Ci.nsISupportsString, aboutString); + + let prefs = aData["Preferences"]; + for (let key in prefs) { + try { + let value = prefs[key]; + switch (typeof value) { + case "boolean": + defaults.setBoolPref(key, value); + break; + case "number": + defaults.setIntPref(key, value); + break; + case "string": + case "undefined": + defaults.setCharPref(key, value); + break; + } + } catch (e) { /* ignore bad prefs and move on */ } + } + + let localizedString = Cc["@mozilla.org/pref-localizedstring;1"].createInstance(Ci.nsIPrefLocalizedString); + let localizeablePrefs = aData["LocalizablePreferences"]; + for (let key in localizeablePrefs) { + try { + let value = localizeablePrefs[key]; + value = value.replace("%LOCALE%", locale, "g"); + localizedString.data = "data:text/plain," + key + "=" + value; + defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedString); + } catch (e) { /* ignore bad prefs and move on */ } + } + + let localizeablePrefsOverrides = aData["LocalizablePreferences." + locale]; + for (let key in localizeablePrefsOverrides) { + try { + let value = localizeablePrefsOverrides[key]; + localizedString.data = "data:text/plain," + key + "=" + value; + defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedString); + } catch (e) { /* ignore bad prefs and move on */ } + } + }, + + // aFile is an nsIFile + // aCallback takes the parsed JSON object as a parameter + readJSON: function dc_readJSON(aFile, aCallback) { + if (!aFile.exists()) + return; + + let channel = NetUtil.newChannel(aFile); + channel.contentType = "application/json"; + NetUtil.asyncFetch(channel, function(aStream, aResult) { + if (!Components.isSuccessCode(aResult)) { + Cu.reportError("Distribution: Could not read from " + aFile.leafName + " file"); + return; + } + + let raw = NetUtil.readInputStreamToString(aStream, aStream.available(), { charset : "UTF-8" }) || ""; + aStream.close(); + + try { + aCallback(JSON.parse(raw)); + } catch (e) { + Cu.reportError("Distribution: Could not parse JSON: " + e); + } + }); } }; diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in index 40232e6a453e..1670d2592ec6 100644 --- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -58,6 +58,7 @@ @BINPATH@/res/drawable-hdpi @BINPATH@/res/layout @BINPATH@/recommended-addons.json +@BINPATH@/distribution/* [browser] ; [Base Browser Files] diff --git a/toolkit/mozapps/installer/packager.mk b/toolkit/mozapps/installer/packager.mk index e559883762a6..3c4c15bbba1b 100644 --- a/toolkit/mozapps/installer/packager.mk +++ b/toolkit/mozapps/installer/packager.mk @@ -294,6 +294,7 @@ DIST_FILES += \ update.locale \ removed-files \ recommended-addons.json \ + distribution \ $(NULL) ifdef MOZ_ENABLE_SZIP