package me.ele.amigo.utils;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import me.ele.amigo.AmigoDirs;
import me.ele.amigo.PatchApks;
public class DexExtractor {
private static final String TAG = "DexExtractor";
private Context context;
private String checksum;
private byte[] buffer;
public DexExtractor(Context context, String checkSum) {
this.context = context;
this.checksum = checkSum;
}
private static boolean verifyZipFile(File file) {
try {
ZipFile ex = new ZipFile(file);
try {
ex.close();
return true;
} catch (IOException var3) {
Log.w(TAG, "Failed to close zip file: " + file.getAbsolutePath());
}
} catch (ZipException var4) {
Log.w(TAG, "File " + file.getAbsolutePath() + " is not a valid zip file.",
var4);
} catch (IOException var5) {
Log.w(TAG,
"Got an IOException trying to open zip file: " + file.getAbsolutePath(), var5);
}
return false;
}
private static boolean equals(InputStream newDex, InputStream oldDex) {
try {
byte[] hash1 = DigestUtils.md5(newDex);
byte[] hash2 = DigestUtils.md5(oldDex);
return Arrays.equals(hash1, hash2);
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
public static void closeSilently(InputStream in) {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void closeSilently(ZipFile zipFile) {
if (zipFile != null) {
try {
zipFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public boolean extractDexFiles() {
if (Build.VERSION.SDK_INT >= 21) {
return true; // art supports multi-dex natively
}
return performExtractions(PatchApks.getInstance(context).patchFile(checksum),
AmigoDirs.getInstance(context).dexDir(checksum));
}
private boolean performExtractions(File patchApk, File dexDir) {
ZipFile apk = null;
try {
apk = new ZipFile(patchApk);
int dexNum = 0;
ZipEntry dexFile = apk.getEntry("classes.dex");
for (; dexFile != null; dexFile = apk.getEntry("classes" + dexNum + ".dex")) {
String fileName = dexFile.getName().replace("dex", "zip");
File extractedFile = new File(dexDir, fileName);
extract(apk, dexFile, extractedFile);
verifyZipFile(extractedFile);
if (dexNum == 0) ++dexNum;
++dexNum;
}
return dexNum > 0;
} catch (IOException ioe) {
ioe.printStackTrace();
return false;
} finally {
try {
apk.close();
} catch (IOException var16) {
Log.w("DexExtractor", "Failed to close resource", var16);
}
}
}
private String findMatchedSecondaryDex(final String dexName) {
File secondaryDexDir =
new File(context.getApplicationInfo().dataDir, "code_cache/secondary-dexes");
File[] secondaryDexes = secondaryDexDir.listFiles(new FileFilter() {
@Override public boolean accept(File pathname) {
return pathname.getName().endsWith(dexName);
}
});
return secondaryDexes != null && secondaryDexes.length == 1
? secondaryDexes[0].getAbsolutePath() : null;
}
private void extract(ZipFile patchApk, ZipEntry dexFile, File extractTo) throws IOException {
boolean reused = reusePreExistedODex(patchApk, dexFile);
Log.d(TAG, "extracted: "
+ dexFile.getName() + " success ? "
+ reused
+ ", by reusing pre-existed secondary dex");
if (reused) {
return;
}
InputStream in = null;
File tmp = null;
ZipOutputStream out = null;
try {
in = patchApk.getInputStream(dexFile);
tmp = File.createTempFile(extractTo.getName(), ".tmp", extractTo.getParentFile());
try {
out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tmp)));
ZipEntry classesDex = new ZipEntry("classes.dex");
classesDex.setTime(dexFile.getTime());
out.putNextEntry(classesDex);
if (buffer == null) {
buffer = new byte[16384];
}
for (int length = in.read(buffer); length != -1; length = in.read(buffer)) {
out.write(buffer, 0, length);
}
} finally {
if (out != null) {
out.closeEntry();
out.close();
}
}
if (!tmp.renameTo(extractTo)) {
throw new IOException("Failed to rename \""
+ tmp.getAbsolutePath()
+ "\" to \""
+ extractTo.getAbsolutePath()
+ "\"");
}
} finally {
closeSilently(in);
if (tmp != null) tmp.delete();
}
}
private boolean reusePreExistedODex(ZipFile apk, ZipEntry dexFile) {
String preExistedDexPath;
File preExistedOdex;
boolean isMainDex = dexFile.getName().equals("classes.dex");
if (isMainDex) {
File baseApk = new File(context.getApplicationInfo().sourceDir);
preExistedDexPath = baseApk.getAbsolutePath();
preExistedOdex =
new File("/data/dalvik-cache/data@app@" + baseApk.getName() + "@classes.dex");
} else {
preExistedDexPath = findMatchedSecondaryDex(dexFile.getName().replace("dex", "zip"));
if (preExistedDexPath == null) {
return false;
}
preExistedOdex = new File(preExistedDexPath.replace("zip", "dex"));
}
if (!preExistedOdex.exists() || !preExistedOdex.canRead()) {
return false;
}
boolean canReuse;
ZipFile baseApk = null;
InputStream baseDexInputStream = null;
try {
InputStream patchDexInputStream = apk.getInputStream(dexFile);
baseApk = new ZipFile(preExistedDexPath);
baseDexInputStream = baseApk.getInputStream(new ZipEntry("classes.dex"));
canReuse = equals(patchDexInputStream, baseDexInputStream);
} catch (IOException e) {
e.printStackTrace();
canReuse = false;
} finally {
closeSilently(baseDexInputStream);
closeSilently(baseApk);
closeSilently(baseDexInputStream);
}
if (!canReuse) {
return false;
}
File extractTo = new File(AmigoDirs.getInstance(context).dexDir(checksum),
dexFile.getName().replace("dex", "zip"));
File extractToOdex = new File(AmigoDirs.getInstance(context).dexOptDir(checksum),
dexFile.getName());
if (isMainDex) {
try {
// make a link directing to the base apk instead of the patch apk here,
// because dalvik uses the modification time of the dex entry to
// verify the odex file
SymbolicLinkUtil.createLink(new File(context.getApplicationInfo().sourceDir),
extractTo);
} catch (IOException e) {
Log.e(TAG, "reusePreExistedODex: create first dex symlink failed", e);
return false;
}
} else {
// make a existed secondary dex copy in case of system update, we don't want
// the dex file to be deleted like the odex file under that
if (!FileUtils.copyFile(new File(preExistedDexPath), extractTo)) {
return false;
}
}
try {
// try to create a symlink to save space
SymbolicLinkUtil.createLink(preExistedOdex, extractToOdex);
return true;
} catch (IOException e) {
Log.e(TAG, "reusePreExistedODex: create odex symlink failed", e);
}
// fall back to make a copy of the odex file
return FileUtils.copyFile(preExistedOdex, extractToOdex);
}
}