/*
* Copyright (C) 2016 Baidu, Inc. All Rights Reserved.
*/
package com.dodola.rocoofix;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build.VERSION;
import android.util.Log;
@SuppressLint({ "NewApi" })
public class RocooSoFix {
private static final String TAG = "RocooSoFix";
/**
* 从指定目录加载so
*
* 注意,目录要跟jniLibs下的结构一样才可以比如 armeabi/libstub.so
*
* @param context
* @param soDirPath
*/
public static void applyPatch(Context context, String soDirPath) {
try {
ApplicationInfo applicationInfo = getApplicationInfo(context);
if (applicationInfo == null) {
return;
}
ClassLoader loader;
try {
loader = context.getClassLoader();
} catch (RuntimeException e) {
Log.w(TAG, "Failure while trying to obtain Context class loader. "
+ "Must be running in test mode. Skip patching.", e);
return;
}
if (loader == null) {
Log.e(TAG, "Context class loader is null. Must be running in test mode. " + "Skip patching.");
return;
}
File soDirFile = new File(soDirPath);
if (soDirFile.exists()) {
installSoDir(loader, soDirFile);
}
} catch (Exception e) {
e.printStackTrace();
} catch (Throwable e) {
e.printStackTrace();
}
}
private static ApplicationInfo getApplicationInfo(Context context) throws PackageManager.NameNotFoundException {
PackageManager pm;
String packageName;
try {
pm = context.getPackageManager();
packageName = context.getPackageName();
} catch (RuntimeException e) {
/*
* Ignore those exceptions so that we don't break tests relying on Context like a
* android.test.mock.MockContext or a android.content.ContextWrapper with a null base Context.
*/
Log.w(TAG, "Failure while trying to obtain ApplicationInfo from Context. "
+ "Must be running in test mode. Skip patching.", e);
return null;
}
if (pm == null || packageName == null) {
// This is most likely a mock context, so just return without patching.
return null;
}
ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
return applicationInfo;
}
public static void installSoDir(ClassLoader classloader, File additionalSoPath) throws InvocationTargetException,
NoSuchMethodException, IllegalAccessException, NoSuchFieldException, InstantiationException, IOException {
if (additionalSoPath != null && additionalSoPath.exists()) {
if (VERSION.SDK_INT >= 23) {
V23.install(classloader, additionalSoPath);
} else if (VERSION.SDK_INT >= 14) {
V14.install(classloader, additionalSoPath);
} else {
V4.install(classloader, additionalSoPath);
}
}
}
private static final class V4 {
private static void install(ClassLoader loader, File additionalSoPath) throws IllegalArgumentException,
IllegalAccessException, NoSuchFieldException, IOException {
Field pathField = RocooUtils.findField(loader, "path");
List<String> libraryPathElements =
(List<String>) RocooUtils.findField(loader, "libraryPathElements").get(loader);
if (libraryPathElements != null) {
libraryPathElements.add(0, additionalSoPath.getAbsolutePath());
}
}
}
private static final class V14 {
private static void install(ClassLoader loader, File additionalSoPath) throws IllegalArgumentException,
IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
Field pathListField = RocooUtils.findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
Field nativeLibraryDirectoriesField = RocooUtils.findField(dexPathList, "nativeLibraryDirectories");
File[] nativeLibraryDirectories = (File[]) nativeLibraryDirectoriesField.get(dexPathList);
if (nativeLibraryDirectories != null) {
File[] tmp = new File[(nativeLibraryDirectories.length + 1)];
tmp[0] = additionalSoPath;
System.arraycopy(nativeLibraryDirectories, 0, tmp, 1, nativeLibraryDirectories.length);
nativeLibraryDirectoriesField.set(dexPathList, tmp);
}
}
}
private static final class V23 {
private static void install(ClassLoader loader, File additionalSoPath) throws IllegalArgumentException,
IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException,
InstantiationException {
try {
Object dexPathList = RocooUtils.findField(loader, "pathList").get(loader);
ArrayList<File> additionalPathEntries = new ArrayList<>();
additionalPathEntries.add(additionalSoPath);
ArrayList<IOException> suppressedExceptions = new ArrayList<>();
RocooUtils.expandFieldArray(dexPathList, "nativeLibraryPathElements",
RocooUtils.makePathElements(dexPathList, additionalPathEntries, null, suppressedExceptions));
if (suppressedExceptions.size() > 0) {
for (IOException e : suppressedExceptions) {
Log.w(TAG, "Exception in makeDexElement", e);
}
Field suppressedExceptionsField =
RocooUtils.findField(dexPathList, "dexElementsSuppressedExceptions");
IOException[] dexElementsSuppressedExceptions =
(IOException[]) suppressedExceptionsField.get(dexPathList);
if (dexElementsSuppressedExceptions == null) {
dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
IOException[] combined =
new IOException[suppressedExceptions.size() + dexElementsSuppressedExceptions.length];
suppressedExceptions.toArray(combined);
System.arraycopy(dexElementsSuppressedExceptions, 0, combined, suppressedExceptions.size(),
dexElementsSuppressedExceptions.length);
dexElementsSuppressedExceptions = combined;
}
suppressedExceptionsField.set(dexPathList, dexElementsSuppressedExceptions);
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e2) {
e2.printStackTrace();
} catch (IllegalArgumentException e3) {
e3.printStackTrace();
}
}
}
}