| Index: base/android/java/src/org/chromium/base/library_loader/LibraryLoaderHelper.java
|
| diff --git a/base/android/java/src/org/chromium/base/library_loader/LibraryLoaderHelper.java b/base/android/java/src/org/chromium/base/library_loader/LibraryLoaderHelper.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..fce379705df958855afc3e745b9006c35129522a
|
| --- /dev/null
|
| +++ b/base/android/java/src/org/chromium/base/library_loader/LibraryLoaderHelper.java
|
| @@ -0,0 +1,252 @@
|
| +// Copyright 2014 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +
|
| +package org.chromium.base.library_loader;
|
| +
|
| +import android.content.Context;
|
| +import android.content.pm.ApplicationInfo;
|
| +import android.os.Build;
|
| +import android.util.Log;
|
| +
|
| +import java.io.File;
|
| +import java.io.FileOutputStream;
|
| +import java.io.IOException;
|
| +import java.io.InputStream;
|
| +import java.util.zip.ZipEntry;
|
| +import java.util.zip.ZipFile;
|
| +
|
| +/**
|
| + * The class provides helper functions to extract native libraries from APK,
|
| + * and load libraries from there.
|
| + *
|
| + * The class should be package-visible only, but made public for testing
|
| + * purpose.
|
| + */
|
| +public class LibraryLoaderHelper {
|
| + private static final String TAG = "LibraryLoaderHelper";
|
| +
|
| + private static final String LIB_DIR = "lib";
|
| +
|
| + /**
|
| + * One-way switch becomes true if native libraries were unpacked
|
| + * from APK.
|
| + */
|
| + private static boolean sLibrariesWereUnpacked = false;
|
| +
|
| + /**
|
| + * Loads native libraries using workaround only, skip the library in system
|
| + * lib path. The method exists only for testing purpose.
|
| + * Caller must ensure thread safety of this method.
|
| + * @param context
|
| + */
|
| + public static boolean loadNativeLibrariesUsingWorkaroundForTesting(Context context) {
|
| + // Although tryLoadLibraryUsingWorkaround might be called multiple times,
|
| + // libraries should only be unpacked once, this is guaranteed by
|
| + // sLibrariesWereUnpacked.
|
| + for (String library : NativeLibraries.LIBRARIES) {
|
| + if (!tryLoadLibraryUsingWorkaround(context, library, true)) {
|
| + return false;
|
| + }
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * Try to load a native library using a workaround of
|
| + * http://b/13216167.
|
| + *
|
| + * Workaround for b/13216167 was adapted from code in
|
| + * https://googleplex-android-review.git.corp.google.com/#/c/433061
|
| + *
|
| + * More details about http://b/13216167:
|
| + * PackageManager may fail to update shared library.
|
| + *
|
| + * Native library directory in an updated package is a symbolic link
|
| + * to a directory in /data/app-lib/<package name>, for example:
|
| + * /data/data/com.android.chrome/lib -> /data/app-lib/com.android.chrome[-1].
|
| + * When updating the application, the PackageManager create a new directory,
|
| + * e.g., /data/app-lib/com.android.chrome-2, and remove the old symlink and
|
| + * recreate one to the new directory. However, on some devices (e.g. Sony Xperia),
|
| + * the symlink was updated, but fails to extract new native libraries from
|
| + * the new apk.
|
| +
|
| + * We make the following changes to alleviate the issue:
|
| + * 1) name the native library with apk version code, e.g.,
|
| + * libchrome.1750.136.so, 1750.136 is Chrome version number;
|
| + * 2) first try to load the library using System.loadLibrary,
|
| + * if that failed due to the library file was not found,
|
| + * search the named library in a /data/data/com.android.chrome/app_lib
|
| + * directory. Because of change 1), each version has a different native
|
| + * library name, so avoid mistakenly using the old native library.
|
| + *
|
| + * If named library is not in /data/data/com.android.chrome/app_lib directory,
|
| + * extract native libraries from apk and cache in the directory.
|
| + *
|
| + * This function doesn't throw UnsatisfiedLinkError, the caller needs to
|
| + * check the return value.
|
| + */
|
| + static boolean tryLoadLibraryUsingWorkaround(
|
| + Context context, String library, boolean useNativeLibraryHack) {
|
| + File libFile = getWorkaroundLibFile(context, library);
|
| + if (!libFile.exists() && useNativeLibraryHack && !unpackLibrariesOnce(context)) {
|
| + return false;
|
| + }
|
| + try {
|
| + System.load(libFile.getAbsolutePath());
|
| + return true;
|
| + } catch (UnsatisfiedLinkError e) {
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Returns the directory for holding extracted native libraries.
|
| + * It may create the directory if it doesn't exist.
|
| + *
|
| + * @param context
|
| + * @return the directory file object
|
| + */
|
| + public static File getWorkaroundLibDir(Context context) {
|
| + return context.getDir(LIB_DIR, Context.MODE_PRIVATE);
|
| + }
|
| +
|
| + private static File getWorkaroundLibFile(Context context, String library) {
|
| + String libName = System.mapLibraryName(library);
|
| + return new File(getWorkaroundLibDir(context), libName);
|
| + }
|
| +
|
| + /**
|
| + * Unpack native libraries from the APK file. The method is supposed to
|
| + * be called only once. It deletes existing files in unpacked directory
|
| + * before unpacking.
|
| + *
|
| + * @param context
|
| + * @return true when unpacking was successful, false when failed or called
|
| + * more than once.
|
| + */
|
| + private static boolean unpackLibrariesOnce(Context context) {
|
| + if (sLibrariesWereUnpacked) {
|
| + return false;
|
| + }
|
| + sLibrariesWereUnpacked = true;
|
| +
|
| + File libDir = getWorkaroundLibDir(context);
|
| + if (libDir.exists()) {
|
| + assert libDir.isDirectory();
|
| + deleteDirectorySync(libDir);
|
| + }
|
| +
|
| + try {
|
| + ApplicationInfo appInfo = context.getApplicationInfo();
|
| + ZipFile file = new ZipFile(new File(appInfo.sourceDir), ZipFile.OPEN_READ);
|
| + for (String libName : NativeLibraries.LIBRARIES) {
|
| + String jniNameInApk = "lib/" + Build.CPU_ABI + "/" +
|
| + System.mapLibraryName(libName);
|
| +
|
| + final ZipEntry entry = file.getEntry(jniNameInApk);
|
| + if (entry == null) {
|
| + Log.e(TAG, appInfo.sourceDir + " doesn't have file " + jniNameInApk);
|
| + file.close();
|
| + deleteDirectorySync(libDir);
|
| + return false;
|
| + }
|
| +
|
| + File outputFile = getWorkaroundLibFile(context, libName);
|
| +
|
| + Log.i(TAG, "Extracting native libraries into " + outputFile.getAbsolutePath());
|
| +
|
| + assert !outputFile.exists();
|
| +
|
| + try {
|
| + outputFile.createNewFile();
|
| +
|
| + InputStream is = null;
|
| + FileOutputStream os = null;
|
| + try {
|
| + is = file.getInputStream(entry);
|
| + os = new FileOutputStream(outputFile);
|
| + int count = 0;
|
| + byte[] buffer = new byte[16 * 1024];
|
| + while ((count = is.read(buffer)) > 0) {
|
| + os.write(buffer, 0, count);
|
| + }
|
| + } finally {
|
| + if (is != null) is.close();
|
| + if (os != null) os.close();
|
| + }
|
| + // Change permission to rwxr-xr-x
|
| + outputFile.setReadable(true, false);
|
| + outputFile.setExecutable(true, false);
|
| + outputFile.setWritable(true);
|
| + } catch (IOException e) {
|
| + if (outputFile.exists()) {
|
| + outputFile.delete();
|
| + }
|
| + file.close();
|
| + throw e;
|
| + }
|
| + }
|
| + file.close();
|
| + return true;
|
| + } catch (IOException e) {
|
| + Log.e(TAG, "Failed to unpack native libraries", e);
|
| + deleteDirectorySync(libDir);
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Delete old library files in the backup directory.
|
| + * The actual deletion is done in a background thread.
|
| + *
|
| + * @param context
|
| + */
|
| + static void deleteWorkaroundLibrariesAsynchronously(Context context) {
|
| + // Child process should not reach here.
|
| + final File libDir = getWorkaroundLibDir(context);
|
| + if (libDir.exists()) {
|
| + assert libDir.isDirectory();
|
| + // Async deletion
|
| + new Thread() {
|
| + @Override
|
| + public void run() {
|
| + deleteDirectorySync(libDir);
|
| + }
|
| + }.start();
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Delete the workaround libraries and directory synchronously.
|
| + * For testing purpose only.
|
| + * @param context
|
| + */
|
| + public static void deleteWorkaroundLibrariesSynchronously(Context context) {
|
| + File libDir = getWorkaroundLibDir(context);
|
| + if (libDir.exists()) {
|
| + deleteDirectorySync(libDir);
|
| + }
|
| + }
|
| +
|
| + private static void deleteDirectorySync(File dir) {
|
| + try {
|
| + File[] files = dir.listFiles();
|
| + if (files != null) {
|
| + for (File file : files) {
|
| + String fileName = file.getName();
|
| + if (!file.delete()) {
|
| + Log.e(TAG, "Failed to remove " + file.getAbsolutePath());
|
| + }
|
| + }
|
| + }
|
| + if (!dir.delete()) {
|
| + Log.w(TAG, "Failed to remove " + dir.getAbsolutePath());
|
| + }
|
| + return;
|
| + } catch (Exception e) {
|
| + Log.e(TAG, "Failed to remove old libs, ", e);
|
| + }
|
| + }
|
| +}
|
|
|