blob: a48312e78746b3b21485e579e01b2e0e143ea5f7 [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.impl;
import android.content.Context;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
import androidx.annotation.VisibleForTesting;
import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.NativeMethods;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.net.NetworkChangeNotifier;
import org.chromium.net.httpflags.BaseFeature;
import org.chromium.net.httpflags.Flags;
import org.chromium.net.httpflags.HttpFlagsLoader;
import org.chromium.net.httpflags.ResolvedFlags;
/** CronetLibraryLoader loads and initializes native library on init thread. */
@JNINamespace("cronet")
@VisibleForTesting
public class CronetLibraryLoader {
// Synchronize initialization.
private static final Object sLoadLock = new Object();
private static final String LIBRARY_NAME = "mainlinecronet." + ImplVersion.getCronetVersion();
@VisibleForTesting
public static final String TAG = CronetLibraryLoader.class.getSimpleName();
// Thread used for initialization work and processing callbacks for
// long-lived global singletons. This thread lives forever as things like
// the global singleton NetworkChangeNotifier live on it and are never killed.
private static final HandlerThread sInitThread = new HandlerThread("CronetInit");
// Has library loading commenced? Setting guarded by sLoadLock.
private static volatile boolean sLibraryLoaded;
// Has ensureInitThreadInitialized() completed?
private static volatile boolean sInitThreadInitDone;
// Block calling native methods until this ConditionVariable opens to indicate loadLibrary()
// is completed and native methods have been registered.
private static final ConditionVariable sWaitForLibLoad = new ConditionVariable();
private static final ConditionVariable sHttpFlagsLoaded = new ConditionVariable();
private static ResolvedFlags sHttpFlags;
@VisibleForTesting public static final String LOG_FLAG_NAME = "Cronet_log_me";
/**
* Ensure that native library is loaded and initialized. Can be called from
* any thread, the load and initialization is performed on init thread.
*/
public static void ensureInitialized(
Context applicationContext, final CronetEngineBuilderImpl builder) {
synchronized (sLoadLock) {
if (!sInitThreadInitDone) {
ContextUtils.initApplicationContext(applicationContext);
if (!sInitThread.isAlive()) {
sInitThread.start();
}
postToInitThread(
new Runnable() {
@Override
public void run() {
ensureInitializedOnInitThread();
}
});
}
if (!sLibraryLoaded) {
System.loadLibrary(LIBRARY_NAME);
String implVersion = ImplVersion.getCronetVersion();
if (!implVersion.equals(CronetLibraryLoaderJni.get().getCronetVersion())) {
throw new RuntimeException(
String.format(
"Expected Cronet version number %s, "
+ "actual version number %s.",
implVersion, CronetLibraryLoaderJni.get().getCronetVersion()));
}
Log.i(
TAG,
"Cronet version: %s, arch: %s",
implVersion,
System.getProperty("os.arch"));
setNativeLoggingLevel();
sLibraryLoaded = true;
sWaitForLibLoad.open();
}
}
}
private static void setNativeLoggingLevel() {
// The constants used here should be kept in sync with logging::LogMessage::~LogMessage().
final String nativeLogTag = "chromium";
int loggingLevel;
// TODO: this way of enabling VLOG is a hack - it doesn't make a ton of sense because
// logging::LogMessage() will still log VLOG() at the Android INFO log level, not DEBUG or
// VERBOSE; also this doesn't make it possible to use advanced filters like --vmodule. See
// https://crbug.com/1488393 for a proposed alternative.
if (Log.isLoggable(nativeLogTag, Log.VERBOSE)) {
loggingLevel = -2; // VLOG(2)
} else if (Log.isLoggable(nativeLogTag, Log.DEBUG)) {
loggingLevel = -1; // VLOG(1)
} else {
// Use the default log level, which logs everything except VLOG(). Skip the
// setMinLogLevel() call to avoid paying for an unnecessary JNI transition.
return;
}
CronetLibraryLoaderJni.get().setMinLogLevel(loggingLevel);
}
/** Returns {@code true} if running on the initialization thread. */
private static boolean onInitThread() {
return sInitThread.getLooper() == Looper.myLooper();
}
/**
* Ensure that the init thread initialization has completed. Can only be called from
* the init thread. Ensures that HTTP flags are loaded, the NetworkChangeNotifier is initialzied
* and the init thread native MessageLoop is initialized.
*/
static void ensureInitializedOnInitThread() {
assert onInitThread();
if (sInitThreadInitDone) {
return;
}
// Load HTTP flags. This is a potentially expensive call, so we do this in parallel with
// library loading in the hope of minimizing impact on Cronet initialization latency.
assert sHttpFlags == null;
Context applicationContext = ContextUtils.getApplicationContext();
Flags flags = HttpFlagsLoader.load(applicationContext);
sHttpFlags =
ResolvedFlags.resolve(
flags != null ? flags : Flags.newBuilder().build(),
applicationContext.getPackageName(),
ImplVersion.getCronetVersion());
sHttpFlagsLoaded.open();
ResolvedFlags.Value logMe = sHttpFlags.flags().get(LOG_FLAG_NAME);
if (logMe != null) {
Log.i(TAG, "HTTP flags log line: %s", logMe.getStringValue());
}
NetworkChangeNotifier.init();
// Registers to always receive network notifications. Note
// that this call is fine for Cronet because Cronet
// embedders do not have API access to create network change
// observers. Existing observers in the net stack do not
// perform expensive work.
NetworkChangeNotifier.registerToReceiveNotificationsAlways();
// Wait for loadLibrary() to complete so JNI is registered.
sWaitForLibLoad.block();
assert sLibraryLoaded;
// TODO: override native base::Feature flags based on `resolvedFlags`. Note that this might
// be tricky because we can only set up base::Feature overrides after the .so is loaded, but
// we have to do it before any native code runs and tries to use any base::Feature flag.
// registerToReceiveNotificationsAlways() is called before the native
// NetworkChangeNotifierAndroid is created, so as to avoid receiving
// the undesired initial network change observer notification, which
// will cause active requests to fail with ERR_NETWORK_CHANGED.
CronetLibraryLoaderJni.get().cronetInitOnInitThread();
sInitThreadInitDone = true;
}
/** Run {@code r} on the initialization thread. */
public static void postToInitThread(Runnable r) {
if (onInitThread()) {
r.run();
} else {
new Handler(sInitThread.getLooper()).post(r);
}
}
/**
* Called by native Cronet library early initialization code to obtain the values of
* native base::Feature overrides that will be applied for the entire lifetime of the Cronet
* native library.
*
* <p>Note that this call sits in the critical path of native library initialization, as
* practically no Chromium native code can run until base::Feature values have settled.
*
* @return The base::Feature overrides as a binary serialized {@link
* org.chromium.net.httpflags.BaseFeatureOverrides} proto.
*/
@CalledByNative
private static byte[] getBaseFeatureOverrides() {
sHttpFlagsLoaded.block();
return BaseFeature.getOverrides(sHttpFlags).toByteArray();
}
/**
* Called from native library to get default user agent constructed
* using application context. May be called on any thread.
*
* Expects that ContextUtils.initApplicationContext() was called already
* either by some testing framework or an embedder constructing a Java
* CronetEngine via CronetEngine.Builder.build().
*/
@CalledByNative
private static String getDefaultUserAgent() {
return UserAgent.getDefault();
}
/**
* Called from native library to ensure that library is initialized.
* May be called on any thread, but initialization is performed on
* this.sInitThread.
*
* Expects that ContextUtils.initApplicationContext() was called already
* either by some testing framework or an embedder constructing a Java
* CronetEngine via CronetEngine.Builder.build().
*
* TODO(mef): In the long term this should be changed to some API with
* lower overhead like CronetEngine.Builder.loadNativeCronet().
*/
@CalledByNative
private static void ensureInitializedFromNative() {
// Called by native, so native library is already loaded.
// It is possible that loaded native library is not regular
// "libcronet.xyz.so" but test library that statically links
// native code like "libcronet_unittests.so".
synchronized (sLoadLock) {
sLibraryLoaded = true;
sWaitForLibLoad.open();
}
// The application context must already be initialized
// using ContextUtils.initApplicationContext().
Context applicationContext = ContextUtils.getApplicationContext();
assert applicationContext != null;
ensureInitialized(applicationContext, null);
}
@CalledByNative
private static void setNetworkThreadPriorityOnNetworkThread(int priority) {
Process.setThreadPriority(priority);
}
@NativeMethods
interface Natives {
// Native methods are implemented in cronet_library_loader.cc.
void cronetInitOnInitThread();
String getCronetVersion();
void setMinLogLevel(int loggingLevel);
}
}