Bug 1102488 - Part 2: Don't preprocess GeckoLoader. r=rnewman
authorNick Alexander <nalexander@mozilla.com>
Thu, 20 Nov 2014 14:39:10 -0800
changeset 241744 840208e80c41509e3e58ec90e934524a75891231
parent 241743 51ce636dfed12a83c6b84babeb27df11c1d57ad6
child 241745 7a2706f87fe601251a119b8ba390ffa7fac592e9
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrnewman
bugs1102488
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1102488 - Part 2: Don't preprocess GeckoLoader. r=rnewman I replaced the compile-time ANDROID_PACKAGE_NAME with the run-time context.getPackageName() for two reasons: 1) I claim this is more correct. It's hard to imagine Fennec working with ANDROID_PACKAGE_NAME != context.getPackageName(), but right here we should use the run-time, not the build-time state. 2) GeckoLoader is part of GeckoView, and as such it shouldn't assume anything about the package it's running as. GeckoView consumers may ship for multiple architectures, so we shouldn't assume anything about the build-time architecture, but the reference to MOZ_CPU_ABI is purely diagnostic. There are substantive changes to make here; we'll cross that bridge some other time.
mobile/android/base/AppConstants.java.in
mobile/android/base/moz.build
mobile/android/base/mozglue/GeckoLoader.java
mobile/android/base/mozglue/GeckoLoader.java.in
--- a/mobile/android/base/AppConstants.java.in
+++ b/mobile/android/base/AppConstants.java.in
@@ -260,9 +260,16 @@ public class AppConstants {
 //#endif
 
     public static final boolean ANDROID_DOWNLOADS_INTEGRATION =
 //#ifdef MOZ_ANDROID_DOWNLOADS_INTEGRATION
     AppConstants.Versions.feature12Plus;
 //#else
     false;
 //#endif
+
+    public static final boolean MOZ_LINKER_EXTRACT =
+//#ifdef MOZ_LINKER_EXTRACT
+    true;
+//#else
+    false;
+//#endif
 }
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -31,28 +31,27 @@ if CONFIG['MOZ_NATIVE_DEVICES']:
 
 resjar.javac_flags += ['-Xlint:all']
 
 mgjar = add_java_jar('gecko-mozglue')
 mgjar.sources += [
     'mozglue/ByteBufferInputStream.java',
     'mozglue/ContextUtils.java',
     'mozglue/DirectBufferAllocator.java',
+    'mozglue/GeckoLoader.java',
     'mozglue/generatorannotations/OptionalGeneratedParameter.java',
     'mozglue/generatorannotations/WrapElementForJNI.java',
     'mozglue/generatorannotations/WrapEntireClassForJNI.java',
     'mozglue/JNITarget.java',
     'mozglue/NativeReference.java',
     'mozglue/NativeZip.java',
     'mozglue/RobocopTarget.java',
     'mozglue/WebRTCJNITarget.java',
 ]
-mgjar.generated_sources += [
-    'org/mozilla/gecko/mozglue/GeckoLoader.java',
-]
+mgjar.generated_sources = [] # Keep it this way.
 mgjar.extra_jars += [
     'constants.jar',
 ]
 mgjar.javac_flags += ['-Xlint:all']
 
 gujar = add_java_jar('gecko-util')
 gujar.sources += [
     'util/ActivityResultHandler.java',
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/mozglue/GeckoLoader.java
@@ -0,0 +1,566 @@
+/* -*- 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.mozglue;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.util.Locale;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Environment;
+import android.util.Log;
+
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.mozglue.ContextUtils.SafeIntent;
+
+public final class GeckoLoader {
+    private static final String LOGTAG = "GeckoLoader";
+
+    private static volatile SafeIntent sIntent;
+    private static File sCacheFile;
+    private static File sGREDir;
+
+    private static final Object sLibLoadingLock = new Object();
+    // Must hold sLibLoadingLock while accessing the following boolean variables.
+    private static boolean sSQLiteLibsLoaded;
+    private static boolean sNSSLibsLoaded;
+    private static boolean sMozGlueLoaded;
+    private static boolean sLibsSetup;
+
+    private GeckoLoader() {
+        // prevent instantiation
+    }
+
+    public static File getCacheDir(Context context) {
+        if (sCacheFile == null) {
+            sCacheFile = context.getCacheDir();
+        }
+        return sCacheFile;
+    }
+
+    public static File getGREDir(Context context) {
+        if (sGREDir == null) {
+            sGREDir = new File(context.getApplicationInfo().dataDir);
+        }
+        return sGREDir;
+    }
+
+    private static void setupPluginEnvironment(Context context, String[] pluginDirs) {
+        // setup plugin path directories
+        try {
+            // Check to see if plugins were blocked.
+            if (pluginDirs == null) {
+                putenv("MOZ_PLUGINS_BLOCKED=1");
+                putenv("MOZ_PLUGIN_PATH=");
+                return;
+            }
+
+            StringBuilder pluginSearchPath = new StringBuilder();
+            for (int i = 0; i < pluginDirs.length; i++) {
+                pluginSearchPath.append(pluginDirs[i]);
+                pluginSearchPath.append(":");
+            }
+            putenv("MOZ_PLUGIN_PATH="+pluginSearchPath);
+
+            File pluginDataDir = context.getDir("plugins", 0);
+            putenv("ANDROID_PLUGIN_DATADIR=" + pluginDataDir.getPath());
+
+            File pluginPrivateDataDir = context.getDir("plugins_private", 0);
+            putenv("ANDROID_PLUGIN_DATADIR_PRIVATE=" + pluginPrivateDataDir.getPath());
+
+        } catch (Exception ex) {
+            Log.w(LOGTAG, "Caught exception getting plugin dirs.", ex);
+        }
+    }
+
+    private static void setupDownloadEnvironment(final Context context) {
+        try {
+            File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+            File updatesDir  = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
+            if (downloadDir == null) {
+                downloadDir = new File(Environment.getExternalStorageDirectory().getPath(), "download");
+            }
+            if (updatesDir == null) {
+                updatesDir = downloadDir;
+            }
+            putenv("DOWNLOADS_DIRECTORY=" + downloadDir.getPath());
+            putenv("UPDATES_DIRECTORY="   + updatesDir.getPath());
+        } catch (Exception e) {
+            Log.w(LOGTAG, "No download directory found.", e);
+        }
+    }
+
+    private static void delTree(File file) {
+        if (file.isDirectory()) {
+            File children[] = file.listFiles();
+            for (File child : children) {
+                delTree(child);
+            }
+        }
+        file.delete();
+    }
+
+    private static File getTmpDir(Context context) {
+        File tmpDir = context.getDir("tmpdir", Context.MODE_PRIVATE);
+        // check if the old tmp dir is there
+        File oldDir = new File(tmpDir.getParentFile(), "app_tmp");
+        if (oldDir.exists()) {
+            delTree(oldDir);
+        }
+        return tmpDir;
+    }
+
+    public static void setLastIntent(SafeIntent intent) {
+        sIntent = intent;
+    }
+
+    public static void setupGeckoEnvironment(Context context, String[] pluginDirs, String profilePath) {
+        // if we have an intent (we're being launched by an activity)
+        // read in any environmental variables from it here
+        final SafeIntent intent = sIntent;
+        if (intent != null) {
+            String env = intent.getStringExtra("env0");
+            Log.d(LOGTAG, "Gecko environment env0: " + env);
+            for (int c = 1; env != null; c++) {
+                putenv(env);
+                env = intent.getStringExtra("env" + c);
+                Log.d(LOGTAG, "env" + c + ": " + env);
+            }
+        }
+
+        setupPluginEnvironment(context, pluginDirs);
+        setupDownloadEnvironment(context);
+
+        // profile home path
+        putenv("HOME=" + profilePath);
+
+        // setup the tmp path
+        File f = getTmpDir(context);
+        if (!f.exists()) {
+            f.mkdirs();
+        }
+        putenv("TMPDIR=" + f.getPath());
+
+        // setup the downloads path
+        f = Environment.getDownloadCacheDirectory();
+        putenv("EXTERNAL_STORAGE=" + f.getPath());
+
+        // setup the app-specific cache path
+        f = context.getCacheDir();
+        putenv("CACHE_DIRECTORY=" + f.getPath());
+
+        /* We really want to use this code, but it requires bumping up the SDK to 17 so for now
+           we will use reflection. See https://bugzilla.mozilla.org/show_bug.cgi?id=811763#c11
+
+        if (Build.VERSION.SDK_INT >= 17) {
+            android.os.UserManager um = (android.os.UserManager)context.getSystemService(Context.USER_SERVICE);
+            if (um != null) {
+                putenv("MOZ_ANDROID_USER_SERIAL_NUMBER=" + um.getSerialNumberForUser(android.os.Process.myUserHandle()));
+            } else {
+                Log.d(LOGTAG, "Unable to obtain user manager service on a device with SDK version " + Build.VERSION.SDK_INT);
+            }
+        }
+        */
+        try {
+            Object userManager = context.getSystemService("user");
+            if (userManager != null) {
+                // if userManager is non-null that means we're running on 4.2+ and so the rest of this
+                // should just work
+                Object userHandle = android.os.Process.class.getMethod("myUserHandle", (Class[])null).invoke(null);
+                Object userSerial = userManager.getClass().getMethod("getSerialNumberForUser", userHandle.getClass()).invoke(userManager, userHandle);
+                putenv("MOZ_ANDROID_USER_SERIAL_NUMBER=" + userSerial.toString());
+            }
+        } catch (Exception e) {
+            // Guard against any unexpected failures
+            Log.d(LOGTAG, "Unable to set the user serial number", e);
+        }
+
+        setupLocaleEnvironment();
+
+        // We don't need this any more.
+        sIntent = null;
+    }
+
+    private static void loadLibsSetup(Context context) {
+        synchronized (sLibLoadingLock) {
+            if (sLibsSetup) {
+                return;
+            }
+            sLibsSetup = true;
+        }
+
+        // The package data lib directory isn't placed in ld.so's
+        // search path, so we have to manually load libraries that
+        // libxul will depend on.  Not ideal.
+
+        File cacheFile = getCacheDir(context);
+        putenv("GRE_HOME=" + getGREDir(context).getPath());
+
+        // setup the libs cache
+        String linkerCache = System.getenv("MOZ_LINKER_CACHE");
+        if (linkerCache == null) {
+            linkerCache = cacheFile.getPath();
+            putenv("MOZ_LINKER_CACHE=" + linkerCache);
+        }
+
+        // Disable on-demand decompression of the linker on devices where it
+        // is known to cause crashes.
+        String forced_ondemand = System.getenv("MOZ_LINKER_ONDEMAND");
+        if (forced_ondemand == null) {
+            if ("HTC".equals(android.os.Build.MANUFACTURER) &&
+                "HTC Vision".equals(android.os.Build.MODEL)) {
+                putenv("MOZ_LINKER_ONDEMAND=0");
+            }
+        }
+
+        if (AppConstants.MOZ_LINKER_EXTRACT) {
+            putenv("MOZ_LINKER_EXTRACT=1");
+            // Ensure that the cache dir is world-writable
+            File cacheDir = new File(linkerCache);
+            if (cacheDir.isDirectory()) {
+                cacheDir.setWritable(true, false);
+                cacheDir.setExecutable(true, false);
+                cacheDir.setReadable(true, false);
+            }
+        }
+    }
+
+    @RobocopTarget
+    public static void loadSQLiteLibs(final Context context, final String apkName) {
+        synchronized (sLibLoadingLock) {
+            if (sSQLiteLibsLoaded) {
+                return;
+            }
+            sSQLiteLibsLoaded = true;
+        }
+
+        loadMozGlue(context);
+        loadLibsSetup(context);
+        loadSQLiteLibsNative(apkName);
+    }
+
+    public static void loadNSSLibs(final Context context, final String apkName) {
+        synchronized (sLibLoadingLock) {
+            if (sNSSLibsLoaded) {
+                return;
+            }
+            sNSSLibsLoaded = true;
+        }
+
+        loadMozGlue(context);
+        loadLibsSetup(context);
+        loadNSSLibsNative(apkName);
+    }
+
+    @SuppressWarnings("deprecation")
+    private static final String getCPUABI() {
+        return android.os.Build.CPU_ABI;
+    }
+
+    /**
+     * Copy a library out of our APK.
+     *
+     * @param context a Context.
+     * @param lib the name of the library; e.g., "mozglue".
+     * @param outDir the output directory for the .so. No trailing slash.
+     * @return true on success, false on failure.
+     */
+    private static boolean extractLibrary(final Context context, final String lib, final String outDir) {
+        final String apkPath = context.getApplicationInfo().sourceDir;
+
+        // Sanity check.
+        if (!apkPath.endsWith(".apk")) {
+            Log.w(LOGTAG, "sourceDir is not an APK.");
+            return false;
+        }
+
+        // Try to extract the named library from the APK.
+        File outDirFile = new File(outDir);
+        if (!outDirFile.isDirectory()) {
+            if (!outDirFile.mkdirs()) {
+                Log.e(LOGTAG, "Couldn't create " + outDir);
+                return false;
+            }
+        }
+
+        final String abi = getCPUABI();
+
+        try {
+            final ZipFile zipFile = new ZipFile(new File(apkPath));
+            try {
+                final String libPath = "lib/" + abi + "/lib" + lib + ".so";
+                final ZipEntry entry = zipFile.getEntry(libPath);
+                if (entry == null) {
+                    Log.w(LOGTAG, libPath + " not found in APK " + apkPath);
+                    return false;
+                }
+
+                final InputStream in = zipFile.getInputStream(entry);
+                try {
+                    final String outPath = outDir + "/lib" + lib + ".so";
+                    final FileOutputStream out = new FileOutputStream(outPath);
+                    final byte[] bytes = new byte[1024];
+                    int read;
+
+                    Log.d(LOGTAG, "Copying " + libPath + " to " + outPath);
+                    boolean failed = false;
+                    try {
+                        while ((read = in.read(bytes, 0, 1024)) != -1) {
+                            out.write(bytes, 0, read);
+                        }
+                    } catch (Exception e) {
+                        Log.w(LOGTAG, "Failing library copy.", e);
+                        failed = true;
+                    } finally {
+                        out.close();
+                    }
+
+                    if (failed) {
+                        // Delete the partial copy so we don't fail to load it.
+                        // Don't bother to check the return value -- there's nothing
+                        // we can do about a failure.
+                        new File(outPath).delete();
+                    } else {
+                        // Mark the file as executable. This doesn't seem to be
+                        // necessary for the loader, but it's the normal state of
+                        // affairs.
+                        Log.d(LOGTAG, "Marking " + outPath + " as executable.");
+                        new File(outPath).setExecutable(true);
+                    }
+
+                    return !failed;
+                } finally {
+                    in.close();
+                }
+            } finally {
+                zipFile.close();
+            }
+        } catch (Exception e) {
+            Log.e(LOGTAG, "Failed to extract lib from APK.", e);
+            return false;
+        }
+    }
+
+    private static String getLoadDiagnostics(final Context context, final String lib) {
+        final String androidPackageName = context.getPackageName();
+
+        final StringBuilder message = new StringBuilder("LOAD ");
+        message.append(lib);
+
+        // These might differ. If so, we know why the library won't load!
+        message.append(": ABI: " + AppConstants.MOZ_APP_ABI + ", " + getCPUABI());
+        message.append(": Data: " + context.getApplicationInfo().dataDir);
+        try {
+            final boolean appLibExists = new File("/data/app-lib/" + androidPackageName + "/lib" + lib + ".so").exists();
+            final boolean dataDataExists = new File("/data/data/" + androidPackageName + "/lib/lib" + lib + ".so").exists();
+            message.append(", ax=" + appLibExists);
+            message.append(", ddx=" + dataDataExists);
+        } catch (Throwable e) {
+            message.append(": ax/ddx fail, ");
+        }
+
+        try {
+            final String dashOne = "/data/data/" + androidPackageName + "-1";
+            final String dashTwo = "/data/data/" + androidPackageName + "-2";
+            final boolean dashOneExists = new File(dashOne).exists();
+            final boolean dashTwoExists = new File(dashTwo).exists();
+            message.append(", -1x=" + dashOneExists);
+            message.append(", -2x=" + dashTwoExists);
+        } catch (Throwable e) {
+            message.append(", dash fail, ");
+        }
+
+        try {
+            if (Build.VERSION.SDK_INT >= 9) {
+                final String nativeLibPath = context.getApplicationInfo().nativeLibraryDir;
+                final boolean nativeLibDirExists = new File(nativeLibPath).exists();
+                final boolean nativeLibLibExists = new File(nativeLibPath + "/lib" + lib + ".so").exists();
+
+                message.append(", nativeLib: " + nativeLibPath);
+                message.append(", dirx=" + nativeLibDirExists);
+                message.append(", libx=" + nativeLibLibExists);
+            } else {
+                message.append(", <pre-9>");
+            }
+        } catch (Throwable e) {
+            message.append(", nativeLib fail.");
+        }
+
+        return message.toString();
+    }
+
+    private static final boolean attemptLoad(final String path) {
+        try {
+            System.load(path);
+            return true;
+        } catch (Throwable e) {
+            Log.wtf(LOGTAG, "Couldn't load " + path + ": " + e);
+        }
+
+        return false;
+    }
+
+    /**
+     * The first two attempts at loading a library: directly, and
+     * then using the app library path.
+     *
+     * Returns null or the cause exception.
+     */
+    private static final Throwable doLoadLibraryExpected(final Context context, final String lib) {
+        try {
+            // Attempt 1: the way that should work.
+            System.loadLibrary(lib);
+            return null;
+        } catch (Throwable e) {
+            Log.wtf(LOGTAG, "Couldn't load " + lib + ". Trying native library dir.");
+
+            if (Build.VERSION.SDK_INT < 9) {
+                // We can't use nativeLibraryDir.
+                return e;
+            }
+
+            // Attempt 2: use nativeLibraryDir, which should also work.
+            final String libDir = context.getApplicationInfo().nativeLibraryDir;
+            final String libPath = libDir + "/lib" + lib + ".so";
+
+            // Does it even exist?
+            if (new File(libPath).exists()) {
+                if (attemptLoad(libPath)) {
+                    // Success!
+                    return null;
+                }
+                Log.wtf(LOGTAG, "Library exists but couldn't load!");
+            } else {
+                Log.wtf(LOGTAG, "Library doesn't exist when it should.");
+            }
+
+            // We failed. Return the original cause.
+            return e;
+        }
+    }
+
+    public static void doLoadLibrary(final Context context, final String lib) {
+        final Throwable e = doLoadLibraryExpected(context, lib);
+        if (e == null) {
+            // Success.
+            return;
+        }
+
+        // If we're in a mismatched UID state (Bug 1042935 Comment 16) there's really
+        // nothing we can do.
+        if (Build.VERSION.SDK_INT >= 9) {
+            final String nativeLibPath = context.getApplicationInfo().nativeLibraryDir;
+            if (nativeLibPath.contains("mismatched_uid")) {
+                throw new RuntimeException("Fatal: mismatched UID: cannot load.");
+            }
+        }
+
+        // Attempt 3: try finding the path the pseudo-supported way using .dataDir.
+        final String dataLibPath = context.getApplicationInfo().dataDir + "/lib/lib" + lib + ".so";
+        if (attemptLoad(dataLibPath)) {
+            return;
+        }
+
+        // Attempt 4: use /data/app-lib directly. This is a last-ditch effort.
+        final String androidPackageName = context.getPackageName();
+        if (attemptLoad("/data/app-lib/" + androidPackageName + "/lib" + lib + ".so")) {
+            return;
+        }
+
+        // Attempt 5: even more optimistic.
+        if (attemptLoad("/data/data/" + androidPackageName + "/lib/lib" + lib + ".so")) {
+            return;
+        }
+
+        // Look in our files directory, copying from the APK first if necessary.
+        final String filesLibDir = context.getFilesDir() + "/lib";
+        final String filesLibPath = filesLibDir + "/lib" + lib + ".so";
+        if (new File(filesLibPath).exists()) {
+            if (attemptLoad(filesLibPath)) {
+                return;
+            }
+        } else {
+            // Try copying.
+            if (extractLibrary(context, lib, filesLibDir)) {
+                // Let's try it!
+                if (attemptLoad(filesLibPath)) {
+                    return;
+                }
+            }
+        }
+
+        // Give up loudly, leaking information to debug the failure.
+        final String message = getLoadDiagnostics(context, lib);
+        Log.e(LOGTAG, "Load diagnostics: " + message);
+
+        // Throw the descriptive message, using the original library load
+        // failure as the cause.
+        throw new RuntimeException(message, e);
+    }
+
+    public static void loadMozGlue(final Context context) {
+        synchronized (sLibLoadingLock) {
+            if (sMozGlueLoaded) {
+                return;
+            }
+            sMozGlueLoaded = true;
+        }
+
+        doLoadLibrary(context, "mozglue");
+    }
+
+    public static void loadGeckoLibs(final Context context, final String apkName) {
+        loadLibsSetup(context);
+        loadGeckoLibsNative(apkName);
+    }
+
+    private static void setupLocaleEnvironment() {
+        putenv("LANG=" + Locale.getDefault().toString());
+        NumberFormat nf = NumberFormat.getInstance();
+        if (nf instanceof DecimalFormat) {
+            DecimalFormat df = (DecimalFormat)nf;
+            DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
+
+            putenv("LOCALE_DECIMAL_POINT=" + dfs.getDecimalSeparator());
+            putenv("LOCALE_THOUSANDS_SEP=" + dfs.getGroupingSeparator());
+            putenv("LOCALE_GROUPING=" + (char)df.getGroupingSize());
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public static class AbortException extends Exception {
+        public AbortException(String msg) {
+            super(msg);
+        }
+    }
+
+    @JNITarget
+    public static void abort(final String msg) {
+        final Thread thread = Thread.currentThread();
+        final Thread.UncaughtExceptionHandler uncaughtHandler =
+            thread.getUncaughtExceptionHandler();
+        if (uncaughtHandler != null) {
+            uncaughtHandler.uncaughtException(thread, new AbortException(msg));
+        }
+    }
+
+    // These methods are implemented in mozglue/android/nsGeckoUtils.cpp
+    private static native void putenv(String map);
+
+    // These methods are implemented in mozglue/android/APKOpen.cpp
+    public static native void nativeRun(String args);
+    private static native void loadGeckoLibsNative(String apkName);
+    private static native void loadSQLiteLibsNative(String apkName);
+    private static native void loadNSSLibsNative(String apkName);
+}
deleted file mode 100644
--- a/mobile/android/base/mozglue/GeckoLoader.java.in
+++ /dev/null
@@ -1,567 +0,0 @@
-//#filter substitution
-/* -*- 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.mozglue;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
-import java.text.NumberFormat;
-import java.util.Locale;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Environment;
-import android.util.Log;
-
-import org.mozilla.gecko.mozglue.ContextUtils.SafeIntent;
-
-public final class GeckoLoader {
-    private static final String LOGTAG = "GeckoLoader";
-
-    // These match AppConstants, but we're built earlier.
-    private static final String ANDROID_PACKAGE_NAME = "@ANDROID_PACKAGE_NAME@";
-    private static final String MOZ_APP_ABI = "@MOZ_APP_ABI@";
-
-    private static volatile SafeIntent sIntent;
-    private static File sCacheFile;
-    private static File sGREDir;
-
-    private static final Object sLibLoadingLock = new Object();
-    // Must hold sLibLoadingLock while accessing the following boolean variables.
-    private static boolean sSQLiteLibsLoaded;
-    private static boolean sNSSLibsLoaded;
-    private static boolean sMozGlueLoaded;
-    private static boolean sLibsSetup;
-
-    private GeckoLoader() {
-        // prevent instantiation
-    }
-
-    public static File getCacheDir(Context context) {
-        if (sCacheFile == null) {
-            sCacheFile = context.getCacheDir();
-        }
-        return sCacheFile;
-    }
-
-    public static File getGREDir(Context context) {
-        if (sGREDir == null) {
-            sGREDir = new File(context.getApplicationInfo().dataDir);
-        }
-        return sGREDir;
-    }
-
-    private static void setupPluginEnvironment(Context context, String[] pluginDirs) {
-        // setup plugin path directories
-        try {
-            // Check to see if plugins were blocked.
-            if (pluginDirs == null) {
-                putenv("MOZ_PLUGINS_BLOCKED=1");
-                putenv("MOZ_PLUGIN_PATH=");
-                return;
-            }
-
-            StringBuilder pluginSearchPath = new StringBuilder();
-            for (int i = 0; i < pluginDirs.length; i++) {
-                pluginSearchPath.append(pluginDirs[i]);
-                pluginSearchPath.append(":");
-            }
-            putenv("MOZ_PLUGIN_PATH="+pluginSearchPath);
-
-            File pluginDataDir = context.getDir("plugins", 0);
-            putenv("ANDROID_PLUGIN_DATADIR=" + pluginDataDir.getPath());
-
-            File pluginPrivateDataDir = context.getDir("plugins_private", 0);
-            putenv("ANDROID_PLUGIN_DATADIR_PRIVATE=" + pluginPrivateDataDir.getPath());
-
-        } catch (Exception ex) {
-            Log.w(LOGTAG, "Caught exception getting plugin dirs.", ex);
-        }
-    }
-
-    private static void setupDownloadEnvironment(final Context context) {
-        try {
-            File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
-            File updatesDir  = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
-            if (downloadDir == null) {
-                downloadDir = new File(Environment.getExternalStorageDirectory().getPath(), "download");
-            }
-            if (updatesDir == null) {
-                updatesDir = downloadDir;
-            }
-            putenv("DOWNLOADS_DIRECTORY=" + downloadDir.getPath());
-            putenv("UPDATES_DIRECTORY="   + updatesDir.getPath());
-        } catch (Exception e) {
-            Log.w(LOGTAG, "No download directory found.", e);
-        }
-    }
-
-    private static void delTree(File file) {
-        if (file.isDirectory()) {
-            File children[] = file.listFiles();
-            for (File child : children) {
-                delTree(child);
-            }
-        }
-        file.delete();
-    }
-
-    private static File getTmpDir(Context context) {
-        File tmpDir = context.getDir("tmpdir", Context.MODE_PRIVATE);
-        // check if the old tmp dir is there
-        File oldDir = new File(tmpDir.getParentFile(), "app_tmp");
-        if (oldDir.exists()) {
-            delTree(oldDir);
-        }
-        return tmpDir;
-    }
-
-    public static void setLastIntent(SafeIntent intent) {
-        sIntent = intent;
-    }
-
-    public static void setupGeckoEnvironment(Context context, String[] pluginDirs, String profilePath) {
-        // if we have an intent (we're being launched by an activity)
-        // read in any environmental variables from it here
-        final SafeIntent intent = sIntent;
-        if (intent != null) {
-            String env = intent.getStringExtra("env0");
-            Log.d(LOGTAG, "Gecko environment env0: " + env);
-            for (int c = 1; env != null; c++) {
-                putenv(env);
-                env = intent.getStringExtra("env" + c);
-                Log.d(LOGTAG, "env" + c + ": " + env);
-            }
-        }
-
-        setupPluginEnvironment(context, pluginDirs);
-        setupDownloadEnvironment(context);
-
-        // profile home path
-        putenv("HOME=" + profilePath);
-
-        // setup the tmp path
-        File f = getTmpDir(context);
-        if (!f.exists()) {
-            f.mkdirs();
-        }
-        putenv("TMPDIR=" + f.getPath());
-
-        // setup the downloads path
-        f = Environment.getDownloadCacheDirectory();
-        putenv("EXTERNAL_STORAGE=" + f.getPath());
-
-        // setup the app-specific cache path
-        f = context.getCacheDir();
-        putenv("CACHE_DIRECTORY=" + f.getPath());
-
-        /* We really want to use this code, but it requires bumping up the SDK to 17 so for now
-           we will use reflection. See https://bugzilla.mozilla.org/show_bug.cgi?id=811763#c11
-
-        if (Build.VERSION.SDK_INT >= 17) {
-            android.os.UserManager um = (android.os.UserManager)context.getSystemService(Context.USER_SERVICE);
-            if (um != null) {
-                putenv("MOZ_ANDROID_USER_SERIAL_NUMBER=" + um.getSerialNumberForUser(android.os.Process.myUserHandle()));
-            } else {
-                Log.d(LOGTAG, "Unable to obtain user manager service on a device with SDK version " + Build.VERSION.SDK_INT);
-            }
-        }
-        */
-        try {
-            Object userManager = context.getSystemService("user");
-            if (userManager != null) {
-                // if userManager is non-null that means we're running on 4.2+ and so the rest of this
-                // should just work
-                Object userHandle = android.os.Process.class.getMethod("myUserHandle", (Class[])null).invoke(null);
-                Object userSerial = userManager.getClass().getMethod("getSerialNumberForUser", userHandle.getClass()).invoke(userManager, userHandle);
-                putenv("MOZ_ANDROID_USER_SERIAL_NUMBER=" + userSerial.toString());
-            }
-        } catch (Exception e) {
-            // Guard against any unexpected failures
-            Log.d(LOGTAG, "Unable to set the user serial number", e);
-        }
-
-        setupLocaleEnvironment();
-
-        // We don't need this any more.
-        sIntent = null;
-    }
-
-    private static void loadLibsSetup(Context context) {
-        synchronized (sLibLoadingLock) {
-            if (sLibsSetup) {
-                return;
-            }
-            sLibsSetup = true;
-        }
-
-        // The package data lib directory isn't placed in ld.so's
-        // search path, so we have to manually load libraries that
-        // libxul will depend on.  Not ideal.
-
-        File cacheFile = getCacheDir(context);
-        putenv("GRE_HOME=" + getGREDir(context).getPath());
-
-        // setup the libs cache
-        String linkerCache = System.getenv("MOZ_LINKER_CACHE");
-        if (linkerCache == null) {
-            linkerCache = cacheFile.getPath();
-            putenv("MOZ_LINKER_CACHE=" + linkerCache);
-        }
-
-        // Disable on-demand decompression of the linker on devices where it
-        // is known to cause crashes.
-        String forced_ondemand = System.getenv("MOZ_LINKER_ONDEMAND");
-        if (forced_ondemand == null) {
-            if ("HTC".equals(android.os.Build.MANUFACTURER) &&
-                "HTC Vision".equals(android.os.Build.MODEL)) {
-                putenv("MOZ_LINKER_ONDEMAND=0");
-            }
-        }
-
-//#ifdef MOZ_LINKER_EXTRACT
-        putenv("MOZ_LINKER_EXTRACT=1");
-        // Ensure that the cache dir is world-writable
-        File cacheDir = new File(linkerCache);
-        if (cacheDir.isDirectory()) {
-            cacheDir.setWritable(true, false);
-            cacheDir.setExecutable(true, false);
-            cacheDir.setReadable(true, false);
-        }
-//#endif
-    }
-
-    @RobocopTarget
-    public static void loadSQLiteLibs(final Context context, final String apkName) {
-        synchronized (sLibLoadingLock) {
-            if (sSQLiteLibsLoaded) {
-                return;
-            }
-            sSQLiteLibsLoaded = true;
-        }
-
-        loadMozGlue(context);
-        loadLibsSetup(context);
-        loadSQLiteLibsNative(apkName);
-    }
-
-    public static void loadNSSLibs(final Context context, final String apkName) {
-        synchronized (sLibLoadingLock) {
-            if (sNSSLibsLoaded) {
-                return;
-            }
-            sNSSLibsLoaded = true;
-        }
-
-        loadMozGlue(context);
-        loadLibsSetup(context);
-        loadNSSLibsNative(apkName);
-    }
-
-    @SuppressWarnings("deprecation")
-    private static final String getCPUABI() {
-        return android.os.Build.CPU_ABI;
-    }
-
-    /**
-     * Copy a library out of our APK.
-     *
-     * @param context a Context.
-     * @param lib the name of the library; e.g., "mozglue".
-     * @param outDir the output directory for the .so. No trailing slash.
-     * @return true on success, false on failure.
-     */
-    private static boolean extractLibrary(final Context context, final String lib, final String outDir) {
-        final String apkPath = context.getApplicationInfo().sourceDir;
-
-        // Sanity check.
-        if (!apkPath.endsWith(".apk")) {
-            Log.w(LOGTAG, "sourceDir is not an APK.");
-            return false;
-        }
-
-        // Try to extract the named library from the APK.
-        File outDirFile = new File(outDir);
-        if (!outDirFile.isDirectory()) {
-            if (!outDirFile.mkdirs()) {
-                Log.e(LOGTAG, "Couldn't create " + outDir);
-                return false;
-            }
-        }
-
-        final String abi = getCPUABI();
-
-        try {
-            final ZipFile zipFile = new ZipFile(new File(apkPath));
-            try {
-                final String libPath = "lib/" + abi + "/lib" + lib + ".so";
-                final ZipEntry entry = zipFile.getEntry(libPath);
-                if (entry == null) {
-                    Log.w(LOGTAG, libPath + " not found in APK " + apkPath);
-                    return false;
-                }
-
-                final InputStream in = zipFile.getInputStream(entry);
-                try {
-                    final String outPath = outDir + "/lib" + lib + ".so";
-                    final FileOutputStream out = new FileOutputStream(outPath);
-                    final byte[] bytes = new byte[1024];
-                    int read;
-
-                    Log.d(LOGTAG, "Copying " + libPath + " to " + outPath);
-                    boolean failed = false;
-                    try {
-                        while ((read = in.read(bytes, 0, 1024)) != -1) {
-                            out.write(bytes, 0, read);
-                        }
-                    } catch (Exception e) {
-                        Log.w(LOGTAG, "Failing library copy.", e);
-                        failed = true;
-                    } finally {
-                        out.close();
-                    }
-
-                    if (failed) {
-                        // Delete the partial copy so we don't fail to load it.
-                        // Don't bother to check the return value -- there's nothing
-                        // we can do about a failure.
-                        new File(outPath).delete();
-                    } else {
-                        // Mark the file as executable. This doesn't seem to be
-                        // necessary for the loader, but it's the normal state of
-                        // affairs.
-                        Log.d(LOGTAG, "Marking " + outPath + " as executable.");
-                        new File(outPath).setExecutable(true);
-                    }
-
-                    return !failed;
-                } finally {
-                    in.close();
-                }
-            } finally {
-                zipFile.close();
-            }
-        } catch (Exception e) {
-            Log.e(LOGTAG, "Failed to extract lib from APK.", e);
-            return false;
-        }
-    }
-
-    private static String getLoadDiagnostics(final Context context, final String lib) {
-        final StringBuilder message = new StringBuilder("LOAD ");
-        message.append(lib);
-
-        // These might differ. If so, we know why the library won't load!
-        message.append(": ABI: " + MOZ_APP_ABI + ", " + getCPUABI());
-        message.append(": Data: " + context.getApplicationInfo().dataDir);
-        try {
-            final boolean appLibExists = new File("/data/app-lib/" + ANDROID_PACKAGE_NAME + "/lib" + lib + ".so").exists();
-            final boolean dataDataExists = new File("/data/data/" + ANDROID_PACKAGE_NAME + "/lib/lib" + lib + ".so").exists();
-            message.append(", ax=" + appLibExists);
-            message.append(", ddx=" + dataDataExists);
-        } catch (Throwable e) {
-            message.append(": ax/ddx fail, ");
-        }
-
-        try {
-            final String dashOne = "/data/data/" + ANDROID_PACKAGE_NAME + "-1";
-            final String dashTwo = "/data/data/" + ANDROID_PACKAGE_NAME + "-2";
-            final boolean dashOneExists = new File(dashOne).exists();
-            final boolean dashTwoExists = new File(dashTwo).exists();
-            message.append(", -1x=" + dashOneExists);
-            message.append(", -2x=" + dashTwoExists);
-        } catch (Throwable e) {
-            message.append(", dash fail, ");
-        }
-
-        try {
-            if (Build.VERSION.SDK_INT >= 9) {
-                final String nativeLibPath = context.getApplicationInfo().nativeLibraryDir;
-                final boolean nativeLibDirExists = new File(nativeLibPath).exists();
-                final boolean nativeLibLibExists = new File(nativeLibPath + "/lib" + lib + ".so").exists();
-
-                message.append(", nativeLib: " + nativeLibPath);
-                message.append(", dirx=" + nativeLibDirExists);
-                message.append(", libx=" + nativeLibLibExists);
-            } else {
-                message.append(", <pre-9>");
-            }
-        } catch (Throwable e) {
-            message.append(", nativeLib fail.");
-        }
-
-        return message.toString();
-    }
-
-    private static final boolean attemptLoad(final String path) {
-        try {
-            System.load(path);
-            return true;
-        } catch (Throwable e) {
-            Log.wtf(LOGTAG, "Couldn't load " + path + ": " + e);
-        }
-
-        return false;
-    }
-
-    /**
-     * The first two attempts at loading a library: directly, and
-     * then using the app library path.
-     *
-     * Returns null or the cause exception.
-     */
-    private static final Throwable doLoadLibraryExpected(final Context context, final String lib) {
-        try {
-            // Attempt 1: the way that should work.
-            System.loadLibrary(lib);
-            return null;
-        } catch (Throwable e) {
-            Log.wtf(LOGTAG, "Couldn't load " + lib + ". Trying native library dir.");
-
-            if (Build.VERSION.SDK_INT < 9) {
-                // We can't use nativeLibraryDir.
-                return e;
-            }
-
-            // Attempt 2: use nativeLibraryDir, which should also work.
-            final String libDir = context.getApplicationInfo().nativeLibraryDir;
-            final String libPath = libDir + "/lib" + lib + ".so";
-
-            // Does it even exist?
-            if (new File(libPath).exists()) {
-                if (attemptLoad(libPath)) {
-                    // Success!
-                    return null;
-                }
-                Log.wtf(LOGTAG, "Library exists but couldn't load!");
-            } else {
-                Log.wtf(LOGTAG, "Library doesn't exist when it should.");
-            }
-
-            // We failed. Return the original cause.
-            return e;
-        }
-    }
-
-    public static void doLoadLibrary(final Context context, final String lib) {
-        final Throwable e = doLoadLibraryExpected(context, lib);
-        if (e == null) {
-            // Success.
-            return;
-        }
-
-        // If we're in a mismatched UID state (Bug 1042935 Comment 16) there's really
-        // nothing we can do.
-        if (Build.VERSION.SDK_INT >= 9) {
-            final String nativeLibPath = context.getApplicationInfo().nativeLibraryDir;
-            if (nativeLibPath.contains("mismatched_uid")) {
-                throw new RuntimeException("Fatal: mismatched UID: cannot load.");
-            }
-        }
-
-        // Attempt 3: try finding the path the pseudo-supported way using .dataDir.
-        final String dataLibPath = context.getApplicationInfo().dataDir + "/lib/lib" + lib + ".so";
-        if (attemptLoad(dataLibPath)) {
-            return;
-        }
-
-        // Attempt 4: use /data/app-lib directly. This is a last-ditch effort.
-        if (attemptLoad("/data/app-lib/" + ANDROID_PACKAGE_NAME + "/lib" + lib + ".so")) {
-            return;
-        }
-
-        // Attempt 5: even more optimistic.
-        if (attemptLoad("/data/data/" + ANDROID_PACKAGE_NAME + "/lib/lib" + lib + ".so")) {
-            return;
-        }
-
-        // Look in our files directory, copying from the APK first if necessary.
-        final String filesLibDir = context.getFilesDir() + "/lib";
-        final String filesLibPath = filesLibDir + "/lib" + lib + ".so";
-        if (new File(filesLibPath).exists()) {
-            if (attemptLoad(filesLibPath)) {
-                return;
-            }
-        } else {
-            // Try copying.
-            if (extractLibrary(context, lib, filesLibDir)) {
-                // Let's try it!
-                if (attemptLoad(filesLibPath)) {
-                    return;
-                }
-            }
-        }
-
-        // Give up loudly, leaking information to debug the failure.
-        final String message = getLoadDiagnostics(context, lib);
-        Log.e(LOGTAG, "Load diagnostics: " + message);
-
-        // Throw the descriptive message, using the original library load
-        // failure as the cause.
-        throw new RuntimeException(message, e);
-    }
-
-    public static void loadMozGlue(final Context context) {
-        synchronized (sLibLoadingLock) {
-            if (sMozGlueLoaded) {
-                return;
-            }
-            sMozGlueLoaded = true;
-        }
-
-        doLoadLibrary(context, "mozglue");
-    }
-
-    public static void loadGeckoLibs(final Context context, final String apkName) {
-        loadLibsSetup(context);
-        loadGeckoLibsNative(apkName);
-    }
-
-    private static void setupLocaleEnvironment() {
-        putenv("LANG=" + Locale.getDefault().toString());
-        NumberFormat nf = NumberFormat.getInstance();
-        if (nf instanceof DecimalFormat) {
-            DecimalFormat df = (DecimalFormat)nf;
-            DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
-
-            putenv("LOCALE_DECIMAL_POINT=" + dfs.getDecimalSeparator());
-            putenv("LOCALE_THOUSANDS_SEP=" + dfs.getGroupingSeparator());
-            putenv("LOCALE_GROUPING=" + (char)df.getGroupingSize());
-        }
-    }
-
-    @SuppressWarnings("serial")
-    public static class AbortException extends Exception {
-        public AbortException(String msg) {
-            super(msg);
-        }
-    }
-
-    @JNITarget
-    public static void abort(final String msg) {
-        final Thread thread = Thread.currentThread();
-        final Thread.UncaughtExceptionHandler uncaughtHandler =
-            thread.getUncaughtExceptionHandler();
-        if (uncaughtHandler != null) {
-            uncaughtHandler.uncaughtException(thread, new AbortException(msg));
-        }
-    }
-
-    // These methods are implemented in mozglue/android/nsGeckoUtils.cpp
-    private static native void putenv(String map);
-
-    // These methods are implemented in mozglue/android/APKOpen.cpp
-    public static native void nativeRun(String args);
-    private static native void loadGeckoLibsNative(String apkName);
-    private static native void loadSQLiteLibsNative(String apkName);
-    private static native void loadNSSLibsNative(String apkName);
-}