/*
*
* Copyright (c) 2015, alipay.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alipay.euler.andfix;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import android.content.Context;
import android.util.Log;
import com.alipay.euler.andfix.annotation.MethodReplace;
import com.alipay.euler.andfix.security.SecurityChecker;
import dalvik.system.DexFile;
/**
* AndFix Manager
*
* @author sanping.li@alipay.com
*
*/
public class AndFixManager {
private static final String TAG = "AndFixManager";
private static final String DIR = "apatch_opt";
/**
* context
*/
private final Context mContext;
/**
* classes will be fixed
*/
private static Map<String, Class<?>> mFixedClass = new ConcurrentHashMap<String, Class<?>>();
/**
* whether support AndFix
*/
private boolean mSupport = false;
/**
* security check
*/
private SecurityChecker mSecurityChecker;
/**
* optimize directory
*/
private File mOptDir;
public AndFixManager(Context context) {
Log.d("euler", "AndFixManager, construct");
mContext = context;
mSupport = Compat.isSupport();
if (mSupport) {
mSecurityChecker = new SecurityChecker(mContext);
mOptDir = new File(mContext.getFilesDir(), DIR);
if (!mOptDir.exists() && !mOptDir.mkdirs()) {// make directory fail
mSupport = false;
Log.e(TAG, "opt dir create error.");
} else if (!mOptDir.isDirectory()) {// not directory
mOptDir.delete();
mSupport = false;
}
}
}
/**
* delete optimize file of patch file
*
* @param file
* patch file
*/
public synchronized void removeOptFile(File file) {
Log.d("euler", "AndFixManager, removeOptFile");
File optfile = new File(mOptDir, file.getName());
if (optfile.exists() && !optfile.delete()) {
Log.e(TAG, optfile.getName() + " delete error.");
}
}
/**
* fix
*
* @param patchPath
* patch path
*/
public synchronized void fix(String patchPath) {
Log.d("euler", "AndFixManager, fix");
fix(new File(patchPath), mContext.getClassLoader(), null);
}
/**
* fix
*
* @param file
* patch file
* @param classLoader
* classloader of class that will be fixed
* @param classes
* classes will be fixed
*/
public synchronized void fix(File file, ClassLoader classLoader,
List<String> classes) {
Log.d("euler", "AndFixManager, fix2, file=" + file.getAbsolutePath());
if (!mSupport) {
return;
}
if (!mSecurityChecker.verifyApk(file)) {// security check fail
return;
}
try {
File optfile = new File(mOptDir, file.getName());
boolean saveFingerprint = true;
if (optfile.exists()) {
// need to verify fingerprint when the optimize file exist,
// prevent someone attack on jailbreak device with
// Vulnerability-Parasyte.
// btw:exaggerated android Vulnerability-Parasyte
// http://secauo.com/Exaggerated-Android-Vulnerability-Parasyte.html
if (mSecurityChecker.verifyOpt(optfile)) {
saveFingerprint = false;
} else if (!optfile.delete()) {
return;
}
}
final DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(),
optfile.getAbsolutePath(), Context.MODE_PRIVATE);
if (saveFingerprint) {
mSecurityChecker.saveOptSig(optfile);
}
ClassLoader patchClassLoader = new ClassLoader(classLoader) {
@Override
protected Class<?> findClass(String className)
throws ClassNotFoundException {
Class<?> clazz = dexFile.loadClass(className, this);
if (clazz == null
&& className.startsWith("com.alipay.euler.andfix")) {
return Class.forName(className);// annotation’s class
// not found
}
if (clazz == null) {
throw new ClassNotFoundException(className);
}
return clazz;
}
};
Enumeration<String> entrys = dexFile.entries();
Class<?> clazz = null;
while (entrys.hasMoreElements()) {
String entry = entrys.nextElement();
if (classes != null && !classes.contains(entry)) {
continue;// skip, not need fix
}
clazz = dexFile.loadClass(entry, patchClassLoader);
if (clazz != null) {
fixClass(clazz, classLoader);
}
}
} catch (IOException e) {
Log.e(TAG, "pacth", e);
}
}
/**
* fix class
*
* @param clazz
* class
*/
private void fixClass(Class<?> clazz, ClassLoader classLoader) {
Log.d("euler", "AndFixManager, fixClass, " + clazz);
Method[] methods = clazz.getDeclaredMethods();
MethodReplace methodReplace;
String clz;
String meth;
for (Method method : methods) {
methodReplace = method.getAnnotation(MethodReplace.class);
if (methodReplace == null)
continue;
clz = methodReplace.clazz();
meth = methodReplace.method();
if (!isEmpty(clz) && !isEmpty(meth)) {
replaceMethod(classLoader, clz, meth, method);
}
}
}
/**
* replace method
*
* @param classLoader classloader
* @param clz class
* @param meth name of target method
* @param method source method
*/
private void replaceMethod(ClassLoader classLoader, String clz,
String meth, Method method) {
Log.d("euler", "AndFixManager, replaceMethod, " + classLoader.toString());
Log.d("euler", "AndFixManager, replaceMethod, " + clz);
Log.d("euler", "AndFixManager, replaceMethod, " + meth);
Log.d("euler", "AndFixManager, replaceMethod, " + method);
try {
String key = clz + "@" + classLoader.toString();
Class<?> clazz = mFixedClass.get(key);
if (clazz == null) {// class not load
Class<?> clzz = classLoader.loadClass(clz);
// initialize target class
clazz = AndFix.initTargetClass(clzz);
}
if (clazz != null) {// initialize class OK
mFixedClass.put(key, clazz);
Method src = clazz.getDeclaredMethod(meth,
method.getParameterTypes());
AndFix.addReplaceMethod(src, method);
}
} catch (Exception e) {
Log.e(TAG, "replaceMethod", e);
}
}
private static boolean isEmpty(String string) {
Log.d("euler", "AndFixManager, isEmpty");
return string == null || string.length() <= 0;
}
}