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.chromium.base.ContextUtils; 17 import org.chromium.base.Log; 18 import org.chromium.base.annotations.CalledByNative; 19 import org.chromium.base.annotations.JNINamespace; 20 import org.chromium.base.annotations.NativeMethods; 21 import org.chromium.net.NetworkChangeNotifier; 22 23 /** 24 * CronetLibraryLoader loads and initializes native library on init thread. 25 */ 26 @JNINamespace("cronet") 27 @VisibleForTesting 28 public class CronetLibraryLoader { 29 // Synchronize initialization. 30 private static final Object sLoadLock = new Object(); 31 private static final String LIBRARY_NAME = "cronet." + ImplVersion.getCronetVersion(); 32 private static final String TAG = CronetLibraryLoader.class.getSimpleName(); 33 // Thread used for initialization work and processing callbacks for 34 // long-lived global singletons. This thread lives forever as things like 35 // the global singleton NetworkChangeNotifier live on it and are never killed. 36 private static final HandlerThread sInitThread = new HandlerThread("CronetInit"); 37 // Has library loading commenced? Setting guarded by sLoadLock. 38 private static volatile boolean sLibraryLoaded = false; 39 // Has ensureInitThreadInitialized() completed? 40 private static volatile boolean sInitThreadInitDone; 41 // Block calling native methods until this ConditionVariable opens to indicate loadLibrary() 42 // is completed and native methods have been registered. 43 private static final ConditionVariable sWaitForLibLoad = new ConditionVariable(); 44 45 /** 46 * Ensure that native library is loaded and initialized. Can be called from 47 * any thread, the load and initialization is performed on init thread. 48 */ ensureInitialized( Context applicationContext, final CronetEngineBuilderImpl builder)49 public static void ensureInitialized( 50 Context applicationContext, final CronetEngineBuilderImpl builder) { 51 synchronized (sLoadLock) { 52 if (!sInitThreadInitDone) { 53 ContextUtils.initApplicationContext(applicationContext); 54 if (!sInitThread.isAlive()) { 55 sInitThread.start(); 56 } 57 postToInitThread(CronetLibraryLoader::ensureInitializedOnInitThread); 58 } 59 if (!sLibraryLoaded) { 60 System.loadLibrary(LIBRARY_NAME); 61 String implVersion = ImplVersion.getCronetVersion(); 62 if (!implVersion.equals(CronetLibraryLoaderJni.get().getCronetVersion())) { 63 throw new RuntimeException(String.format("Expected Cronet version number %s, " 64 + "actual version number %s.", 65 implVersion, CronetLibraryLoaderJni.get().getCronetVersion())); 66 } 67 Log.i(TAG, "Cronet version: %s, arch: %s", implVersion, 68 System.getProperty("os.arch")); 69 sLibraryLoaded = true; 70 sWaitForLibLoad.open(); 71 } 72 } 73 } 74 75 /** 76 * Returns {@code true} if running on the initialization thread. 77 */ onInitThread()78 private static boolean onInitThread() { 79 return sInitThread.getLooper() == Looper.myLooper(); 80 } 81 82 /** 83 * Ensure that the init thread initialization has completed. Can only be called from 84 * the init thread. Ensures that the NetworkChangeNotifier is initialzied and the 85 * init thread native MessageLoop is initialized. 86 */ ensureInitializedOnInitThread()87 static void ensureInitializedOnInitThread() { 88 assert onInitThread(); 89 if (sInitThreadInitDone) { 90 return; 91 } 92 NetworkChangeNotifier.init(); 93 // Registers to always receive network notifications. Note 94 // that this call is fine for Cronet because Cronet 95 // embedders do not have API access to create network change 96 // observers. Existing observers in the net stack do not 97 // perform expensive work. 98 NetworkChangeNotifier.registerToReceiveNotificationsAlways(); 99 // Wait for loadLibrary() to complete so JNI is registered. 100 sWaitForLibLoad.block(); 101 102 assert sLibraryLoaded; 103 // registerToReceiveNotificationsAlways() is called before the native 104 // NetworkChangeNotifierAndroid is created, so as to avoid receiving 105 // the undesired initial network change observer notification, which 106 // will cause active requests to fail with ERR_NETWORK_CHANGED. 107 CronetLibraryLoaderJni.get().cronetInitOnInitThread(); 108 sInitThreadInitDone = true; 109 } 110 111 /** 112 * Run {@code r} on the initialization thread. 113 */ postToInitThread(Runnable r)114 public static void postToInitThread(Runnable r) { 115 if (onInitThread()) { 116 r.run(); 117 } else { 118 new Handler(sInitThread.getLooper()).post(r); 119 } 120 } 121 122 /** 123 * Called from native library to get default user agent constructed 124 * using application context. May be called on any thread. 125 * 126 * Expects that ContextUtils.initApplicationContext() was called already 127 * either by some testing framework or an embedder constructing a Java 128 * CronetEngine via CronetEngine.Builder.build(). 129 */ 130 @CalledByNative getDefaultUserAgent()131 private static String getDefaultUserAgent() { 132 return UserAgent.getDefault(); 133 } 134 135 /** 136 * Called from native library to ensure that library is initialized. 137 * May be called on any thread, but initialization is performed on 138 * this.sInitThread. 139 * 140 * Expects that ContextUtils.initApplicationContext() was called already 141 * either by some testing framework or an embedder constructing a Java 142 * CronetEngine via CronetEngine.Builder.build(). 143 * 144 * TODO(mef): In the long term this should be changed to some API with 145 * lower overhead like CronetEngine.Builder.loadNativeCronet(). 146 */ 147 @CalledByNative ensureInitializedFromNative()148 private static void ensureInitializedFromNative() { 149 // Called by native, so native library is already loaded. 150 // It is possible that loaded native library is not regular 151 // "libcronet.xyz.so" but test library that statically links 152 // native code like "libcronet_unittests.so". 153 synchronized (sLoadLock) { 154 sLibraryLoaded = true; 155 sWaitForLibLoad.open(); 156 } 157 158 // The application context must already be initialized 159 // using ContextUtils.initApplicationContext(). 160 Context applicationContext = ContextUtils.getApplicationContext(); 161 assert applicationContext != null; 162 ensureInitialized(applicationContext, null); 163 } 164 165 @CalledByNative setNetworkThreadPriorityOnNetworkThread(int priority)166 private static void setNetworkThreadPriorityOnNetworkThread(int priority) { 167 Process.setThreadPriority(priority); 168 } 169 170 @NativeMethods 171 interface Natives { 172 // Native methods are implemented in cronet_library_loader.cc. cronetInitOnInitThread()173 void cronetInitOnInitThread(); 174 getCronetVersion()175 String getCronetVersion(); 176 } 177 } 178