| Index: build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java
|
| diff --git a/build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java b/build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..66486c5f7ef23e7ae40e56113d087e961c21ba4d
|
| --- /dev/null
|
| +++ b/build/android/incremental_install/java/org/chromium/incrementalinstall/ClassLoaderPatcher.java
|
| @@ -0,0 +1,232 @@
|
| +// Copyright 2015 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.incrementalinstall;
|
| +
|
| +import android.content.Context;
|
| +import android.os.Build;
|
| +import android.util.Log;
|
| +
|
| +import java.io.File;
|
| +import java.io.FileInputStream;
|
| +import java.io.FileNotFoundException;
|
| +import java.io.FileOutputStream;
|
| +import java.io.IOException;
|
| +import java.util.List;
|
| +
|
| +/**
|
| + * Provides the ability to add native libraries and .dex files to an existing class loader.
|
| + * Tested with Jellybean MR2 - Marshmellow.
|
| + */
|
| +final class ClassLoaderPatcher {
|
| + private static final String TAG = "cr.incrementalinstall";
|
| + private final File mAppFilesSubDir;
|
| + private final ClassLoader mClassLoader;
|
| + private final Object mLibcoreOs;
|
| + private final int mProcessUid;
|
| + final boolean mIsPrimaryProcess;
|
| +
|
| + ClassLoaderPatcher(Context context) throws ReflectiveOperationException {
|
| + mAppFilesSubDir =
|
| + new File(context.getApplicationInfo().dataDir, "incremental-install-files");
|
| + mClassLoader = context.getClassLoader();
|
| + mLibcoreOs = Reflect.getField(Class.forName("libcore.io.Libcore"), "os");
|
| + mProcessUid = (Integer) Reflect.invokeMethod(mLibcoreOs, "getuid");
|
| + mIsPrimaryProcess = context.getApplicationInfo().uid == mProcessUid;
|
| + Log.i(TAG, "uid=" + mProcessUid + " (isPrimary=" + mIsPrimaryProcess + ")");
|
| + }
|
| +
|
| + /**
|
| + * Loads all dex files within |dexDir| into the app's ClassLoader.
|
| + */
|
| + void loadDexFiles(File dexDir) throws ReflectiveOperationException, FileNotFoundException {
|
| + Log.i(TAG, "Installing dex files from: " + dexDir);
|
| + File[] dexFilesArr = dexDir.listFiles();
|
| + if (dexFilesArr == null) {
|
| + throw new FileNotFoundException("Dex dir does not exist: " + dexDir);
|
| + }
|
| + // The optimized dex files will be owned by this process' user.
|
| + // Store them within the app's data dir rather than on /data/local/tmp
|
| + // so that they are still deleted (by the OS) when we uninstall
|
| + // (even on a non-rooted device).
|
| + File incrementalDexesDir = new File(mAppFilesSubDir, "optimized-dexes");
|
| + File isolatedDexesDir = new File(mAppFilesSubDir, "isolated-dexes");
|
| + File optimizedDir;
|
| +
|
| + if (mIsPrimaryProcess) {
|
| + ensureAppFilesSubDirExists();
|
| + // Allows isolated processes to access the same files.
|
| + incrementalDexesDir.mkdir();
|
| + incrementalDexesDir.setReadable(true, false);
|
| + incrementalDexesDir.setExecutable(true, false);
|
| + // Create a directory for isolated processes to create directories in.
|
| + isolatedDexesDir.mkdir();
|
| + isolatedDexesDir.setWritable(true, false);
|
| + isolatedDexesDir.setExecutable(true, false);
|
| +
|
| + optimizedDir = incrementalDexesDir;
|
| + } else {
|
| + // There is a UID check of the directory in dalvik.system.DexFile():
|
| + // https://android.googlesource.com/platform/libcore/+/45e0260/dalvik/src/main/java/dalvik/system/DexFile.java#101
|
| + // Rather than have each isolated process run DexOpt though, we use
|
| + // symlinks within the directory to point at the browser process'
|
| + // optimized dex files.
|
| + optimizedDir = new File(isolatedDexesDir, "isolated-" + mProcessUid);
|
| + optimizedDir.mkdir();
|
| + // Always wipe it out and re-create for simplicity.
|
| + Log.i(TAG, "Creating dex file symlinks for isolated process");
|
| + for (File f : optimizedDir.listFiles()) {
|
| + f.delete();
|
| + }
|
| + for (File f : incrementalDexesDir.listFiles()) {
|
| + String to = "../../" + incrementalDexesDir.getName() + "/" + f.getName();
|
| + File from = new File(optimizedDir, f.getName());
|
| + createSymlink(to, from);
|
| + }
|
| + }
|
| +
|
| + Log.i(TAG, "Code cache dir: " + optimizedDir);
|
| + // TODO(agrieve): Might need to record classpath ordering if we ever have duplicate
|
| + // class names (since then order will matter here).
|
| + Log.i(TAG, "Loading " + dexFilesArr.length + " dex files");
|
| +
|
| + Object dexPathList = Reflect.getField(mClassLoader, "pathList");
|
| + Object[] dexElements = (Object[]) Reflect.getField(dexPathList, "dexElements");
|
| + Object[] additionalElements = makeDexElements(dexFilesArr, optimizedDir);
|
| + Reflect.setField(
|
| + dexPathList, "dexElements", Reflect.concatArrays(dexElements, additionalElements));
|
| + }
|
| +
|
| + /**
|
| + * Sets up all libraries within |libDir| to be loadable by System.loadLibrary().
|
| + */
|
| + void importNativeLibs(File libDir) throws ReflectiveOperationException, IOException {
|
| + Log.i(TAG, "Importing native libraries from: " + libDir);
|
| + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
| + libDir = prepareNativeLibsAndroidM(libDir);
|
| + }
|
| + addNativeLibrarySearchPath(libDir);
|
| + }
|
| +
|
| + /**
|
| + * Primary process: Copies native libraries into the app's data directory
|
| + * Other processes: Waits for primary process to finish copying.
|
| + */
|
| + private File prepareNativeLibsAndroidM(File libDir) throws IOException {
|
| + File localLibsDir = new File(mAppFilesSubDir, "lib");
|
| + File copyLibsLockFile = new File(mAppFilesSubDir, "libcopy.lock");
|
| + // Due to a new SELinux policy, all libs must be copied into the app's
|
| + // data directory first.
|
| + // https://code.google.com/p/android/issues/detail?id=79480
|
| + if (mIsPrimaryProcess) {
|
| + LockFile lockFile = LockFile.acquireRuntimeLock(copyLibsLockFile);
|
| + if (lockFile == null) {
|
| + LockFile.waitForRuntimeLock(copyLibsLockFile, 10 * 1000);
|
| + } else {
|
| + try {
|
| + ensureAppFilesSubDirExists();
|
| + localLibsDir.mkdir();
|
| + localLibsDir.setReadable(true, false);
|
| + localLibsDir.setExecutable(true, false);
|
| + copyChangedFiles(libDir, localLibsDir);
|
| + } finally {
|
| + lockFile.release();
|
| + }
|
| + }
|
| + } else {
|
| + // TODO: Work around this issue by using APK splits to install each dex / lib.
|
| + throw new RuntimeException("Incremental install does not work on Android M+ "
|
| + + "with isolated processes. Use the gn arg:\n"
|
| + + " disable_incremental_isolated_processes=true\n"
|
| + + "and try again.");
|
| + }
|
| + return localLibsDir;
|
| + }
|
| +
|
| + @SuppressWarnings("unchecked")
|
| + private void addNativeLibrarySearchPath(File nativeLibDir) throws ReflectiveOperationException {
|
| + Object dexPathList = Reflect.getField(mClassLoader, "pathList");
|
| + Object currentDirs = Reflect.getField(dexPathList, "nativeLibraryDirectories");
|
| + File[] newDirs = new File[] { nativeLibDir };
|
| + // Switched from an array to an ArrayList in Lollipop.
|
| + if (currentDirs instanceof List) {
|
| + List<File> dirsAsList = (List<File>) currentDirs;
|
| + dirsAsList.add(nativeLibDir);
|
| + } else {
|
| + File[] dirsAsArray = (File[]) currentDirs;
|
| + Reflect.setField(dexPathList, "nativeLibraryDirectories",
|
| + Reflect.concatArrays(dirsAsArray, newDirs));
|
| + }
|
| +
|
| + Object[] nativeLibraryPathElements;
|
| + try {
|
| + nativeLibraryPathElements =
|
| + (Object[]) Reflect.getField(dexPathList, "nativeLibraryPathElements");
|
| + } catch (NoSuchFieldException e) {
|
| + // This field doesn't exist pre-M.
|
| + return;
|
| + }
|
| + Object[] additionalElements = makeNativePathElements(newDirs);
|
| + Reflect.setField(
|
| + dexPathList, "nativeLibraryPathElements",
|
| + Reflect.concatArrays(nativeLibraryPathElements, additionalElements));
|
| + }
|
| +
|
| + private static void copyChangedFiles(File srcDir, File dstDir) throws IOException {
|
| + // No need to delete stale libs since libraries are loaded explicitly.
|
| + for (File f : srcDir.listFiles()) {
|
| + // Note: Tried using hardlinks, but resulted in EACCES exceptions.
|
| + File dest = new File(dstDir, f.getName());
|
| + copyIfModified(f, dest);
|
| + }
|
| + }
|
| +
|
| + private static void copyIfModified(File src, File dest) throws IOException {
|
| + long lastModified = src.lastModified();
|
| + if (!dest.exists() || dest.lastModified() != lastModified) {
|
| + Log.i(TAG, "Copying " + src + " -> " + dest);
|
| + FileInputStream istream = new FileInputStream(src);
|
| + FileOutputStream ostream = new FileOutputStream(dest);
|
| + ostream.getChannel().transferFrom(istream.getChannel(), 0, istream.getChannel().size());
|
| + istream.close();
|
| + ostream.close();
|
| + dest.setReadable(true, false);
|
| + dest.setExecutable(true, false);
|
| + dest.setLastModified(lastModified);
|
| + }
|
| + }
|
| +
|
| + private void ensureAppFilesSubDirExists() {
|
| + mAppFilesSubDir.mkdir();
|
| + mAppFilesSubDir.setExecutable(true, false);
|
| + }
|
| +
|
| + private void createSymlink(String to, File from) throws ReflectiveOperationException {
|
| + Reflect.invokeMethod(mLibcoreOs, "symlink", to, from.getAbsolutePath());
|
| + }
|
| +
|
| + private static Object[] makeNativePathElements(File[] paths)
|
| + throws ReflectiveOperationException {
|
| + Class<?> entryClazz = Class.forName("dalvik.system.DexPathList$Element");
|
| + Object[] entries = new Object[paths.length];
|
| + for (int i = 0; i < paths.length; ++i) {
|
| + entries[i] = Reflect.newInstance(entryClazz, paths[i], true, null, null);
|
| + }
|
| + return entries;
|
| + }
|
| +
|
| + private static Object[] makeDexElements(File[] files, File optimizedDirectory)
|
| + throws ReflectiveOperationException {
|
| + Class<?> entryClazz = Class.forName("dalvik.system.DexPathList$Element");
|
| + Class<?> clazz = Class.forName("dalvik.system.DexPathList");
|
| + Object[] entries = new Object[files.length];
|
| + File emptyDir = new File("");
|
| + for (int i = 0; i < files.length; ++i) {
|
| + File file = files[i];
|
| + Object dexFile = Reflect.invokeMethod(clazz, "loadDexFile", file, optimizedDirectory);
|
| + entries[i] = Reflect.newInstance(entryClazz, emptyDir, false, file, dexFile);
|
| + }
|
| + return entries;
|
| + }
|
| +}
|
|
|