Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
michael@0 | 1 | #filter substitution |
michael@0 | 2 | /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- |
michael@0 | 3 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | package org.mozilla.gecko.mozglue; |
michael@0 | 8 | |
michael@0 | 9 | import android.content.Context; |
michael@0 | 10 | import android.content.Intent; |
michael@0 | 11 | import android.os.Build; |
michael@0 | 12 | import android.os.Environment; |
michael@0 | 13 | import android.util.Log; |
michael@0 | 14 | |
michael@0 | 15 | import java.io.File; |
michael@0 | 16 | import java.text.DecimalFormat; |
michael@0 | 17 | import java.text.DecimalFormatSymbols; |
michael@0 | 18 | import java.text.NumberFormat; |
michael@0 | 19 | import java.util.Locale; |
michael@0 | 20 | |
michael@0 | 21 | public final class GeckoLoader { |
michael@0 | 22 | private static final String LOGTAG = "GeckoLoader"; |
michael@0 | 23 | |
michael@0 | 24 | // This matches AppConstants, but we're built earlier. |
michael@0 | 25 | private static final String ANDROID_PACKAGE_NAME = "@ANDROID_PACKAGE_NAME@"; |
michael@0 | 26 | |
michael@0 | 27 | private static volatile Intent sIntent; |
michael@0 | 28 | private static File sCacheFile; |
michael@0 | 29 | private static File sGREDir; |
michael@0 | 30 | |
michael@0 | 31 | private static final Object sLibLoadingLock = new Object(); |
michael@0 | 32 | // Must hold sLibLoadingLock while accessing the following boolean variables. |
michael@0 | 33 | private static boolean sSQLiteLibsLoaded; |
michael@0 | 34 | private static boolean sNSSLibsLoaded; |
michael@0 | 35 | private static boolean sMozGlueLoaded; |
michael@0 | 36 | private static boolean sLibsSetup; |
michael@0 | 37 | |
michael@0 | 38 | private GeckoLoader() { |
michael@0 | 39 | // prevent instantiation |
michael@0 | 40 | } |
michael@0 | 41 | |
michael@0 | 42 | public static File getCacheDir(Context context) { |
michael@0 | 43 | if (sCacheFile == null) { |
michael@0 | 44 | sCacheFile = context.getCacheDir(); |
michael@0 | 45 | } |
michael@0 | 46 | return sCacheFile; |
michael@0 | 47 | } |
michael@0 | 48 | |
michael@0 | 49 | public static File getGREDir(Context context) { |
michael@0 | 50 | if (sGREDir == null) { |
michael@0 | 51 | sGREDir = new File(context.getApplicationInfo().dataDir); |
michael@0 | 52 | } |
michael@0 | 53 | return sGREDir; |
michael@0 | 54 | } |
michael@0 | 55 | |
michael@0 | 56 | private static void setupPluginEnvironment(Context context, String[] pluginDirs) { |
michael@0 | 57 | // setup plugin path directories |
michael@0 | 58 | try { |
michael@0 | 59 | // Check to see if plugins were blocked. |
michael@0 | 60 | if (pluginDirs == null) { |
michael@0 | 61 | putenv("MOZ_PLUGINS_BLOCKED=1"); |
michael@0 | 62 | putenv("MOZ_PLUGIN_PATH="); |
michael@0 | 63 | return; |
michael@0 | 64 | } |
michael@0 | 65 | |
michael@0 | 66 | StringBuilder pluginSearchPath = new StringBuilder(); |
michael@0 | 67 | for (int i = 0; i < pluginDirs.length; i++) { |
michael@0 | 68 | pluginSearchPath.append(pluginDirs[i]); |
michael@0 | 69 | pluginSearchPath.append(":"); |
michael@0 | 70 | } |
michael@0 | 71 | putenv("MOZ_PLUGIN_PATH="+pluginSearchPath); |
michael@0 | 72 | |
michael@0 | 73 | File pluginDataDir = context.getDir("plugins", 0); |
michael@0 | 74 | putenv("ANDROID_PLUGIN_DATADIR=" + pluginDataDir.getPath()); |
michael@0 | 75 | |
michael@0 | 76 | File pluginPrivateDataDir = context.getDir("plugins_private", 0); |
michael@0 | 77 | putenv("ANDROID_PLUGIN_DATADIR_PRIVATE=" + pluginPrivateDataDir.getPath()); |
michael@0 | 78 | |
michael@0 | 79 | } catch (Exception ex) { |
michael@0 | 80 | Log.w(LOGTAG, "Caught exception getting plugin dirs.", ex); |
michael@0 | 81 | } |
michael@0 | 82 | } |
michael@0 | 83 | |
michael@0 | 84 | private static void setupDownloadEnvironment(Context context) { |
michael@0 | 85 | try { |
michael@0 | 86 | File downloadDir = null; |
michael@0 | 87 | File updatesDir = null; |
michael@0 | 88 | if (Build.VERSION.SDK_INT >= 8) { |
michael@0 | 89 | downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); |
michael@0 | 90 | updatesDir = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); |
michael@0 | 91 | } |
michael@0 | 92 | if (downloadDir == null) { |
michael@0 | 93 | downloadDir = new File(Environment.getExternalStorageDirectory().getPath(), "download"); |
michael@0 | 94 | } |
michael@0 | 95 | if (updatesDir == null) { |
michael@0 | 96 | updatesDir = downloadDir; |
michael@0 | 97 | } |
michael@0 | 98 | putenv("DOWNLOADS_DIRECTORY=" + downloadDir.getPath()); |
michael@0 | 99 | putenv("UPDATES_DIRECTORY=" + updatesDir.getPath()); |
michael@0 | 100 | } |
michael@0 | 101 | catch (Exception e) { |
michael@0 | 102 | Log.w(LOGTAG, "No download directory found.", e); |
michael@0 | 103 | } |
michael@0 | 104 | } |
michael@0 | 105 | |
michael@0 | 106 | private static void delTree(File file) { |
michael@0 | 107 | if (file.isDirectory()) { |
michael@0 | 108 | File children[] = file.listFiles(); |
michael@0 | 109 | for (File child : children) { |
michael@0 | 110 | delTree(child); |
michael@0 | 111 | } |
michael@0 | 112 | } |
michael@0 | 113 | file.delete(); |
michael@0 | 114 | } |
michael@0 | 115 | |
michael@0 | 116 | private static File getTmpDir(Context context) { |
michael@0 | 117 | File tmpDir = context.getDir("tmpdir", Context.MODE_PRIVATE); |
michael@0 | 118 | // check if the old tmp dir is there |
michael@0 | 119 | File oldDir = new File(tmpDir.getParentFile(), "app_tmp"); |
michael@0 | 120 | if (oldDir.exists()) { |
michael@0 | 121 | delTree(oldDir); |
michael@0 | 122 | } |
michael@0 | 123 | return tmpDir; |
michael@0 | 124 | } |
michael@0 | 125 | |
michael@0 | 126 | public static void setLastIntent(Intent intent) { |
michael@0 | 127 | sIntent = intent; |
michael@0 | 128 | } |
michael@0 | 129 | |
michael@0 | 130 | public static void setupGeckoEnvironment(Context context, String[] pluginDirs, String profilePath) { |
michael@0 | 131 | // if we have an intent (we're being launched by an activity) |
michael@0 | 132 | // read in any environmental variables from it here |
michael@0 | 133 | final Intent intent = sIntent; |
michael@0 | 134 | if (intent != null) { |
michael@0 | 135 | String env = intent.getStringExtra("env0"); |
michael@0 | 136 | Log.d(LOGTAG, "Gecko environment env0: " + env); |
michael@0 | 137 | for (int c = 1; env != null; c++) { |
michael@0 | 138 | putenv(env); |
michael@0 | 139 | env = intent.getStringExtra("env" + c); |
michael@0 | 140 | Log.d(LOGTAG, "env" + c + ": " + env); |
michael@0 | 141 | } |
michael@0 | 142 | } |
michael@0 | 143 | |
michael@0 | 144 | setupPluginEnvironment(context, pluginDirs); |
michael@0 | 145 | setupDownloadEnvironment(context); |
michael@0 | 146 | |
michael@0 | 147 | // profile home path |
michael@0 | 148 | putenv("HOME=" + profilePath); |
michael@0 | 149 | |
michael@0 | 150 | // setup the tmp path |
michael@0 | 151 | File f = getTmpDir(context); |
michael@0 | 152 | if (!f.exists()) { |
michael@0 | 153 | f.mkdirs(); |
michael@0 | 154 | } |
michael@0 | 155 | putenv("TMPDIR=" + f.getPath()); |
michael@0 | 156 | |
michael@0 | 157 | // setup the downloads path |
michael@0 | 158 | f = Environment.getDownloadCacheDirectory(); |
michael@0 | 159 | putenv("EXTERNAL_STORAGE=" + f.getPath()); |
michael@0 | 160 | |
michael@0 | 161 | // setup the app-specific cache path |
michael@0 | 162 | f = context.getCacheDir(); |
michael@0 | 163 | putenv("CACHE_DIRECTORY=" + f.getPath()); |
michael@0 | 164 | |
michael@0 | 165 | /* We really want to use this code, but it requires bumping up the SDK to 17 so for now |
michael@0 | 166 | we will use reflection. See https://bugzilla.mozilla.org/show_bug.cgi?id=811763#c11 |
michael@0 | 167 | |
michael@0 | 168 | if (Build.VERSION.SDK_INT >= 17) { |
michael@0 | 169 | android.os.UserManager um = (android.os.UserManager)context.getSystemService(Context.USER_SERVICE); |
michael@0 | 170 | if (um != null) { |
michael@0 | 171 | putenv("MOZ_ANDROID_USER_SERIAL_NUMBER=" + um.getSerialNumberForUser(android.os.Process.myUserHandle())); |
michael@0 | 172 | } else { |
michael@0 | 173 | Log.d(LOGTAG, "Unable to obtain user manager service on a device with SDK version " + Build.VERSION.SDK_INT); |
michael@0 | 174 | } |
michael@0 | 175 | } |
michael@0 | 176 | */ |
michael@0 | 177 | try { |
michael@0 | 178 | Object userManager = context.getSystemService("user"); |
michael@0 | 179 | if (userManager != null) { |
michael@0 | 180 | // if userManager is non-null that means we're running on 4.2+ and so the rest of this |
michael@0 | 181 | // should just work |
michael@0 | 182 | Object userHandle = android.os.Process.class.getMethod("myUserHandle", (Class[])null).invoke(null); |
michael@0 | 183 | Object userSerial = userManager.getClass().getMethod("getSerialNumberForUser", userHandle.getClass()).invoke(userManager, userHandle); |
michael@0 | 184 | putenv("MOZ_ANDROID_USER_SERIAL_NUMBER=" + userSerial.toString()); |
michael@0 | 185 | } |
michael@0 | 186 | } catch (Exception e) { |
michael@0 | 187 | // Guard against any unexpected failures |
michael@0 | 188 | Log.d(LOGTAG, "Unable to set the user serial number", e); |
michael@0 | 189 | } |
michael@0 | 190 | |
michael@0 | 191 | setupLocaleEnvironment(); |
michael@0 | 192 | |
michael@0 | 193 | // We don't need this any more. |
michael@0 | 194 | sIntent = null; |
michael@0 | 195 | } |
michael@0 | 196 | |
michael@0 | 197 | private static void loadLibsSetup(Context context) { |
michael@0 | 198 | synchronized (sLibLoadingLock) { |
michael@0 | 199 | if (sLibsSetup) { |
michael@0 | 200 | return; |
michael@0 | 201 | } |
michael@0 | 202 | sLibsSetup = true; |
michael@0 | 203 | } |
michael@0 | 204 | |
michael@0 | 205 | // The package data lib directory isn't placed in ld.so's |
michael@0 | 206 | // search path, so we have to manually load libraries that |
michael@0 | 207 | // libxul will depend on. Not ideal. |
michael@0 | 208 | |
michael@0 | 209 | File cacheFile = getCacheDir(context); |
michael@0 | 210 | putenv("GRE_HOME=" + getGREDir(context).getPath()); |
michael@0 | 211 | |
michael@0 | 212 | // setup the libs cache |
michael@0 | 213 | String linkerCache = System.getenv("MOZ_LINKER_CACHE"); |
michael@0 | 214 | if (linkerCache == null) { |
michael@0 | 215 | linkerCache = cacheFile.getPath(); |
michael@0 | 216 | putenv("MOZ_LINKER_CACHE=" + linkerCache); |
michael@0 | 217 | } |
michael@0 | 218 | |
michael@0 | 219 | // Disable on-demand decompression of the linker on devices where it |
michael@0 | 220 | // is known to cause crashes. |
michael@0 | 221 | if ("HTC".equals(android.os.Build.MANUFACTURER) && |
michael@0 | 222 | "HTC Vision".equals(android.os.Build.MODEL)) { |
michael@0 | 223 | putenv("MOZ_LINKER_ONDEMAND=0"); |
michael@0 | 224 | } |
michael@0 | 225 | |
michael@0 | 226 | #ifdef MOZ_LINKER_EXTRACT |
michael@0 | 227 | putenv("MOZ_LINKER_EXTRACT=1"); |
michael@0 | 228 | // Ensure that the cache dir is world-writable |
michael@0 | 229 | File cacheDir = new File(linkerCache); |
michael@0 | 230 | if (cacheDir.isDirectory()) { |
michael@0 | 231 | cacheDir.setWritable(true, false); |
michael@0 | 232 | cacheDir.setExecutable(true, false); |
michael@0 | 233 | cacheDir.setReadable(true, false); |
michael@0 | 234 | } |
michael@0 | 235 | #endif |
michael@0 | 236 | } |
michael@0 | 237 | |
michael@0 | 238 | @RobocopTarget |
michael@0 | 239 | public static void loadSQLiteLibs(Context context, String apkName) { |
michael@0 | 240 | synchronized (sLibLoadingLock) { |
michael@0 | 241 | if (sSQLiteLibsLoaded) { |
michael@0 | 242 | return; |
michael@0 | 243 | } |
michael@0 | 244 | sSQLiteLibsLoaded = true; |
michael@0 | 245 | } |
michael@0 | 246 | |
michael@0 | 247 | loadMozGlue(); |
michael@0 | 248 | // the extract libs parameter is being removed in bug 732069 |
michael@0 | 249 | loadLibsSetup(context); |
michael@0 | 250 | loadSQLiteLibsNative(apkName, false); |
michael@0 | 251 | } |
michael@0 | 252 | |
michael@0 | 253 | public static void loadNSSLibs(Context context, String apkName) { |
michael@0 | 254 | synchronized (sLibLoadingLock) { |
michael@0 | 255 | if (sNSSLibsLoaded) { |
michael@0 | 256 | return; |
michael@0 | 257 | } |
michael@0 | 258 | sNSSLibsLoaded = true; |
michael@0 | 259 | } |
michael@0 | 260 | |
michael@0 | 261 | loadMozGlue(); |
michael@0 | 262 | loadLibsSetup(context); |
michael@0 | 263 | loadNSSLibsNative(apkName, false); |
michael@0 | 264 | } |
michael@0 | 265 | |
michael@0 | 266 | public static void doLoadLibrary(final String lib) { |
michael@0 | 267 | try { |
michael@0 | 268 | System.loadLibrary(lib); |
michael@0 | 269 | } catch (UnsatisfiedLinkError e) { |
michael@0 | 270 | Log.wtf(LOGTAG, "Couldn't load " + lib + ". Trying /data/app-lib path."); |
michael@0 | 271 | try { |
michael@0 | 272 | System.load("/data/app-lib/" + ANDROID_PACKAGE_NAME + "/lib" + lib + ".so"); |
michael@0 | 273 | } catch (Throwable ee) { |
michael@0 | 274 | try { |
michael@0 | 275 | Log.wtf(LOGTAG, "Couldn't load " + lib + ": " + ee + ". Trying /data/data path."); |
michael@0 | 276 | System.load("/data/data/" + ANDROID_PACKAGE_NAME + "/lib/lib" + lib + ".so"); |
michael@0 | 277 | } catch (Throwable eee) { |
michael@0 | 278 | Log.wtf(LOGTAG, "Failed every attempt to load " + lib + ". Giving up."); |
michael@0 | 279 | throw new RuntimeException("Unable to load " + lib, eee); |
michael@0 | 280 | } |
michael@0 | 281 | } |
michael@0 | 282 | } |
michael@0 | 283 | } |
michael@0 | 284 | |
michael@0 | 285 | public static void loadMozGlue() { |
michael@0 | 286 | synchronized (sLibLoadingLock) { |
michael@0 | 287 | if (sMozGlueLoaded) { |
michael@0 | 288 | return; |
michael@0 | 289 | } |
michael@0 | 290 | sMozGlueLoaded = true; |
michael@0 | 291 | } |
michael@0 | 292 | |
michael@0 | 293 | doLoadLibrary("mozglue"); |
michael@0 | 294 | } |
michael@0 | 295 | |
michael@0 | 296 | public static void loadGeckoLibs(Context context, String apkName) { |
michael@0 | 297 | loadLibsSetup(context); |
michael@0 | 298 | loadGeckoLibsNative(apkName); |
michael@0 | 299 | } |
michael@0 | 300 | |
michael@0 | 301 | private static void setupLocaleEnvironment() { |
michael@0 | 302 | putenv("LANG=" + Locale.getDefault().toString()); |
michael@0 | 303 | NumberFormat nf = NumberFormat.getInstance(); |
michael@0 | 304 | if (nf instanceof DecimalFormat) { |
michael@0 | 305 | DecimalFormat df = (DecimalFormat)nf; |
michael@0 | 306 | DecimalFormatSymbols dfs = df.getDecimalFormatSymbols(); |
michael@0 | 307 | |
michael@0 | 308 | putenv("LOCALE_DECIMAL_POINT=" + dfs.getDecimalSeparator()); |
michael@0 | 309 | putenv("LOCALE_THOUSANDS_SEP=" + dfs.getGroupingSeparator()); |
michael@0 | 310 | putenv("LOCALE_GROUPING=" + (char)df.getGroupingSize()); |
michael@0 | 311 | } |
michael@0 | 312 | } |
michael@0 | 313 | |
michael@0 | 314 | // These methods are implemented in mozglue/android/nsGeckoUtils.cpp |
michael@0 | 315 | private static native void putenv(String map); |
michael@0 | 316 | |
michael@0 | 317 | // These methods are implemented in mozglue/android/APKOpen.cpp |
michael@0 | 318 | public static native void nativeRun(String args); |
michael@0 | 319 | private static native void loadGeckoLibsNative(String apkName); |
michael@0 | 320 | private static native void loadSQLiteLibsNative(String apkName, boolean shouldExtract); |
michael@0 | 321 | private static native void loadNSSLibsNative(String apkName, boolean shouldExtract); |
michael@0 | 322 | } |