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..27c196b1acb7272f8fd8874f13d5635103b4f930 |
--- /dev/null |
+++ b/base/android/java/src/org/chromium/base/library_loader/LibraryLoaderHelper.java |
@@ -0,0 +1,188 @@ |
+// 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.HashSet; |
+import java.util.Set; |
+import java.util.zip.ZipEntry; |
+import java.util.zip.ZipFile; |
+ |
+class LibraryLoaderHelper { |
+ private static final String TAG = "LibraryLoaderHelper"; |
+ |
+ private static final String LIB_DIR = "lib"; |
+ |
+ /** |
+ * 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 file was not found, |
+ * search the named library in a backup directory. |
+ * Because of change 1, each version has a different native |
+ * library name, so avoids mistakenly using the old native library. |
+ * |
+ * If named library is not in backup directory, extract native libraries |
+ * from apk. |
+ * |
+ * This function doesn't throw UnsatisfiedLinkError, the caller needs to |
+ * check the return value. |
+ */ |
+ static boolean tryLoadLibraryUsingWorkaround( |
+ Context context, String library, boolean shouldUnpack) { |
+ File libFile = getWorkaroundLibFile(context, library); |
+ if (!libFile.exists() && shouldUnpack && !unpackLibraries(context)) { |
+ return false; |
+ } |
+ try { |
+ System.load(libFile.getAbsolutePath()); |
+ return true; |
+ } catch (UnsatisfiedLinkError e) { |
+ return false; |
+ } |
+ } |
+ |
+ private static File getWorkaroundLibFile(Context context, String library) { |
+ String libName = System.mapLibraryName(library); |
+ return new File(context.getDir(LIB_DIR, Context.MODE_PRIVATE), |
+ libName); |
+ } |
+ |
+ private static boolean unpackLibraries(Context context) { |
+ 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(); |
+ 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[4096]; |
+ 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); |
+ return false; |
+ } |
+ } |
+ |
+ /** |
+ * Delete old library files in the backup directory. |
+ * The actual deletion is done in a background thread. |
+ * |
+ * @param context |
+ * @param removeDir if true, deletes the directory |
+ */ |
+ static void cleanupOldLibraries(Context context, final boolean deleteDir) { |
+ // Child process should not reach here. |
+ final File libDir = context.getDir(LIB_DIR, Context.MODE_PRIVATE); |
+ if (libDir.exists() && libDir.isDirectory()) { |
+ new Thread() { |
+ @Override |
+ public void run() { |
+ try { |
+ Set<String> filesToKeep = |
+ new HashSet<String>(NativeLibraries.LIBRARIES.length); |
+ if (!deleteDir) { |
+ for (String libName : NativeLibraries.LIBRARIES) { |
+ filesToKeep.add(System.mapLibraryName(libName)); |
+ } |
+ } |
+ |
+ File[] files = libDir.listFiles(); |
+ if (files != null) { |
+ for (File file : files) { |
+ String fileName = file.getName(); |
+ if (filesToKeep.contains(fileName)) { |
+ continue; |
+ } else { |
+ if (!file.delete()) { |
+ Log.e(TAG, "Failed to remove " + file.getAbsolutePath()); |
+ } |
+ } |
+ } |
+ } |
+ if (deleteDir) { |
+ if (!libDir.delete()) { |
+ Log.w(TAG, "Failed to remove " + libDir.getAbsolutePath()); |
+ } |
+ return; |
+ } |
+ } catch (Exception e) { |
+ Log.e(TAG, "Failed to remove old libs, ", e); |
+ } |
+ } |
+ }.start(); |
+ } |
+ } |
+} |