1 // Copyright 2015 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.net.impl; 6 7 import android.content.Context; 8 import android.os.ConditionVariable; 9 import android.os.Handler; 10 import android.os.HandlerThread; 11 import android.os.Looper; 12 import android.os.Process; 13 14 import androidx.annotation.VisibleForTesting; 15 16 import org.jni_zero.CalledByNative; 17 import org.jni_zero.JNINamespace; 18 import org.jni_zero.NativeMethods; 19 20 import org.chromium.base.ContextUtils; 21 import org.chromium.base.Log; 22 import org.chromium.net.NetworkChangeNotifier; 23 import org.chromium.net.httpflags.BaseFeature; 24 import org.chromium.net.httpflags.Flags; 25 import org.chromium.net.httpflags.HttpFlagsLoader; 26 import org.chromium.net.httpflags.ResolvedFlags; 27 28 /** CronetLibraryLoader loads and initializes native library on init thread. */ 29 @JNINamespace("cronet") 30 @VisibleForTesting 31 public class CronetLibraryLoader { 32 // Synchronize initialization. 33 private static final Object sLoadLock = new Object(); 34 private static final String LIBRARY_NAME = "mainlinecronet." + ImplVersion.getCronetVersion(); 35 @VisibleForTesting 36 public static final String TAG = CronetLibraryLoader.class.getSimpleName(); 37 // Thread used for initialization work and processing callbacks for 38 // long-lived global singletons. This thread lives forever as things like 39 // the global singleton NetworkChangeNotifier live on it and are never killed. 40 private static final HandlerThread sInitThread = new HandlerThread("CronetInit"); 41 // Has library loading commenced? Setting guarded by sLoadLock. 42 private static volatile boolean sLibraryLoaded; 43 // Has ensureInitThreadInitialized() completed? 44 private static volatile boolean sInitThreadInitDone; 45 // Block calling native methods until this ConditionVariable opens to indicate loadLibrary() 46 // is completed and native methods have been registered. 47 private static final ConditionVariable sWaitForLibLoad = new ConditionVariable(); 48 49 private static final ConditionVariable sHttpFlagsLoaded = new ConditionVariable(); 50 private static ResolvedFlags sHttpFlags; 51 52 @VisibleForTesting public static final String LOG_FLAG_NAME = "Cronet_log_me"; 53 54 /** 55 * Ensure that native library is loaded and initialized. Can be called from 56 * any thread, the load and initialization is performed on init thread. 57 */ ensureInitialized( Context applicationContext, final CronetEngineBuilderImpl builder)58 public static void ensureInitialized( 59 Context applicationContext, final CronetEngineBuilderImpl builder) { 60 synchronized (sLoadLock) { 61 if (!sInitThreadInitDone) { 62 ContextUtils.initApplicationContext(applicationContext); 63 if (!sInitThread.isAlive()) { 64 sInitThread.start(); 65 } 66 postToInitThread( 67 new Runnable() { 68 @Override 69 public void run() { 70 ensureInitializedOnInitThread(); 71 } 72 }); 73 } 74 if (!sLibraryLoaded) { 75 System.loadLibrary(LIBRARY_NAME); 76 String implVersion = ImplVersion.getCronetVersion(); 77 if (!implVersion.equals(CronetLibraryLoaderJni.get().getCronetVersion())) { 78 throw new RuntimeException( 79 String.format( 80 "Expected Cronet version number %s, " 81 + "actual version number %s.", 82 implVersion, CronetLibraryLoaderJni.get().getCronetVersion())); 83 } 84 Log.i( 85 TAG, 86 "Cronet version: %s, arch: %s", 87 implVersion, 88 System.getProperty("os.arch")); 89 setNativeLoggingLevel(); 90 sLibraryLoaded = true; 91 sWaitForLibLoad.open(); 92 } 93 } 94 } 95 setNativeLoggingLevel()96 private static void setNativeLoggingLevel() { 97 // The constants used here should be kept in sync with logging::LogMessage::~LogMessage(). 98 final String nativeLogTag = "chromium"; 99 int loggingLevel; 100 // TODO: this way of enabling VLOG is a hack - it doesn't make a ton of sense because 101 // logging::LogMessage() will still log VLOG() at the Android INFO log level, not DEBUG or 102 // VERBOSE; also this doesn't make it possible to use advanced filters like --vmodule. See 103 // https://crbug.com/1488393 for a proposed alternative. 104 if (Log.isLoggable(nativeLogTag, Log.VERBOSE)) { 105 loggingLevel = -2; // VLOG(2) 106 } else if (Log.isLoggable(nativeLogTag, Log.DEBUG)) { 107 loggingLevel = -1; // VLOG(1) 108 } else { 109 // Use the default log level, which logs everything except VLOG(). Skip the 110 // setMinLogLevel() call to avoid paying for an unnecessary JNI transition. 111 return; 112 } 113 CronetLibraryLoaderJni.get().setMinLogLevel(loggingLevel); 114 } 115 116 /** Returns {@code true} if running on the initialization thread. */ onInitThread()117 private static boolean onInitThread() { 118 return sInitThread.getLooper() == Looper.myLooper(); 119 } 120 121 /** 122 * Ensure that the init thread initialization has completed. Can only be called from 123 * the init thread. Ensures that HTTP flags are loaded, the NetworkChangeNotifier is initialzied 124 * and the init thread native MessageLoop is initialized. 125 */ ensureInitializedOnInitThread()126 static void ensureInitializedOnInitThread() { 127 assert onInitThread(); 128 if (sInitThreadInitDone) { 129 return; 130 } 131 132 // Load HTTP flags. This is a potentially expensive call, so we do this in parallel with 133 // library loading in the hope of minimizing impact on Cronet initialization latency. 134 assert sHttpFlags == null; 135 Context applicationContext = ContextUtils.getApplicationContext(); 136 Flags flags = HttpFlagsLoader.load(applicationContext); 137 sHttpFlags = 138 ResolvedFlags.resolve( 139 flags != null ? flags : Flags.newBuilder().build(), 140 applicationContext.getPackageName(), 141 ImplVersion.getCronetVersion()); 142 sHttpFlagsLoaded.open(); 143 ResolvedFlags.Value logMe = sHttpFlags.flags().get(LOG_FLAG_NAME); 144 if (logMe != null) { 145 Log.i(TAG, "HTTP flags log line: %s", logMe.getStringValue()); 146 } 147 148 NetworkChangeNotifier.init(); 149 // Registers to always receive network notifications. Note 150 // that this call is fine for Cronet because Cronet 151 // embedders do not have API access to create network change 152 // observers. Existing observers in the net stack do not 153 // perform expensive work. 154 NetworkChangeNotifier.registerToReceiveNotificationsAlways(); 155 // Wait for loadLibrary() to complete so JNI is registered. 156 sWaitForLibLoad.block(); 157 assert sLibraryLoaded; 158 159 // TODO: override native base::Feature flags based on `resolvedFlags`. Note that this might 160 // be tricky because we can only set up base::Feature overrides after the .so is loaded, but 161 // we have to do it before any native code runs and tries to use any base::Feature flag. 162 163 // registerToReceiveNotificationsAlways() is called before the native 164 // NetworkChangeNotifierAndroid is created, so as to avoid receiving 165 // the undesired initial network change observer notification, which 166 // will cause active requests to fail with ERR_NETWORK_CHANGED. 167 CronetLibraryLoaderJni.get().cronetInitOnInitThread(); 168 sInitThreadInitDone = true; 169 } 170 171 /** Run {@code r} on the initialization thread. */ postToInitThread(Runnable r)172 public static void postToInitThread(Runnable r) { 173 if (onInitThread()) { 174 r.run(); 175 } else { 176 new Handler(sInitThread.getLooper()).post(r); 177 } 178 } 179 180 /** 181 * Called by native Cronet library early initialization code to obtain the values of 182 * native base::Feature overrides that will be applied for the entire lifetime of the Cronet 183 * native library. 184 * 185 * <p>Note that this call sits in the critical path of native library initialization, as 186 * practically no Chromium native code can run until base::Feature values have settled. 187 * 188 * @return The base::Feature overrides as a binary serialized {@link 189 * org.chromium.net.httpflags.BaseFeatureOverrides} proto. 190 */ 191 @CalledByNative getBaseFeatureOverrides()192 private static byte[] getBaseFeatureOverrides() { 193 sHttpFlagsLoaded.block(); 194 return BaseFeature.getOverrides(sHttpFlags).toByteArray(); 195 } 196 197 /** 198 * Called from native library to get default user agent constructed 199 * using application context. May be called on any thread. 200 * 201 * Expects that ContextUtils.initApplicationContext() was called already 202 * either by some testing framework or an embedder constructing a Java 203 * CronetEngine via CronetEngine.Builder.build(). 204 */ 205 @CalledByNative getDefaultUserAgent()206 private static String getDefaultUserAgent() { 207 return UserAgent.getDefault(); 208 } 209 210 /** 211 * Called from native library to ensure that library is initialized. 212 * May be called on any thread, but initialization is performed on 213 * this.sInitThread. 214 * 215 * Expects that ContextUtils.initApplicationContext() was called already 216 * either by some testing framework or an embedder constructing a Java 217 * CronetEngine via CronetEngine.Builder.build(). 218 * 219 * TODO(mef): In the long term this should be changed to some API with 220 * lower overhead like CronetEngine.Builder.loadNativeCronet(). 221 */ 222 @CalledByNative ensureInitializedFromNative()223 private static void ensureInitializedFromNative() { 224 // Called by native, so native library is already loaded. 225 // It is possible that loaded native library is not regular 226 // "libcronet.xyz.so" but test library that statically links 227 // native code like "libcronet_unittests.so". 228 synchronized (sLoadLock) { 229 sLibraryLoaded = true; 230 sWaitForLibLoad.open(); 231 } 232 233 // The application context must already be initialized 234 // using ContextUtils.initApplicationContext(). 235 Context applicationContext = ContextUtils.getApplicationContext(); 236 assert applicationContext != null; 237 ensureInitialized(applicationContext, null); 238 } 239 240 @CalledByNative setNetworkThreadPriorityOnNetworkThread(int priority)241 private static void setNetworkThreadPriorityOnNetworkThread(int priority) { 242 Process.setThreadPriority(priority); 243 } 244 245 @NativeMethods 246 interface Natives { 247 // Native methods are implemented in cronet_library_loader.cc. cronetInitOnInitThread()248 void cronetInitOnInitThread(); 249 getCronetVersion()250 String getCronetVersion(); 251 setMinLogLevel(int loggingLevel)252 void setMinLogLevel(int loggingLevel); 253 } 254 } 255