gecko-dev/mobile/android/base/mozglue/GeckoLoader.java.in

419 lines
15 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 android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Locale;
public final class GeckoLoader {
private static final String LOGTAG = "GeckoLoader";
// This matches AppConstants, but we're built earlier.
private static final String ANDROID_PACKAGE_NAME = "@ANDROID_PACKAGE_NAME@";
private static volatile Intent 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(Intent 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 Intent 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, false);
}
public static void loadNSSLibs(final Context context, final String apkName) {
synchronized (sLibLoadingLock) {
if (sNSSLibsLoaded) {
return;
}
sNSSLibsLoaded = true;
}
loadMozGlue(context);
loadLibsSetup(context);
loadNSSLibsNative(apkName, false);
}
private static String getLoadDiagnostics(final Context context, final String lib) {
final StringBuilder message = new StringBuilder("LOAD ");
message.append(lib);
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;
if (attemptLoad(libDir + "/lib" + lib + ".so")) {
// Success!
return null;
}
// 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;
}
// 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;
}
// 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());
}
}
// 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, boolean shouldExtract);
private static native void loadNSSLibsNative(String apkName, boolean shouldExtract);
}