content: package com.antfortune.freeline;
  
  import android.app.Application;
  import android.content.Context;
  import android.content.Intent;
  import android.content.SharedPreferences;
  import android.os.Build;
  import android.text.TextUtils;
  import android.util.Log;
  
  import com.antfortune.freeline.gradle.GradleDynamic;
  import com.antfortune.freeline.resources.MonkeyPatcher;
  import com.antfortune.freeline.util.ActivityManager;
  import com.antfortune.freeline.util.AppUtils;
  import com.antfortune.freeline.util.DexUtils;
  import com.antfortune.freeline.util.FileUtils;
  import com.antfortune.freeline.util.NativeUtils;
  
  import java.io.File;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.io.PrintWriter;
  import java.io.StringWriter;
  import java.io.Writer;
  import java.lang.reflect.Field;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import dalvik.system.PathClassLoader;
  
  /**
   * Created by xianying on 16/3/16.
   */
  public class FreelineCore {
  
      private static final String TAG = "Freeline";
  
      private static final String DYNAMIC_INFO_FILE_NAME = "FREELINE_DYNAMIC_INFO";
  
      private static final String SYNC_INFO_FILE_NAME = "FREELINE_SYNC_INFO";
  
      public static final String DEFAULT_PACKAGE_ID = "base-res.key";
  
      private static final String DYNAMIC_INFO_DEX_PATH_KEY = "dynamic_dex_key";
  
      private static final String DYNAMIC_INFO_DEX_DIR_KEY = "dynamic_dex_dir_key";
  
      private static final String DYNAMIC_INFO_OPT_PATH_KEY = "dynamic_opt_key";
  
      private static long sApkBuildFlag = 0;
  
      private static Application sApplication;
  
      private static Application sRealApplication;
  
      private static IDynamic sDynamic;
  
      public static void init(Application app, Application realApplication) {
          sRealApplication = realApplication;
          init(app, new GradleDynamic(app));
      }
  
      @Deprecated
      public static void init(Application app) {
          init(app, new GradleDynamic(app));
      }
  
      public static void init(Application app, IDynamic dynamicImpl) {
          Log.i(TAG, "freeline start initial process...");
          sApplication = app;
          setDynamicImpl(dynamicImpl);
  
          // 子进程也可以应用 patch
          //if (AppUtils.isApkDebugable(app) && AppUtils.isMainProcess(app)) {
          if (AppUtils.isApkDebugable(app)) {
              Log.i(TAG, "freeline init application");
              ActivityManager.initApplication(app);
              MonkeyPatcher.monkeyPatchApplication(app, app, sRealApplication, null);
  
              try {
                  Object mPackageInfo = getPackageInfo(app);
                  Field field = mPackageInfo.getClass().getDeclaredField("mClassLoader");
                  field.setAccessible(true);
                  PathClassLoader origin = (PathClassLoader) field.get(mPackageInfo);
  
                  if (checkVersionChange()) {
                      Log.i(TAG, "the apk has recover, delete cache");
                      clearDynamicCache();
                      clearSyncCache();
                  } else {
                      Log.i(TAG, "start to inject dex...");
                      injectDex(origin);
                      Log.i(TAG, "start to inject resources...");
                      injectResources();
                  }
  
                  Log.i(TAG, "start to load hackload.dex...");
                  injectHackDex(app, origin);
                  Log.i(TAG, "start to inject native lib...");
                  injectHackNativeLib(app,origin);
              } catch (Exception e) {
                  printStackTrace(e);
              }
  
              Log.i(TAG, "freeline init server");
              startLongLinkServer();
          }
      }
  
      public static void setDynamicImpl(IDynamic dynamicImpl) {
          sDynamic = dynamicImpl;
      }
  
      private static void startLongLinkServer() {
          Intent intent = new Intent(sApplication, FreelineService.class);
          sApplication.startService(intent);
      }
  
      private static String getDynamicDexPath() {
          return getDynamicInfoSp().getString(DYNAMIC_INFO_DEX_PATH_KEY, null);
      }
  
      private static String getDynamicDexDirPath() {
          return getDynamicInfoSp().getString(DYNAMIC_INFO_DEX_DIR_KEY, null);
      }
  
      private static String getDynamicDexOptPath() {
          return getDynamicInfoSp().getString(DYNAMIC_INFO_OPT_PATH_KEY, null);
      }
  
      public static void clearDynamicCache() {
          getDynamicInfoSp().edit().clear().commit();
          FileUtils.rm(new File(getDynamicInfoTempDir()));
          Log.i(TAG, "clear dynamic info sp cache");
      }
  
      public static void clearSyncCache() {
          getSyncInfoSp().edit().clear().commit();
          Log.i(TAG, "clear sync info sp cache");
      }
  
      public static long getBuildTime(Context context) {
          // 使用 ApkBuildFlag 作为基点进行对比，判断应用版本是否匹配
          long buildTime = getApkBuildFlag();
          Log.i(TAG, "Build Time is: " + buildTime);
          return buildTime;
      }
  
      public static long getApkBuildFlag() {
          if (sApkBuildFlag == 0) {
              try {
                  InputStream is = sApplication.getAssets().open("apktime");
                  int size = is.available();
  
                  // Read the entire asset into a local byte buffer.
                  byte[] buffer = new byte[size];
                  is.read(buffer);
                  is.close();
  
                  String text = new String(buffer, "GB2312");
                  Log.i(TAG, "ext:" + text);
                  sApkBuildFlag = Long.parseLong(text);
              } catch (Exception e) {
                  FreelineCore.printStackTrace(e);
              }
          }
          return sApkBuildFlag;
      }
  
      private static void copyAssets(Context context, String assetName, String strOutFileName) throws IOException {
          InputStream myInput;
          OutputStream myOutput = new FileOutputStream(strOutFileName);
          myInput = context.getAssets().open(assetName);
          byte[] buffer = new byte[1024];
          int length = myInput.read(buffer);
          while (length > 0) {
              myOutput.write(buffer, 0, length);
              length = myInput.read(buffer);
          }
          myOutput.flush();
          myInput.close();
          myOutput.close();
      }
  
  
      private static String getDynamicCacheDir() {
          File dir = new File(sApplication.getCacheDir(), "hack");
          if (!dir.exists()) {
              dir.mkdirs();
          }
          return dir.getAbsolutePath();
      }
  
      private static long getDynamicTime() {
          // 使用 ApkBuildFlag 作为基点进行对比，判断应用版本是否匹配
          long dynamicTime = getDynamicInfoSp().getLong("dynamicTime", getApkBuildFlag());
          Log.i(TAG, "Dynamic Time is: " + dynamicTime);
          return dynamicTime;
      }
  
      private static boolean checkVersionChange() {
          return getBuildTime(sApplication) > getDynamicTime();
      }
  
  
      private static Object getPackageInfo(Application app) throws NoSuchFieldException,
              IllegalArgumentException, IllegalAccessException {
          Context contextImpl = app.getBaseContext();
          Field mPackageInfoField = contextImpl.getClass().getDeclaredField(
                  "mPackageInfo");
          mPackageInfoField.setAccessible(true);
          Object mPackageInfo = mPackageInfoField.get(contextImpl);
          return mPackageInfo;
      }
  
      private static void injectHackDex(Context context, PathClassLoader origin) {
          File hackDex = new File(getDynamicCacheDir(), "hackload.dex");
          if (!hackDex.exists() || hackDex.length() < 100) {
              try {
                  copyAssets(context, "hackload.dex", hackDex.getAbsolutePath());
                  Log.i(TAG, "copy hackload dex from assets success");
              } catch (Exception e) {
                  printStackTrace(e);
              }
          }
          if (hackDex.exists() && hackDex.length() > 100) {
              File opt = new File(getDynamicCacheDir(), "opt");
              if (!opt.exists()) {
                  opt.mkdirs();
              }
              DexUtils.inject(origin, hackDex, opt);
              Log.i(TAG, "load hackload，dex size:" + hackDex.length());
          }
      }
  
      private static void injectHackNativeLib(Context context, PathClassLoader classLoader) {
          // 修复so patch不生效问题
          try {
              NativeUtils.installNativeLibraryPath(classLoader, new File(getDynamicNativeDir()), false);
          } catch (Throwable throwable) {
              throwable.printStackTrace();
          }
          //NativeUtils.injectHackNativeLib(getDynamicNativeDir(), classLoader);
      }
  
  
      private static void injectDex(PathClassLoader origin) {
          String dexDirPath = getDynamicDexDirPath();
          if (!TextUtils.isEmpty(dexDirPath)) {
              File dexDir = new File(dexDirPath);
              if (dexDir.isDirectory()) {
                  File[] dexFiles = dexDir.listFiles();
  
                  if (dexFiles.length > 0) {
                      File opt = new File(getDynamicDexOptPath());
                      if (!opt.exists()) {
                          opt.mkdirs();
                      }
                      for (File dexFile : dexFiles) {
                          String dirName = generateStringMD5(dexFile.getName());
                          File dexOptDir = new File(opt, dirName);
                          if (!dexOptDir.exists()) {
                              dexOptDir.mkdirs();
                          }
                          DexUtils.inject(origin, dexFile, dexOptDir);
                      }
                      Log.i(TAG, "find increment package");
                  }
              }
          }
      }
  
      public static void injectResources() {
          Map<String, ?> map = getDynamicInfoSp().getAll();
          Log.i(TAG, "dynamicInfoSp: " + map.toString());
          HashMap<String, String> resMap = new HashMap<String, String>();
          for (String key : map.keySet()) {
              if (key.contains("-")) {
                  resMap.put(key, (String) map.get(key));
              }
          }
          Log.i(TAG, "resMap: " + resMap.toString());
          if (!resMap.isEmpty()) {
              applyDynamicRes(resMap);
          }
      }
  
      public static boolean applyDynamicDex(String dexFileStr, String dexOptDir) {
          Log.i(TAG, "apply dynamicDex " + dexFileStr);
          SharedPreferences sp = getDynamicInfoSp();
          SharedPreferences.Editor editor = sp.edit();
          //editor.putString(DYNAMIC_INFO_DEX_PATH_KEY, dexFileStr);
          editor.putString(DYNAMIC_INFO_DEX_DIR_KEY, dexFileStr);
          editor.putString(DYNAMIC_INFO_OPT_PATH_KEY, dexOptDir);
          editor.commit();
          return true;
      }
  
      public static void printStackTrace(Throwable aThrowable) {
          Writer result = new StringWriter();
          PrintWriter printWriter = new PrintWriter(result);
          aThrowable.printStackTrace(printWriter);
          String resultStr = result.toString();
          Log.e(TAG, resultStr);
      }
  
  
      public static String getDynamicResPath(String packageId) {
          return getDynamicInfoSp().getString(getDynamicResPathKey(packageId), null);
      }
  
      private static String getDynamicResPathKey(String packageId) {
          return packageId + ".key";
      }
  
      public static long getLastDynamicSyncId() {
          return getSyncInfoSp().getLong("lastSync", 0);
      }
  
      public static void saveLastDynamicSyncId(long sync) {
          getSyncInfoSp().edit().putLong("lastSync", sync).commit();
      }
  
      private static SharedPreferences getDynamicInfoSp() {
          return sApplication.getBaseContext().getSharedPreferences(DYNAMIC_INFO_FILE_NAME, Context.MODE_PRIVATE);
      }
  
      private static SharedPreferences getSyncInfoSp() {
          return sApplication.getBaseContext().getSharedPreferences(SYNC_INFO_FILE_NAME, Context.MODE_PRIVATE);
      }
  
      public static String getBundleFilePathByPackageId(String packageId) {
          if (sDynamic != null) {
              return sDynamic.getOriginResPath(packageId);
          }
          return null;
      }
  
      private static boolean applyDynamicRes(HashMap<String, String> dynamicRes) {
          if (sDynamic != null) {
              return sDynamic.applyDynamicRes(dynamicRes);
          }
          return false;
      }
  
      public static Application getRealApplication() {
          return sRealApplication;
      }
  
      public static void updateDynamicTime() {
          // 使用 ApkBuildFlag 作为基点进行对比，判断应用版本是否匹配
          long dynamicTime = getApkBuildFlag();
          Log.i(TAG, "update dynamic time: " + dynamicTime);
          getDynamicInfoSp().edit().putLong("dynamicTime", dynamicTime).commit();
      }
  
      /***
       * packagid + newResPath
       *
       * @param dynamicRes
       */
      public static boolean saveDynamicResInfo(HashMap<String, String> dynamicRes) {
          boolean result = true;
          SharedPreferences sp = getDynamicInfoSp();
          SharedPreferences.Editor editor = sp.edit();
          for (String packageId : dynamicRes.keySet()) {
              String pendingPath = dynamicRes.get(packageId);
              editor.putString(getDynamicResPathKey(packageId), pendingPath);
          }
          editor.commit();
          Log.i(TAG, "apply res :" + dynamicRes);
          injectResources();
          return result;
      }
  
  
      public static String getDynamicInfoTempDir() {
          File dir = new File(sApplication.getCacheDir(), "temp");
          if (!dir.exists()) {
              dir.mkdirs();
          }
          return dir.getAbsolutePath();
      }
  
      public static String getDynamicDexDir() {
          File dir = new File(getDynamicInfoTempDir(), "dex");
          if (!dir.exists()) {
              dir.mkdirs();
          }
          return dir.getAbsolutePath();
      }
  
      public static String getDynamicNativeDir() {
          File dir = new File(getDynamicInfoTempDir(), "native");
          if (!dir.exists()) {
              dir.mkdirs();
          }
          return dir.getAbsolutePath();
      }
  
      public static String getDynamicInfoTempPath(String packageId) {
          File dir;
          if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
              dir = new File(getDynamicInfoTempDir(), packageId + ".jar");
          } else {
              dir = new File(getDynamicInfoTempDir(), packageId);
          }
          return dir.getAbsolutePath();
      }
  
      public static String getUuid() {
          return String.valueOf(getApkBuildFlag());
      }
  
      private static String generateStringMD5(String input) {
          try {
              MessageDigest md = MessageDigest.getInstance("MD5");
              byte[] bytes = md.digest(input.getBytes());
              StringBuilder sb = new StringBuilder();
              for (byte aByte : bytes) {
                  sb.append(Integer.toHexString((aByte & 0xFF) | 0x100).substring(1, 3));
              }
              return sb.toString();
          } catch (NoSuchAlgorithmException e) {
              Log.e(TAG, "MD5 algorithm not found.");
              return input;
          }
      }
  
      public static void clearResourcesCache() {
          if (sDynamic != null) {
              sDynamic.clearResourcesCache();
          }
      }
  
      public static Application getApplication() {
          return sApplication;
      }
  
      public static void updateActivity(String bundleName, String path) {
          Intent intent = new Intent();
          intent.setAction("android.intent.action.FreelineReceiver");
          intent.putExtra(FreelineReceiver.UUID, getUuid());
          intent.putExtra(FreelineReceiver.ACTION_KEY, FreelineReceiver.ACTION_UPDATE_ACTIVITY);
          intent.putExtra(FreelineReceiver.SP_KEY, bundleName);
          intent.putExtra(FreelineReceiver.SP_VALUE, path);
          sApplication.sendBroadcast(intent);
      }
  
      public static void restartApplication(String bundleName, String path, String dexPath, String dirPath) {
          Intent intent = new Intent();
          intent.setAction("android.intent.action.FreelineReceiver");
          intent.putExtra(FreelineReceiver.UUID, getUuid());
          intent.putExtra(FreelineReceiver.ACTION_KEY, FreelineReceiver.ACTION_RESTART_APPLICATION);
          intent.putExtra(FreelineReceiver.SP_KEY, bundleName);
          intent.putExtra(FreelineReceiver.SP_VALUE, path);
          intent.putExtra(FreelineReceiver.DEX_VALUE, dexPath);
          intent.putExtra(FreelineReceiver.OPT_VALUE, dirPath);
          sApplication.sendBroadcast(intent);
      }
  
      @Deprecated
      public static void saveDynamicInfo(String bundleName, String path) {
          Intent intent = new Intent();
          intent.setAction("android.intent.action.FreelineReceiver");
          intent.putExtra(FreelineReceiver.UUID, getUuid());
          intent.putExtra(FreelineReceiver.ACTION_KEY, FreelineReceiver.ACTION_SAVE_DYNAMIC_INFO);
          intent.putExtra(FreelineReceiver.SP_KEY, bundleName);
          intent.putExtra(FreelineReceiver.SP_VALUE, path);
          sApplication.sendBroadcast(intent);
      }
  
  }