package me.ele.amigo; import android.content.Context; import android.content.pm.ApplicationInfo; import android.os.Build; import android.util.Log; import java.io.File; import java.util.HashMap; import java.util.Map; import me.ele.amigo.utils.CommonUtils; import me.ele.amigo.utils.FileUtils; public final class AmigoDirs { private static final String TAG = AmigoDirs.class.getSimpleName(); private static final String CODE_CACHE_NAME = "code_cache/amigo_odex"; private static final String AMIGO_FOLDER_NAME = "amigo"; private static final String AMIGO_DEX_FOLDER_NAME = "dexes"; private static final String AMIGO_LIB_FOLDER_NAME = "libs"; private static final String PATCH_INFO_FILE_NAME = "amigo.json"; private static AmigoDirs sInstance; private Context context; /** * /data/data/{package_name}/files/amigo */ private File amigoDir; /** * System manages */ private File odexDir; /** * /data/data/{package_name}/code_cache/{checksum}/amigo-dexes */ private Map<String, File> optDirs = new HashMap<>(); /** * /data/data/{package_name}/files/amigo/{checksum}/dexes */ private Map<String, File> dexDirs = new HashMap<>(); /** * /data/data/{package_name}/files/amigo/{checksum}/libs */ private Map<String, File> libDirs = new HashMap<>(); private AmigoDirs(Context context) { this.context = context; ensureAmigoDir(); } public synchronized static AmigoDirs getInstance(Context context) { if (sInstance == null) { sInstance = new AmigoDirs(context); } return sInstance; } public File amigoDir() { ensureAmigoDir(); return amigoDir; } public File dexOptDir(String checksum) { ensurePatchDirs(checksum); return optDirs.get(checksum); } public File dexDir(String checksum) { ensurePatchDirs(checksum); return dexDirs.get(checksum); } public File patchInfoFile() { ensureAmigoDir(); return new File(amigoDir, PATCH_INFO_FILE_NAME); } public File libDir(String checksum) { ensurePatchDirs(checksum); return libDirs.get(checksum); } private void ensureAmigoDir() throws RuntimeException { if (amigoDir != null && amigoDir.exists() && odexDir != null && odexDir.exists()) return; try { ApplicationInfo applicationInfo = CommonUtils.getApplicationInfo(context); if (applicationInfo == null) { // Looks like running on a test Context, so just return without initiate. return; } // data dir's real path amigoDir = new File(context.getFilesDir().getCanonicalPath(), AMIGO_FOLDER_NAME); amigoDir.mkdirs(); odexDir = new File(new File(applicationInfo.dataDir).getCanonicalPath(), CODE_CACHE_NAME); // NOTE: use adb install xxx.apk on Android N would cause the code_cache dir not // writable, refer to https://code.google.com/p/android/issues/detail?id=225735 odexDir.mkdirs(); if (!odexDir.canRead() || !odexDir.canWrite()) { throw new RuntimeException("do not have access to " + odexDir); } } catch (Exception e) { throw new RuntimeException("Initiate amigo files failed (" + e.getMessage() + ")."); } } private void ensurePatchDirs(String checksum) { try { ensureAmigoDir(); File patchDir = new File(amigoDir, checksum); if (!patchDir.exists()) { FileUtils.mkdirChecked(patchDir); } File dexDir = new File(patchDir, AMIGO_DEX_FOLDER_NAME); if (!dexDir.exists()) { FileUtils.mkdirChecked(dexDir); } dexDirs.put(checksum, dexDir); File libDir = new File(patchDir, AMIGO_LIB_FOLDER_NAME); if (!libDir.exists()) { FileUtils.mkdirChecked(libDir); } libDirs.put(checksum, libDir); File patchCacheDir = new File(odexDir, checksum); if (!patchCacheDir.exists()) { patchCacheDir.mkdirs(); } optDirs.put(checksum, patchCacheDir); } catch (Exception e) { throw new RuntimeException("Initiate amigo files for patch apk: " + checksum + " " + "failed (" + e.getMessage() + ")."); } } public boolean isOptedDexExists(String checksum) { File[] odexes = dexOptDir(checksum).listFiles(); int odexFilesLength; if (odexes == null || (odexFilesLength = odexes.length) == 0) { return false; } if (Build.VERSION.SDK_INT >= 21) { return odexFilesLength == 1 && odexes[0].exists(); } File[] dexFiles = dexDir(checksum).listFiles(); int dexFilesLength; if (dexFiles == null || (dexFilesLength = dexFiles.length) != odexFilesLength) { return false; } for (int i = 0; i < dexFilesLength; i++) { if (!dexFiles[i].exists() || !odexes[i].exists()) { return false; } } return true; } // delete dex, odex, so files of a patch and also clear unused patches public void deletePatchExceptApk(String checksum) { FileUtils.removeFile(odexDir); clearAmigoDir(PatchApks.getInstance(context).patchFile(checksum)); Log.d(TAG, "deletePatchExceptApk: " + checksum); } public void clear() { FileUtils.removeFile(amigoDir); FileUtils.removeFile(odexDir); Log.d(TAG, "clear:"); } public void deleteAllPatches(String excludeChecksum) { FileUtils.removeFile(odexDir, new File(odexDir, excludeChecksum)); File excludeDir = new File(amigoDir, excludeChecksum); clearAmigoDir(excludeDir); Log.d(TAG, "deleted old patches"); } private void clearAmigoDir(File excludeFile) { File[] subFiles = amigoDir.listFiles(); if (subFiles == null) { return; } //always keep amigo.json File patchInfoFile = patchInfoFile(); for (File subFile : subFiles) { if (!subFile.equals(patchInfoFile) && !subFile.equals(excludeFile)) { FileUtils.removeFile(subFile, excludeFile); } } } }