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); |
+ } |
+ } |
+} |