package me.ele.amigo.compat;
import android.annotation.TargetApi;
import android.app.Application;
import android.os.Build;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import me.ele.amigo.reflect.MethodUtils;
import me.ele.amigo.utils.FileUtils;
public class NativeLibraryHelperCompat {
private static final String TAG = NativeLibraryHelperCompat.class.getSimpleName();
private static final Class nativeLibraryHelperClass() throws ClassNotFoundException {
return Class.forName("com.android.internal.content.NativeLibraryHelper");
}
private static final Class handleClass() throws ClassNotFoundException {
return Class.forName("com.android.internal.content.NativeLibraryHelper$Handle");
}
public static final int copyNativeBinaries(File apkFile, File sharedLibraryDir) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return copyNativeBinariesAfterL(apkFile, sharedLibraryDir);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return copyNativeBinariesAfterICE(apkFile, sharedLibraryDir);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
return copyNativeBinariesAfterGingerBread(apkFile, sharedLibraryDir);
} else {
return copyNativeBinariesAfterECLAIR_MR1(apkFile, sharedLibraryDir);
}
}
private static int copyNativeBinariesAfterGingerBread(File apkFile, File sharedLibraryDir) {
try {
Object[] args = new Object[2];
args[0] = apkFile;
args[1] = sharedLibraryDir;
return (int) MethodUtils.invokeStaticMethod(nativeLibraryHelperClass(),
"copyNativeBinariesLI", args);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return -1;
}
private static int copyNativeBinariesAfterICE(File apkFile, File sharedLibraryDir) {
try {
Object[] args = new Object[2];
args[0] = apkFile;
args[1] = sharedLibraryDir;
return (int) MethodUtils.invokeStaticMethod(nativeLibraryHelperClass(),
"copyNativeBinariesIfNeededLI", args);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return -1;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static int copyNativeBinariesAfterL(File apkFile, File sharedLibraryDir) {
try {
Object handleInstance = MethodUtils.invokeStaticMethod(handleClass(), "create",
apkFile);
if (handleInstance == null) {
return -1;
}
String abi = null;
if (isVM64()) {
if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
Set<String> abis = getAbisFromApk(apkFile.getAbsolutePath());
if (abis == null || abis.isEmpty()) {
return 0;
}
int abiIndex = (int) MethodUtils.invokeStaticMethod(nativeLibraryHelperClass
(), "findSupportedAbi", handleInstance, Build.SUPPORTED_64_BIT_ABIS);
if (abiIndex >= 0) {
abi = Build.SUPPORTED_64_BIT_ABIS[abiIndex];
}
}
} else {
if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
Set<String> abis = getAbisFromApk(apkFile.getAbsolutePath());
if (abis == null || abis.isEmpty()) {
return 0;
}
int abiIndex = (int) MethodUtils.invokeStaticMethod(nativeLibraryHelperClass
(), "findSupportedAbi", handleInstance, Build.SUPPORTED_32_BIT_ABIS);
if (abiIndex >= 0) {
abi = Build.SUPPORTED_32_BIT_ABIS[abiIndex];
}
}
}
if (abi == null) {
return -1;
}
Object[] args = new Object[3];
args[0] = handleInstance;
args[1] = sharedLibraryDir;
args[2] = abi;
return (int) MethodUtils.invokeStaticMethod(nativeLibraryHelperClass(),
"copyNativeBinaries", args);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return -1;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static boolean isVM64() throws ClassNotFoundException, NoSuchMethodException,
InvocationTargetException, IllegalAccessException {
Set<String> supportedAbis = getAbisFromApk(getHostApk());
if (Build.SUPPORTED_64_BIT_ABIS.length == 0) {
return false;
}
if (supportedAbis == null || supportedAbis.isEmpty()) {
return true;
}
for (String supportedAbi : supportedAbis) {
if ("arm64-v8a".endsWith(supportedAbi)
|| "x86_64".equals(supportedAbi)
|| "mips64".equals(supportedAbi)) {
return true;
}
}
return false;
}
private static Set<String> getAbisFromApk(String apk) {
try {
ZipFile apkFile = new ZipFile(apk);
Enumeration<? extends ZipEntry> entries = apkFile.entries();
Set<String> supportedAbis = new HashSet<>();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
if (name.contains("../")) {
continue;
}
if (name.startsWith("lib/") && !entry.isDirectory() && name.endsWith(".so")) {
String supportedAbi = name.substring(name.indexOf("/") + 1, name.lastIndexOf
("/"));
supportedAbis.add(supportedAbi);
}
}
Log.d(TAG, "supportedAbis : " + supportedAbis);
return supportedAbis;
} catch (Exception e) {
Log.e(TAG, "get supportedAbis failure", e);
}
return null;
}
private static String getHostApk() throws ClassNotFoundException, NoSuchMethodException,
IllegalAccessException, InvocationTargetException {
Application application = (Application) MethodUtils.invokeStaticMethod
(ActivityThreadCompat.clazz(), "currentApplication");
return application.getApplicationInfo().sourceDir;
}
private static int copyNativeBinariesAfterECLAIR_MR1(File apkFile, File sharedLibraryDir) {
final String sharedLibraryABI = Build.CPU_ABI;
final String apkSharedLibraryPrefix = "lib/" + sharedLibraryABI + "/";
final String sharedLibrarySuffix = ".so";
byte[] buffer = new byte[8 * 1024];
try {
FileUtils.mkdirChecked(sharedLibraryDir);
ZipInputStream zis = new ZipInputStream(new FileInputStream(apkFile));
ZipEntry ze = zis.getNextEntry();
while (ze != null) {
String fileName = ze.getName();
if (!fileName.startsWith(apkSharedLibraryPrefix)
|| !fileName.endsWith(sharedLibrarySuffix)) {
ze = zis.getNextEntry();
continue;
}
File newFile = new File(sharedLibraryDir + File.separator +
fileName.substring(apkSharedLibraryPrefix.length()));
new File(newFile.getParent()).mkdirs();
FileOutputStream fos = new FileOutputStream(newFile);
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
fos.close();
ze = zis.getNextEntry();
}
zis.closeEntry();
zis.close();
} catch (IOException ex) {
ex.printStackTrace();
return -1;
}
return 1;
}
}