1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.base.library_loader;
6
7import android.content.Context;
8import android.os.SystemClock;
9import android.util.Log;
10
11import org.chromium.base.CommandLine;
12import org.chromium.base.JNINamespace;
13import org.chromium.base.SysUtils;
14import org.chromium.base.TraceEvent;
15
16/**
17 * This class provides functionality to load and register the native libraries.
18 * Callers are allowed to separate loading the libraries from initializing them.
19 * This may be an advantage for Android Webview, where the libraries can be loaded
20 * by the zygote process, but then needs per process initialization after the
21 * application processes are forked from the zygote process.
22 *
23 * The libraries may be loaded and initialized from any thread. Synchronization
24 * primitives are used to ensure that overlapping requests from different
25 * threads are handled sequentially.
26 *
27 * See also base/android/library_loader/library_loader_hooks.cc, which contains
28 * the native counterpart to this class.
29 */
30@JNINamespace("base::android")
31public class LibraryLoader {
32    private static final String TAG = "LibraryLoader";
33
34    // Guards all access to the libraries
35    private static final Object sLock = new Object();
36
37    // One-way switch becomes true when the libraries are loaded.
38    private static boolean sLoaded = false;
39
40    // One-way switch becomes true when the libraries are initialized (
41    // by calling nativeLibraryLoaded, which forwards to LibraryLoaded(...) in
42    // library_loader_hooks.cc).
43    private static boolean sInitialized = false;
44
45    // One-way switch becomes true if the system library loading failed,
46    // and the right native library was found and loaded by the hack.
47    // The flag is used to report UMA stats later.
48    private static boolean sNativeLibraryHackWasUsed = false;
49
50    /**
51     * The same as ensureInitialized(null, false), should only be called
52     * by non-browser processes.
53     *
54     * @throws ProcessInitException
55     */
56    public static void ensureInitialized() throws ProcessInitException {
57        ensureInitialized(null, false);
58    }
59
60    /**
61     *  This method blocks until the library is fully loaded and initialized.
62     *
63     *  @param context The context in which the method is called, the caller
64     *    may pass in a null context if it doesn't know in which context it
65     *    is running, or it doesn't need to work around the issue
66     *    http://b/13216167.
67     *
68     *    When the context is not null and native library was not extracted
69     *    by Android package manager, the LibraryLoader class
70     *    will extract the native libraries from APK. This is a hack used to
71     *    work around some Sony devices with the following platform bug:
72     *    http://b/13216167.
73     *
74     *  @param shouldDeleteOldWorkaroundLibraries The flag tells whether the method
75     *    should delete the old workaround libraries or not.
76     */
77    public static void ensureInitialized(
78            Context context, boolean shouldDeleteOldWorkaroundLibraries)
79            throws ProcessInitException {
80        synchronized (sLock) {
81            if (sInitialized) {
82                // Already initialized, nothing to do.
83                return;
84            }
85            loadAlreadyLocked(context, shouldDeleteOldWorkaroundLibraries);
86            initializeAlreadyLocked(CommandLine.getJavaSwitchesOrNull());
87        }
88    }
89
90    /**
91     * Checks if library is fully loaded and initialized.
92     */
93    public static boolean isInitialized() {
94        synchronized (sLock) {
95            return sInitialized;
96        }
97    }
98
99    /**
100     * The same as loadNow(null, false), should only be called by
101     * non-browser process.
102     *
103     * @throws ProcessInitException
104     */
105    public static void loadNow() throws ProcessInitException {
106        loadNow(null, false);
107    }
108
109    /**
110     * Loads the library and blocks until the load completes. The caller is responsible
111     * for subsequently calling ensureInitialized().
112     * May be called on any thread, but should only be called once. Note the thread
113     * this is called on will be the thread that runs the native code's static initializers.
114     * See the comment in doInBackground() for more considerations on this.
115     *
116     * @param context The context the code is running, or null if it doesn't have one.
117     * @param shouldDeleteOldWorkaroundLibraries The flag tells whether the method
118     *   should delete the old workaround libraries or not.
119     *
120     * @throws ProcessInitException if the native library failed to load.
121     */
122    public static void loadNow(Context context, boolean shouldDeleteOldWorkaroundLibraries)
123            throws ProcessInitException {
124        synchronized (sLock) {
125            loadAlreadyLocked(context, shouldDeleteOldWorkaroundLibraries);
126        }
127    }
128
129    /**
130     * initializes the library here and now: must be called on the thread that the
131     * native will call its "main" thread. The library must have previously been
132     * loaded with loadNow.
133     * @param initCommandLine The command line arguments that native command line will
134     * be initialized with.
135     */
136    public static void initialize(String[] initCommandLine) throws ProcessInitException {
137        synchronized (sLock) {
138            initializeAlreadyLocked(initCommandLine);
139        }
140    }
141
142    // Invoke System.loadLibrary(...), triggering JNI_OnLoad in native code
143    private static void loadAlreadyLocked(
144            Context context, boolean shouldDeleteOldWorkaroundLibraries)
145            throws ProcessInitException {
146        try {
147            if (!sLoaded) {
148                assert !sInitialized;
149
150                long startTime = SystemClock.uptimeMillis();
151                boolean useChromiumLinker = Linker.isUsed();
152
153                if (useChromiumLinker) Linker.prepareLibraryLoad();
154
155                for (String library : NativeLibraries.LIBRARIES) {
156                    Log.i(TAG, "Loading: " + library);
157                    if (useChromiumLinker) {
158                        Linker.loadLibrary(library);
159                    } else {
160                        try {
161                            System.loadLibrary(library);
162                        } catch (UnsatisfiedLinkError e) {
163                            if (context != null
164                                && LibraryLoaderHelper.tryLoadLibraryUsingWorkaround(context,
165                                                                                     library)) {
166                                sNativeLibraryHackWasUsed = true;
167                            } else {
168                                throw e;
169                            }
170                        }
171                    }
172                }
173                if (useChromiumLinker) Linker.finishLibraryLoad();
174
175                if (context != null
176                    && shouldDeleteOldWorkaroundLibraries
177                    && !sNativeLibraryHackWasUsed) {
178                    LibraryLoaderHelper.deleteWorkaroundLibrariesAsynchronously(
179                        context);
180                }
181
182                long stopTime = SystemClock.uptimeMillis();
183                Log.i(TAG, String.format("Time to load native libraries: %d ms (timestamps %d-%d)",
184                        stopTime - startTime,
185                        startTime % 10000,
186                        stopTime % 10000));
187                sLoaded = true;
188            }
189        } catch (UnsatisfiedLinkError e) {
190            throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED, e);
191        }
192        // Check that the version of the library we have loaded matches the version we expect
193        Log.i(TAG, String.format(
194                "Expected native library version number \"%s\"," +
195                        "actual native library version number \"%s\"",
196                NativeLibraries.VERSION_NUMBER,
197                nativeGetVersionNumber()));
198        if (!NativeLibraries.VERSION_NUMBER.equals(nativeGetVersionNumber())) {
199            throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_WRONG_VERSION);
200        }
201    }
202
203    // Invoke base::android::LibraryLoaded in library_loader_hooks.cc
204    private static void initializeAlreadyLocked(String[] initCommandLine)
205            throws ProcessInitException {
206        if (sInitialized) {
207            return;
208        }
209        if (!nativeLibraryLoaded(initCommandLine)) {
210            Log.e(TAG, "error calling nativeLibraryLoaded");
211            throw new ProcessInitException(LoaderErrors.LOADER_ERROR_FAILED_TO_REGISTER_JNI);
212        }
213        // From this point on, native code is ready to use and checkIsReady()
214        // shouldn't complain from now on (and in fact, it's used by the
215        // following calls).
216        sInitialized = true;
217        CommandLine.enableNativeProxy();
218
219        // From now on, keep tracing in sync with native.
220        TraceEvent.registerNativeEnabledObserver();
221
222        // Record histogram for the Chromium linker.
223        if (Linker.isUsed()) {
224            nativeRecordChromiumAndroidLinkerHistogram(Linker.loadAtFixedAddressFailed(),
225                    SysUtils.isLowEndDevice());
226        }
227
228        nativeRecordNativeLibraryHack(sNativeLibraryHackWasUsed);
229    }
230
231    // Only methods needed before or during normal JNI registration are during System.OnLoad.
232    // nativeLibraryLoaded is then called to register everything else.  This process is called
233    // "initialization".  This method will be mapped (by generated code) to the LibraryLoaded
234    // definition in base/android/library_loader/library_loader_hooks.cc.
235    //
236    // Return true on success and false on failure.
237    private static native boolean nativeLibraryLoaded(String[] initCommandLine);
238
239    // Method called to record statistics about the Chromium linker operation,
240    // i.e. whether the library failed to be loaded at a fixed address, and
241    // whether the device is 'low-memory'.
242    private static native void nativeRecordChromiumAndroidLinkerHistogram(
243            boolean loadedAtFixedAddressFailed,
244            boolean isLowMemoryDevice);
245
246    // Get the version of the native library. This is needed so that we can check we
247    // have the right version before initializing the (rest of the) JNI.
248    private static native String nativeGetVersionNumber();
249
250    private static native void nativeRecordNativeLibraryHack(boolean usedHack);
251}
252