michael@0: #filter substitution michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.mozglue; michael@0: michael@0: import android.content.Context; michael@0: import android.content.Intent; michael@0: import android.os.Build; michael@0: import android.os.Environment; michael@0: import android.util.Log; michael@0: michael@0: import java.io.File; michael@0: import java.text.DecimalFormat; michael@0: import java.text.DecimalFormatSymbols; michael@0: import java.text.NumberFormat; michael@0: import java.util.Locale; michael@0: michael@0: public final class GeckoLoader { michael@0: private static final String LOGTAG = "GeckoLoader"; michael@0: michael@0: // This matches AppConstants, but we're built earlier. michael@0: private static final String ANDROID_PACKAGE_NAME = "@ANDROID_PACKAGE_NAME@"; michael@0: michael@0: private static volatile Intent sIntent; michael@0: private static File sCacheFile; michael@0: private static File sGREDir; michael@0: michael@0: private static final Object sLibLoadingLock = new Object(); michael@0: // Must hold sLibLoadingLock while accessing the following boolean variables. michael@0: private static boolean sSQLiteLibsLoaded; michael@0: private static boolean sNSSLibsLoaded; michael@0: private static boolean sMozGlueLoaded; michael@0: private static boolean sLibsSetup; michael@0: michael@0: private GeckoLoader() { michael@0: // prevent instantiation michael@0: } michael@0: michael@0: public static File getCacheDir(Context context) { michael@0: if (sCacheFile == null) { michael@0: sCacheFile = context.getCacheDir(); michael@0: } michael@0: return sCacheFile; michael@0: } michael@0: michael@0: public static File getGREDir(Context context) { michael@0: if (sGREDir == null) { michael@0: sGREDir = new File(context.getApplicationInfo().dataDir); michael@0: } michael@0: return sGREDir; michael@0: } michael@0: michael@0: private static void setupPluginEnvironment(Context context, String[] pluginDirs) { michael@0: // setup plugin path directories michael@0: try { michael@0: // Check to see if plugins were blocked. michael@0: if (pluginDirs == null) { michael@0: putenv("MOZ_PLUGINS_BLOCKED=1"); michael@0: putenv("MOZ_PLUGIN_PATH="); michael@0: return; michael@0: } michael@0: michael@0: StringBuilder pluginSearchPath = new StringBuilder(); michael@0: for (int i = 0; i < pluginDirs.length; i++) { michael@0: pluginSearchPath.append(pluginDirs[i]); michael@0: pluginSearchPath.append(":"); michael@0: } michael@0: putenv("MOZ_PLUGIN_PATH="+pluginSearchPath); michael@0: michael@0: File pluginDataDir = context.getDir("plugins", 0); michael@0: putenv("ANDROID_PLUGIN_DATADIR=" + pluginDataDir.getPath()); michael@0: michael@0: File pluginPrivateDataDir = context.getDir("plugins_private", 0); michael@0: putenv("ANDROID_PLUGIN_DATADIR_PRIVATE=" + pluginPrivateDataDir.getPath()); michael@0: michael@0: } catch (Exception ex) { michael@0: Log.w(LOGTAG, "Caught exception getting plugin dirs.", ex); michael@0: } michael@0: } michael@0: michael@0: private static void setupDownloadEnvironment(Context context) { michael@0: try { michael@0: File downloadDir = null; michael@0: File updatesDir = null; michael@0: if (Build.VERSION.SDK_INT >= 8) { michael@0: downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); michael@0: updatesDir = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); michael@0: } michael@0: if (downloadDir == null) { michael@0: downloadDir = new File(Environment.getExternalStorageDirectory().getPath(), "download"); michael@0: } michael@0: if (updatesDir == null) { michael@0: updatesDir = downloadDir; michael@0: } michael@0: putenv("DOWNLOADS_DIRECTORY=" + downloadDir.getPath()); michael@0: putenv("UPDATES_DIRECTORY=" + updatesDir.getPath()); michael@0: } michael@0: catch (Exception e) { michael@0: Log.w(LOGTAG, "No download directory found.", e); michael@0: } michael@0: } michael@0: michael@0: private static void delTree(File file) { michael@0: if (file.isDirectory()) { michael@0: File children[] = file.listFiles(); michael@0: for (File child : children) { michael@0: delTree(child); michael@0: } michael@0: } michael@0: file.delete(); michael@0: } michael@0: michael@0: private static File getTmpDir(Context context) { michael@0: File tmpDir = context.getDir("tmpdir", Context.MODE_PRIVATE); michael@0: // check if the old tmp dir is there michael@0: File oldDir = new File(tmpDir.getParentFile(), "app_tmp"); michael@0: if (oldDir.exists()) { michael@0: delTree(oldDir); michael@0: } michael@0: return tmpDir; michael@0: } michael@0: michael@0: public static void setLastIntent(Intent intent) { michael@0: sIntent = intent; michael@0: } michael@0: michael@0: public static void setupGeckoEnvironment(Context context, String[] pluginDirs, String profilePath) { michael@0: // if we have an intent (we're being launched by an activity) michael@0: // read in any environmental variables from it here michael@0: final Intent intent = sIntent; michael@0: if (intent != null) { michael@0: String env = intent.getStringExtra("env0"); michael@0: Log.d(LOGTAG, "Gecko environment env0: " + env); michael@0: for (int c = 1; env != null; c++) { michael@0: putenv(env); michael@0: env = intent.getStringExtra("env" + c); michael@0: Log.d(LOGTAG, "env" + c + ": " + env); michael@0: } michael@0: } michael@0: michael@0: setupPluginEnvironment(context, pluginDirs); michael@0: setupDownloadEnvironment(context); michael@0: michael@0: // profile home path michael@0: putenv("HOME=" + profilePath); michael@0: michael@0: // setup the tmp path michael@0: File f = getTmpDir(context); michael@0: if (!f.exists()) { michael@0: f.mkdirs(); michael@0: } michael@0: putenv("TMPDIR=" + f.getPath()); michael@0: michael@0: // setup the downloads path michael@0: f = Environment.getDownloadCacheDirectory(); michael@0: putenv("EXTERNAL_STORAGE=" + f.getPath()); michael@0: michael@0: // setup the app-specific cache path michael@0: f = context.getCacheDir(); michael@0: putenv("CACHE_DIRECTORY=" + f.getPath()); michael@0: michael@0: /* We really want to use this code, but it requires bumping up the SDK to 17 so for now michael@0: we will use reflection. See https://bugzilla.mozilla.org/show_bug.cgi?id=811763#c11 michael@0: michael@0: if (Build.VERSION.SDK_INT >= 17) { michael@0: android.os.UserManager um = (android.os.UserManager)context.getSystemService(Context.USER_SERVICE); michael@0: if (um != null) { michael@0: putenv("MOZ_ANDROID_USER_SERIAL_NUMBER=" + um.getSerialNumberForUser(android.os.Process.myUserHandle())); michael@0: } else { michael@0: Log.d(LOGTAG, "Unable to obtain user manager service on a device with SDK version " + Build.VERSION.SDK_INT); michael@0: } michael@0: } michael@0: */ michael@0: try { michael@0: Object userManager = context.getSystemService("user"); michael@0: if (userManager != null) { michael@0: // if userManager is non-null that means we're running on 4.2+ and so the rest of this michael@0: // should just work michael@0: Object userHandle = android.os.Process.class.getMethod("myUserHandle", (Class[])null).invoke(null); michael@0: Object userSerial = userManager.getClass().getMethod("getSerialNumberForUser", userHandle.getClass()).invoke(userManager, userHandle); michael@0: putenv("MOZ_ANDROID_USER_SERIAL_NUMBER=" + userSerial.toString()); michael@0: } michael@0: } catch (Exception e) { michael@0: // Guard against any unexpected failures michael@0: Log.d(LOGTAG, "Unable to set the user serial number", e); michael@0: } michael@0: michael@0: setupLocaleEnvironment(); michael@0: michael@0: // We don't need this any more. michael@0: sIntent = null; michael@0: } michael@0: michael@0: private static void loadLibsSetup(Context context) { michael@0: synchronized (sLibLoadingLock) { michael@0: if (sLibsSetup) { michael@0: return; michael@0: } michael@0: sLibsSetup = true; michael@0: } michael@0: michael@0: // The package data lib directory isn't placed in ld.so's michael@0: // search path, so we have to manually load libraries that michael@0: // libxul will depend on. Not ideal. michael@0: michael@0: File cacheFile = getCacheDir(context); michael@0: putenv("GRE_HOME=" + getGREDir(context).getPath()); michael@0: michael@0: // setup the libs cache michael@0: String linkerCache = System.getenv("MOZ_LINKER_CACHE"); michael@0: if (linkerCache == null) { michael@0: linkerCache = cacheFile.getPath(); michael@0: putenv("MOZ_LINKER_CACHE=" + linkerCache); michael@0: } michael@0: michael@0: // Disable on-demand decompression of the linker on devices where it michael@0: // is known to cause crashes. michael@0: if ("HTC".equals(android.os.Build.MANUFACTURER) && michael@0: "HTC Vision".equals(android.os.Build.MODEL)) { michael@0: putenv("MOZ_LINKER_ONDEMAND=0"); michael@0: } michael@0: michael@0: #ifdef MOZ_LINKER_EXTRACT michael@0: putenv("MOZ_LINKER_EXTRACT=1"); michael@0: // Ensure that the cache dir is world-writable michael@0: File cacheDir = new File(linkerCache); michael@0: if (cacheDir.isDirectory()) { michael@0: cacheDir.setWritable(true, false); michael@0: cacheDir.setExecutable(true, false); michael@0: cacheDir.setReadable(true, false); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: @RobocopTarget michael@0: public static void loadSQLiteLibs(Context context, String apkName) { michael@0: synchronized (sLibLoadingLock) { michael@0: if (sSQLiteLibsLoaded) { michael@0: return; michael@0: } michael@0: sSQLiteLibsLoaded = true; michael@0: } michael@0: michael@0: loadMozGlue(); michael@0: // the extract libs parameter is being removed in bug 732069 michael@0: loadLibsSetup(context); michael@0: loadSQLiteLibsNative(apkName, false); michael@0: } michael@0: michael@0: public static void loadNSSLibs(Context context, String apkName) { michael@0: synchronized (sLibLoadingLock) { michael@0: if (sNSSLibsLoaded) { michael@0: return; michael@0: } michael@0: sNSSLibsLoaded = true; michael@0: } michael@0: michael@0: loadMozGlue(); michael@0: loadLibsSetup(context); michael@0: loadNSSLibsNative(apkName, false); michael@0: } michael@0: michael@0: public static void doLoadLibrary(final String lib) { michael@0: try { michael@0: System.loadLibrary(lib); michael@0: } catch (UnsatisfiedLinkError e) { michael@0: Log.wtf(LOGTAG, "Couldn't load " + lib + ". Trying /data/app-lib path."); michael@0: try { michael@0: System.load("/data/app-lib/" + ANDROID_PACKAGE_NAME + "/lib" + lib + ".so"); michael@0: } catch (Throwable ee) { michael@0: try { michael@0: Log.wtf(LOGTAG, "Couldn't load " + lib + ": " + ee + ". Trying /data/data path."); michael@0: System.load("/data/data/" + ANDROID_PACKAGE_NAME + "/lib/lib" + lib + ".so"); michael@0: } catch (Throwable eee) { michael@0: Log.wtf(LOGTAG, "Failed every attempt to load " + lib + ". Giving up."); michael@0: throw new RuntimeException("Unable to load " + lib, eee); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: public static void loadMozGlue() { michael@0: synchronized (sLibLoadingLock) { michael@0: if (sMozGlueLoaded) { michael@0: return; michael@0: } michael@0: sMozGlueLoaded = true; michael@0: } michael@0: michael@0: doLoadLibrary("mozglue"); michael@0: } michael@0: michael@0: public static void loadGeckoLibs(Context context, String apkName) { michael@0: loadLibsSetup(context); michael@0: loadGeckoLibsNative(apkName); michael@0: } michael@0: michael@0: private static void setupLocaleEnvironment() { michael@0: putenv("LANG=" + Locale.getDefault().toString()); michael@0: NumberFormat nf = NumberFormat.getInstance(); michael@0: if (nf instanceof DecimalFormat) { michael@0: DecimalFormat df = (DecimalFormat)nf; michael@0: DecimalFormatSymbols dfs = df.getDecimalFormatSymbols(); michael@0: michael@0: putenv("LOCALE_DECIMAL_POINT=" + dfs.getDecimalSeparator()); michael@0: putenv("LOCALE_THOUSANDS_SEP=" + dfs.getGroupingSeparator()); michael@0: putenv("LOCALE_GROUPING=" + (char)df.getGroupingSize()); michael@0: } michael@0: } michael@0: michael@0: // These methods are implemented in mozglue/android/nsGeckoUtils.cpp michael@0: private static native void putenv(String map); michael@0: michael@0: // These methods are implemented in mozglue/android/APKOpen.cpp michael@0: public static native void nativeRun(String args); michael@0: private static native void loadGeckoLibsNative(String apkName); michael@0: private static native void loadSQLiteLibsNative(String apkName, boolean shouldExtract); michael@0: private static native void loadNSSLibsNative(String apkName, boolean shouldExtract); michael@0: }