/* -*- 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.app.Activity; 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"; 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(Context context) { try { File downloadDir = null; File updatesDir = null; if (Build.VERSION.SDK_INT >= 8) { downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); 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 setupGeckoEnvironment(Activity 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 Intent intent = context.getIntent(); 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(); } 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. 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(Context context, String apkName) { synchronized (sLibLoadingLock) { if (sSQLiteLibsLoaded) { return; } sSQLiteLibsLoaded = true; } loadMozGlue(); // the extract libs parameter is being removed in bug 732069 loadLibsSetup(context); loadSQLiteLibsNative(apkName, false); } public static void loadNSSLibs(Context context, String apkName) { synchronized (sLibLoadingLock) { if (sNSSLibsLoaded) { return; } sNSSLibsLoaded = true; } loadMozGlue(); loadLibsSetup(context); loadNSSLibsNative(apkName, false); } public static void loadMozGlue() { synchronized (sLibLoadingLock) { if (sMozGlueLoaded) { return; } sMozGlueLoaded = true; } System.loadLibrary("mozglue"); } public static void loadGeckoLibs(Context context, 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); }