| // 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); |
| } |
| } |