Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(402)

Unified Diff: base/android/java/src/org/chromium/base/library_loader/LibraryLoaderHelper.java

Issue 200753002: [Android] Workaround of an android platform bug. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: ChildProcessService needs to use the hack Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..7eeb2c8c3421774464ad1694fd80196eadec2d29
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/library_loader/LibraryLoaderHelper.java
@@ -0,0 +1,259 @@
+// 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)) {
+ 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) {
+ assert context != null;
+ File libFile = getWorkaroundLibFile(context, library);
+ if (!libFile.exists() && !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 {
+ if (!outputFile.createNewFile()) {
+ throw new IOException();
+ }
+
+ 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 {
+ try {
+ if (is != null) is.close();
+ } finally {
+ 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()) {
+ if (!outputFile.delete()) {
+ Log.e(TAG, "Failed to delete " + outputFile.getAbsolutePath());
+ }
+ }
+ 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);
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698