Chromium Code Reviews| 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..e7bc3c6ff925f70aaa11433ddf9b38236f255e0c |
| --- /dev/null |
| +++ b/base/android/java/src/org/chromium/base/library_loader/LibraryLoaderHelper.java |
| @@ -0,0 +1,183 @@ |
| +// 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; |
| + |
| +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 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 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) { |
| + File libDir = context.getDir(LIB_DIR, Context.MODE_PRIVATE); |
| + 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(); |
|
klobag.chromium
2014/03/19 17:23:46
should we call deleteDirectorySync(libDir) to clea
Feng Qian
2014/03/19 17:52:51
Done.
|
| + 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); |
|
klobag.chromium
2014/03/19 17:23:46
clean up?
Feng Qian
2014/03/19 17:52:51
Done.
|
| + 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 deleteWorkaroundLibraries(Context context) { |
| + // Child process should not reach here. |
| + final File libDir = context.getDir(LIB_DIR, Context.MODE_PRIVATE); |
| + if (libDir.exists()) { |
| + assert libDir.isDirectory(); |
| + new Thread() { |
| + @Override |
| + public void run() { |
| + deleteDirectorySync(libDir); |
| + } |
| + }.start(); |
| + } |
| + } |
| + |
| + 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); |
| + } |
| + } |
| +} |