• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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