mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-31 06:05:44 +00:00
568 lines
20 KiB
Java
568 lines
20 KiB
Java
//#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);
|
|
}
|