diff --git a/mobile/android/base/Distribution.java b/mobile/android/base/Distribution.java
index 9a6a485d9cf..999d0789708 100644
--- a/mobile/android/base/Distribution.java
+++ b/mobile/android/base/Distribution.java
@@ -1,11 +1,7 @@
/* -*- 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 ***** */
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko;
@@ -19,14 +15,12 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
-import java.io.BufferedReader;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.util.Scanner;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -39,87 +33,149 @@ public final class Distribution {
private static final int STATE_SET = 2;
/**
- * Initializes distribution if it hasn't already been initalized.
+ * Initializes distribution if it hasn't already been initalized. Sends
+ * messages to Gecko as appropriate.
*
- * @param packagePath specifies where to look for the distribution directory.
+ * @param packagePath where to look for the distribution directory.
*/
public static void init(final Context context, final String packagePath) {
// Read/write preferences and files on the background thread.
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
- // Bail if we've already initialized the distribution.
- SharedPreferences settings = context.getSharedPreferences(GeckoApp.PREFS_NAME, Activity.MODE_PRIVATE);
- String keyName = context.getPackageName() + ".distribution_state";
- int state = settings.getInt(keyName, STATE_UNKNOWN);
- if (state == STATE_NONE) {
- return;
- }
-
- // Send a message to Gecko if we've set a distribution.
- if (state == STATE_SET) {
- GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Distribution:Set", ""));
- return;
- }
-
- boolean distributionSet = false;
- try {
- // First, try copying distribution files out of the APK.
- distributionSet = copyFiles(context, packagePath);
- } catch (IOException e) {
- Log.e(LOGTAG, "Error copying distribution files", e);
- }
-
- if (!distributionSet) {
- // If there aren't any distribution files in the APK, look in the /system directory.
- File distDir = new File("/system/" + context.getPackageName() + "/distribution");
- if (distDir.exists()) {
- distributionSet = true;
- }
- }
-
+ Distribution dist = new Distribution(context, packagePath);
+ boolean distributionSet = dist.doInit();
if (distributionSet) {
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Distribution:Set", ""));
- settings.edit().putInt(keyName, STATE_SET).commit();
- } else {
- settings.edit().putInt(keyName, STATE_NONE).commit();
}
}
});
}
+ /**
+ * Use Context.getPackageResourcePath
to find an implicit
+ * package path.
+ */
+ public static void init(final Context context) {
+ Distribution.init(context, context.getPackageResourcePath());
+ }
+
+ /**
+ * Returns parsed contents of bookmarks.json.
+ * This method should only be called from a background thread.
+ */
+ public static JSONArray getBookmarks(final Context context) {
+ Distribution dist = new Distribution(context);
+ return dist.getBookmarks();
+ }
+
+ private final String packagePath;
+ private final Context context;
+
+ private int state = STATE_UNKNOWN;
+ private File distributionDir = null;
+
+ /**
+ * @param packagePath where to look for the distribution directory.
+ */
+ public Distribution(final Context context, final String packagePath) {
+ this.context = context;
+ this.packagePath = packagePath;
+ }
+
+ public Distribution(final Context context) {
+ this(context, context.getPackageResourcePath());
+ }
+
+ /**
+ * Don't call from the main thread.
+ *
+ * @return true if we've set a distribution.
+ */
+ private boolean doInit() {
+ // Bail if we've already tried to initialize the distribution, and
+ // there wasn't one.
+ SharedPreferences settings = context.getSharedPreferences(GeckoApp.PREFS_NAME, Activity.MODE_PRIVATE);
+ String keyName = context.getPackageName() + ".distribution_state";
+ this.state = settings.getInt(keyName, STATE_UNKNOWN);
+ if (this.state == STATE_NONE) {
+ return false;
+ }
+
+ // We've done the work once; don't do it again.
+ if (this.state == STATE_SET) {
+ // Note that we don't compute the distribution directory.
+ // Call `ensureDistributionDir` if you need it.
+ return true;
+ }
+
+ boolean distributionSet = false;
+ try {
+ // First, try copying distribution files out of the APK.
+ distributionSet = copyFiles();
+ if (distributionSet) {
+ // We always copy to the data dir, and we only copy files from
+ // a 'distribution' subdirectory. Track our dist dir now that
+ // we know it.
+ this.distributionDir = new File(getDataDir(), "distribution/");
+ }
+ } catch (IOException e) {
+ Log.e(LOGTAG, "Error copying distribution files", e);
+ }
+
+ if (!distributionSet) {
+ // If there aren't any distribution files in the APK, look in the /system directory.
+ File distDir = getSystemDistributionDir();
+ if (distDir.exists()) {
+ distributionSet = true;
+ this.distributionDir = distDir;
+ }
+ }
+
+ this.state = distributionSet ? STATE_SET : STATE_NONE;
+ settings.edit().putInt(keyName, this.state).commit();
+ return distributionSet;
+ }
+
/**
* Copies the /distribution folder out of the APK and into the app's data directory.
* Returns true if distribution files were found and copied.
*/
- private static boolean copyFiles(Context context, String packagePath) throws IOException {
+ private boolean copyFiles() throws IOException {
File applicationPackage = new File(packagePath);
ZipFile zip = new ZipFile(applicationPackage);
boolean distributionSet = false;
Enumeration extends ZipEntry> zipEntries = zip.entries();
+
+ byte[] buffer = new byte[1024];
while (zipEntries.hasMoreElements()) {
ZipEntry fileEntry = zipEntries.nextElement();
String name = fileEntry.getName();
- if (!name.startsWith("distribution/"))
+ if (!name.startsWith("distribution/")) {
continue;
+ }
distributionSet = true;
- File dataDir = new File(context.getApplicationInfo().dataDir);
- File outFile = new File(dataDir, name);
-
+ File outFile = new File(getDataDir(), name);
File dir = outFile.getParentFile();
- if (!dir.exists())
- dir.mkdirs();
+
+ if (!dir.exists()) {
+ if (!dir.mkdirs()) {
+ Log.e(LOGTAG, "Unable to create directories: " + dir.getAbsolutePath());
+ continue;
+ }
+ }
InputStream fileStream = zip.getInputStream(fileEntry);
OutputStream outStream = new FileOutputStream(outFile);
- int b;
- while ((b = fileStream.read()) != -1)
- outStream.write(b);
+ int count;
+ while ((count = fileStream.read(buffer)) != -1) {
+ outStream.write(buffer, 0, count);
+ }
fileStream.close();
outStream.close();
@@ -132,77 +188,77 @@ public final class Distribution {
}
/**
- * Returns parsed contents of bookmarks.json.
- * This method should only be called from a background thread.
+ * After calling this method, either distributionDir
+ * will be set, or there is no distribution in use.
+ *
+ * Only call after init.
*/
- public static JSONArray getBookmarks(Context context) {
- SharedPreferences settings = context.getSharedPreferences(GeckoApp.PREFS_NAME, Activity.MODE_PRIVATE);
- String keyName = context.getPackageName() + ".distribution_state";
- int state = settings.getInt(keyName, STATE_UNKNOWN);
- if (state == STATE_NONE) {
+ private File ensureDistributionDir() {
+ if (this.distributionDir != null) {
+ return this.distributionDir;
+ }
+
+ if (this.state != STATE_SET) {
return null;
}
- ZipFile zip = null;
- InputStream inputStream = null;
+ // After init, we know that either we've copied a distribution out of
+ // the APK, or it exists in /system/.
+ // Look in each location in turn.
+ // (This could be optimized by caching the path in shared prefs.)
+ File copied = new File(getDataDir(), "distribution/");
+ if (copied.exists()) {
+ return this.distributionDir = copied;
+ }
+ File system = getSystemDistributionDir();
+ if (system.exists()) {
+ return this.distributionDir = system;
+ }
+ return null;
+ }
+
+ public JSONArray getBookmarks() {
+ if (this.state == STATE_UNKNOWN) {
+ this.doInit();
+ }
+
+ File dist = ensureDistributionDir();
+ if (dist == null) {
+ return null;
+ }
+
+ File bookmarks = new File(dist, "bookmarks.json");
+ if (!bookmarks.exists()) {
+ return null;
+ }
+
+ // Shortcut to slurp a file without messing around with streams.
try {
- if (state == STATE_UNKNOWN) {
- // If the distribution hasn't been set yet, first look for bookmarks.json in the APK.
- File applicationPackage = new File(context.getPackageResourcePath());
- zip = new ZipFile(applicationPackage);
- ZipEntry zipEntry = zip.getEntry("distribution/bookmarks.json");
- if (zipEntry != null) {
- inputStream = zip.getInputStream(zipEntry);
- } else {
- // If there's no bookmarks.json in the APK, but there is a preferences.json,
- // don't create any distribution bookmarks.
- zipEntry = zip.getEntry("distribution/preferences.json");
- if (zipEntry != null) {
- return null;
- }
- // Otherwise, look for bookmarks.json in the /system directory.
- File systemFile = new File("/system/" + context.getPackageName() + "/distribution/bookmarks.json");
- if (!systemFile.exists()) {
- return null;
- }
- inputStream = new FileInputStream(systemFile);
+ Scanner scanner = null;
+ try {
+ scanner = new Scanner(bookmarks, "UTF-8");
+ final String contents = scanner.useDelimiter("\\A").next();
+ return new JSONArray(contents);
+ } finally {
+ if (scanner != null) {
+ scanner.close();
}
- } else {
- // Otherwise, first look for the distribution in the data directory.
- File distDir = new File(context.getApplicationInfo().dataDir, "distribution");
- if (!distDir.exists()) {
- // If that doesn't exist, then we must be using a distribution from the system directory.
- distDir = new File("/system/" + context.getPackageName() + "/distribution");
- }
-
- File file = new File(distDir, "bookmarks.json");
- inputStream = new FileInputStream(file);
}
- // Convert input stream to JSONArray
- BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
- StringBuilder stringBuilder = new StringBuilder();
- String s;
- while ((s = reader.readLine()) != null) {
- stringBuilder.append(s);
- }
- return new JSONArray(stringBuilder.toString());
} catch (IOException e) {
Log.e(LOGTAG, "Error getting bookmarks", e);
} catch (JSONException e) {
Log.e(LOGTAG, "Error parsing bookmarks.json", e);
- } finally {
- try {
- if (zip != null) {
- zip.close();
- }
- if (inputStream != null) {
- inputStream.close();
- }
- } catch (IOException e) {
- Log.e(LOGTAG, "Error closing streams", e);
- }
}
+
return null;
}
+
+ private String getDataDir() {
+ return context.getApplicationInfo().dataDir;
+ }
+
+ private File getSystemDistributionDir() {
+ return new File("/system/" + context.getPackageName() + "/distribution");
+ }
}