// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.base.library_loader;
import android.content.Context;
import android.util.Log;
import org.chromium.base.CommandLine;
import org.chromium.base.JNINamespace;
import org.chromium.base.SysUtils;
import org.chromium.base.TraceEvent;
/**
* This class provides functionality to load and register the native libraries.
* Callers are allowed to separate loading the libraries from initializing them.
* This may be an advantage for Android Webview, where the libraries can be loaded
* by the zygote process, but then needs per process initialization after the
* application processes are forked from the zygote process.
*
* The libraries may be loaded and initialized from any thread. Synchronization
* primitives are used to ensure that overlapping requests from different
* threads are handled sequentially.
*
* See also base/android/library_loader/library_loader_hooks.cc, which contains
* the native counterpart to this class.
*/
@JNINamespace("base::android")
public class LibraryLoader {
private static final String TAG = "LibraryLoader";
// Guards all access to the libraries
private static final Object sLock = new Object();
// One-way switch becomes true when the libraries are loaded.
private static boolean sLoaded = false;
// One-way switch becomes true when the libraries are initialized (
// by calling nativeLibraryLoaded, which forwards to LibraryLoaded(...) in
// library_loader_hooks.cc).
private static boolean sInitialized = false;
// One-way switch becomes true if the system library loading failed,
// and the right native library was found and loaded by the hack.
// The flag is used to report UMA stats later.
private static boolean sNativeLibraryHackWasUsed = false;
// TODO(cjhopman): Remove this once it's unused.
/**
* Doesn't do anything.
*/
@Deprecated
public static void setLibraryToLoad(String library) {
}
/**
* TODO: http://crbug.com/354655
* remove this method once WebViewChromiumFactoryProvider.java
* changes the call to ensureInitialized(null).
*/
public static void ensureInitialized() throws ProcessInitException {
ensureInitialized(null);
}
/**
* This method blocks until the library is fully loaded and initialized.
*
* @param context The context in which the method is called, the caller
* may pass in a null context if it doesn't know in which context it
* is running, or it doesn't need to work around the issue
* http://b/13216167.
*
* When the context is not null and native library was not extracted
* by Android package manager, the LibraryLoader class
* will extract the native libraries from APK. This is a hack used to
* work around some Sony devices with the following platform bug:
* http://b/13216167.
*/
public static void ensureInitialized(Context context) throws ProcessInitException {
synchronized (sLock) {
if (sInitialized) {
// Already initialized, nothing to do.
return;
}
loadAlreadyLocked(context);
initializeAlreadyLocked(CommandLine.getJavaSwitchesOrNull());
}
}
/**
* Checks if library is fully loaded and initialized.
*/
public static boolean isInitialized() {
synchronized (sLock) {
return sInitialized;
}
}
/**
* Loads the library and blocks until the load completes. The caller is responsible
* for subsequently calling ensureInitialized().
* May be called on any thread, but should only be called once. Note the thread
* this is called on will be the thread that runs the native code's static initializers.
* See the comment in doInBackground() for more considerations on this.
*
* @throws ProcessInitException if the native library failed to load.
*/
public static void loadNow(Context context) throws ProcessInitException {
synchronized (sLock) {
loadAlreadyLocked(context);
}
}
/**
* initializes the library here and now: must be called on the thread that the
* native will call its "main" thread. The library must have previously been
* loaded with loadNow.
* @param initCommandLine The command line arguments that native command line will
* be initialized with.
*/
public static void initialize(String[] initCommandLine) throws ProcessInitException {
synchronized (sLock) {
initializeAlreadyLocked(initCommandLine);
}
}
// Invoke System.loadLibrary(...), triggering JNI_OnLoad in native code
private static void loadAlreadyLocked(Context context) throws ProcessInitException {
try {
if (!sLoaded) {
assert !sInitialized;
long startTime = System.currentTimeMillis();
boolean useChromiumLinker = Linker.isUsed();
if (useChromiumLinker)
Linker.prepareLibraryLoad();
for (String library : NativeLibraries.LIBRARIES) {
Log.i(TAG, "Loading: " + library);
if (useChromiumLinker)
Linker.loadLibrary(library);
else
try {
System.loadLibrary(library);
if (context != null) {
LibraryLoaderHelper.deleteWorkaroundLibrariesAsynchronously(
context);
}
} catch (UnsatisfiedLinkError e) {
if (context != null
&& LibraryLoaderHelper.tryLoadLibraryUsingWorkaround(context,
library)) {
sNativeLibraryHackWasUsed = true;
} else {
throw e;
}
}
}
if (useChromiumLinker)
Linker.finishLibraryLoad();
long stopTime = System.currentTimeMillis();
Log.i(TAG, String.format("Time to load native libraries: %d ms (timestamps %d-%d)",
stopTime - startTime,
startTime % 10000,
stopTime % 10000));
sLoaded = true;
}
} catch (UnsatisfiedLinkError e) {
throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED, e);
}
// Check that the version of the library we have loaded matches the version we expect
Log.i(TAG, String.format(
"Expected native library version number \"%s\"," +
"actual native library version number \"%s\"",
NativeLibraries.VERSION_NUMBER,
nativeGetVersionNumber()));
if (!NativeLibraries.VERSION_NUMBER.equals(nativeGetVersionNumber())) {
throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_WRONG_VERSION);
}
}
// Invoke base::android::LibraryLoaded in library_loader_hooks.cc
private static void initializeAlreadyLocked(String[] initCommandLine)
throws ProcessInitException {
if (sInitialized) {
return;
}
if (!nativeLibraryLoaded(initCommandLine)) {
Log.e(TAG, "error calling nativeLibraryLoaded");
throw new ProcessInitException(LoaderErrors.LOADER_ERROR_FAILED_TO_REGISTER_JNI);
}
// From this point on, native code is ready to use and checkIsReady()
// shouldn't complain from now on (and in fact, it's used by the
// following calls).
sInitialized = true;
CommandLine.enableNativeProxy();
TraceEvent.setEnabledToMatchNative();
// Record histogram for the Chromium linker.
if (Linker.isUsed())
nativeRecordChromiumAndroidLinkerHistogram(Linker.loadAtFixedAddressFailed(),
SysUtils.isLowEndDevice());
nativeRecordNativeLibraryHack(sNativeLibraryHackWasUsed);
}
// Only methods needed before or during normal JNI registration are during System.OnLoad.
// nativeLibraryLoaded is then called to register everything else. This process is called
// "initialization". This method will be mapped (by generated code) to the LibraryLoaded
// definition in base/android/library_loader/library_loader_hooks.cc.
//
// Return true on success and false on failure.
private static native boolean nativeLibraryLoaded(String[] initCommandLine);
// Method called to record statistics about the Chromium linker operation,
// i.e. whether the library failed to be loaded at a fixed address, and
// whether the device is 'low-memory'.
private static native void nativeRecordChromiumAndroidLinkerHistogram(
boolean loadedAtFixedAddressFailed,
boolean isLowMemoryDevice);
// Get the version of the native library. This is needed so that we can check we
// have the right version before initializing the (rest of the) JNI.
private static native String nativeGetVersionNumber();
private static native void nativeRecordNativeLibraryHack(boolean usedHack);
}