/** * OpenAtlasForAndroid Project * The MIT License (MIT) Copyright (OpenAtlasForAndroid) 2015 Bunny Blue,achellies * <p> * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the "Software"), to deal in the Software * without restriction, including without limitation the rights to use, copy, modify, * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to the following conditions: * <p> * The above copyright notice and this permission notice shall be included in all copies * or substantial portions of the Software. * <p> * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * @author BunnyBlue **/ package com.openatlas.framework.bundlestorage; import android.content.res.AssetManager; import android.os.Build; import android.text.TextUtils; import com.openatlas.bundleInfo.BundleInfoList; import com.openatlas.dexopt.InitExecutor; import com.openatlas.framework.Framework; import com.openatlas.hack.OpenAtlasHacks; import com.openatlas.log.Logger; import com.openatlas.log.LoggerFactory; import com.openatlas.runtime.RuntimeVariables; import com.openatlas.util.ApkUtils; import com.openatlas.util.OpenAtlasFileLock; import com.openatlas.util.StringUtils; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import dalvik.system.DexClassLoader; import dalvik.system.DexFile; /*** * BundleArchiveRevision is Bundle Archive real implementation * ***/ public class BundleArchiveRevision { static final String BUNDLE_FILE_NAME = "bundle.zip"; static final String BUNDLE_LEX_FILE = "bundle.lex"; static final String BUNDLE_ODEX_FILE = "bundle.dex"; static final String FILE_PROTOCOL = "file:"; static final String REFERENCE_PROTOCOL = "reference:"; static final Logger log; private final File bundleFile; private ClassLoader dexClassLoader; private DexFile dexFile; private boolean isDexFileUsed; private Manifest manifest; private final File revisionDir; private final String revisionLocation; private final long revisionNum; private ZipFile zipFile; class BundleArchiveRevisionClassLoader extends DexClassLoader { /** * @param dexPath the list of jar/apk files containing classes and resources, delimited by File.pathSeparator, which defaults to ":" on Android * @param optimizedDirectory directory where optimized dex files should be written; must not be null * @param libraryPath the list of directories containing native libraries, delimited by File.pathSeparator; may be null * @param * **/ BundleArchiveRevisionClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, optimizedDirectory, libraryPath, parent); } @Override public String findLibrary(String name) { String findLibrary = super.findLibrary(name); if (!TextUtils.isEmpty(findLibrary)) { return findLibrary; } File findSoLibrary = BundleArchiveRevision.this .findSoLibrary(System.mapLibraryName(name)); if (findSoLibrary != null && findSoLibrary.exists()) { return findSoLibrary.getAbsolutePath(); } try { return (String) OpenAtlasHacks.ClassLoader_findLibrary.invoke( Framework.getSystemClassLoader(), name); } catch (Exception e) { e.printStackTrace(); return null; } } } public static class DexLoadException extends RuntimeException { /** * */ private static final long serialVersionUID = 1L; DexLoadException(String str) { super(str); } } static { log = LoggerFactory.getInstance("BundleArchiveRevision"); } BundleArchiveRevision(String location, long revisionNum, File revisionDir, InputStream inputStream) throws IOException { boolean withNativeLib=false; this.revisionNum = revisionNum; this.revisionDir = revisionDir; if (!this.revisionDir.exists()) { this.revisionDir.mkdirs(); } this.revisionLocation = FILE_PROTOCOL; this.bundleFile = new File(revisionDir, BUNDLE_FILE_NAME); ApkUtils.copyInputStreamToFile(inputStream, this.bundleFile); BundleInfoList instance = BundleInfoList.getInstance(); instance.dumpBundleInfos(); withNativeLib=instance.getHasSO(location); if (withNativeLib) { installSoLib(this.bundleFile); } updateMetadata(); } BundleArchiveRevision(String packageName, long revisionNum, File revisionDir, File archiveFile) throws IOException { boolean hasSO = false; this.revisionNum = revisionNum; this.revisionDir = revisionDir; BundleInfoList instance = BundleInfoList.getInstance(); if (instance == null || !instance.getHasSO(packageName)) { } else { hasSO = true; } if (!this.revisionDir.exists()) { this.revisionDir.mkdirs(); } if (archiveFile.canWrite()) { if (isSameDriver(revisionDir, archiveFile)) { this.revisionLocation = FILE_PROTOCOL; this.bundleFile = new File(revisionDir, BUNDLE_FILE_NAME); archiveFile.renameTo(this.bundleFile); } else { this.revisionLocation = FILE_PROTOCOL; this.bundleFile = new File(revisionDir, BUNDLE_FILE_NAME); ApkUtils.copyInputStreamToFile(new FileInputStream(archiveFile), this.bundleFile); } if (hasSO) { installSoLib(this.bundleFile); } } else if (Build.HARDWARE.toLowerCase().contains("mt6592") && archiveFile.getName().endsWith(".so")) { this.revisionLocation = FILE_PROTOCOL; this.bundleFile = new File(revisionDir, BUNDLE_FILE_NAME); Runtime.getRuntime().exec( String.format("ln -s %s %s", new Object[]{archiveFile.getAbsolutePath(), this.bundleFile.getAbsolutePath()})); if (hasSO) { installSoLib(archiveFile); } } else if (OpenAtlasHacks.LexFile == null || OpenAtlasHacks.LexFile.getmClass() == null) { this.revisionLocation = REFERENCE_PROTOCOL + archiveFile.getAbsolutePath(); this.bundleFile = archiveFile; if (hasSO) { installSoLib(archiveFile); } } else { this.revisionLocation = FILE_PROTOCOL; this.bundleFile = new File(revisionDir, BUNDLE_FILE_NAME); ApkUtils.copyInputStreamToFile(new FileInputStream(archiveFile), this.bundleFile); if (hasSO) { installSoLib(this.bundleFile); } } updateMetadata(); } BundleArchiveRevision(String location, long revisionNum, File revisionDir) throws IOException { File metaFile = new File(revisionDir, "meta"); if (metaFile.exists()) { DataInputStream dataInputStream = new DataInputStream( new FileInputStream(metaFile)); this.revisionLocation = dataInputStream.readUTF(); dataInputStream.close(); this.revisionNum = revisionNum; this.revisionDir = revisionDir; if (!this.revisionDir.exists()) { this.revisionDir.mkdirs(); } if (StringUtils .startWith(this.revisionLocation, REFERENCE_PROTOCOL)) { this.bundleFile = new File(StringUtils.substringAfter( this.revisionLocation, REFERENCE_PROTOCOL)); return; } else { this.bundleFile = new File(revisionDir, BUNDLE_FILE_NAME); return; } } throw new IOException("Could not find meta file in " + revisionDir.getAbsolutePath()); } void updateMetadata() throws IOException { File metaFile = new File(this.revisionDir, "meta"); DataOutputStream dataOutputStream = null; try { if (!metaFile.getParentFile().exists()) { metaFile.getParentFile().mkdirs(); } dataOutputStream = new DataOutputStream(new FileOutputStream(metaFile)); dataOutputStream.writeUTF(this.revisionLocation); dataOutputStream.flush(); { try { dataOutputStream.close(); return; } catch (IOException e) { e.printStackTrace(); return; } } } catch (IOException e) { throw new IOException("Could not save meta data " + metaFile.getAbsolutePath(), e); } finally { if (dataOutputStream != null) { try { dataOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } public long getRevisionNum() { return this.revisionNum; } public File getRevisionDir() { return this.revisionDir; } public File getRevisionFile() { return this.bundleFile; } public File findSoLibrary(String str) { File file = new File(String.format("%s%s%s%s", new Object[]{ this.revisionDir, File.separator, "lib", File.separator}), str); return (file.exists() && file.isFile()) ? file : null; } public boolean isDexOpted() { if (OpenAtlasHacks.LexFile == null || OpenAtlasHacks.LexFile.getmClass() == null) { return new File(this.revisionDir, BUNDLE_ODEX_FILE).exists(); } return new File(this.revisionDir, BUNDLE_LEX_FILE).exists(); } public synchronized void optDexFile() { if (!isDexOpted()) { if (OpenAtlasHacks.LexFile == null || OpenAtlasHacks.LexFile.getmClass() == null) { File oDexFile = new File(this.revisionDir, BUNDLE_ODEX_FILE); long currentTimeMillis = System.currentTimeMillis(); try { if (!OpenAtlasFileLock.getInstance().LockExclusive(oDexFile)) { log.error("Failed to get file lock for " + this.bundleFile.getAbsolutePath()); } if (oDexFile.length() <= 0) { InitExecutor.optDexFile( this.bundleFile.getAbsolutePath(), oDexFile.getAbsolutePath()); loadDex(oDexFile); OpenAtlasFileLock.getInstance().unLock(oDexFile); // "bundle archieve dexopt bundle " + // this.bundleFile.getAbsolutePath() + " cost time = " + // (System.currentTimeMillis() - currentTimeMillis) + // " ms"; } } catch (Throwable e) { log.error( "Failed optDexFile '" + this.bundleFile.getAbsolutePath() + "' >>> ", e); } finally { OpenAtlasFileLock mAtlasFileLock = OpenAtlasFileLock.getInstance(); mAtlasFileLock.unLock(oDexFile); } } else { DexClassLoader dexClassLoader = new DexClassLoader( this.bundleFile.getAbsolutePath(), this.revisionDir.getAbsolutePath(), null, ClassLoader.getSystemClassLoader()); } } } private synchronized void loadDex(File file) throws IOException { if (this.dexFile == null) { this.dexFile = DexFile.loadDex(this.bundleFile.getAbsolutePath(), file.getAbsolutePath(), 0); } } public void installSoLib(File archiveFile) { try { ZipFile zipFile = new ZipFile(archiveFile); Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry zipEntry = (ZipEntry) entries.nextElement(); String name = zipEntry.getName(); String abi ="armeabi";; if (Build.CPU_ABI.contains("x86")) { abi = "x86"; } if (name.indexOf(String.format("%s%s", new Object[]{"lib/", abi})) != -1) { abi = String .format("%s%s%s%s%s", this.revisionDir, File.separator, "lib", File.separator, name.substring( name.lastIndexOf(File.separator) + 1, name.length())); if (zipEntry.isDirectory()) { File abiFolder = new File(abi); if (!abiFolder.exists()) { abiFolder.mkdirs(); } } else { File abiFolder = new File(abi.substring(0, abi.lastIndexOf("/"))); if (!abiFolder.exists()) { abiFolder.mkdirs(); } BufferedOutputStream bufferedOutputStream = new BufferedOutputStream( new FileOutputStream(abi)); BufferedInputStream bufferedInputStream = new BufferedInputStream( zipFile.getInputStream(zipEntry)); byte[] bArr = new byte[4096]; for (int read = bufferedInputStream.read(bArr); read != -1; read = bufferedInputStream .read(bArr)) { bufferedOutputStream.write(bArr, 0, read); } bufferedOutputStream.close(); } } } zipFile.close(); } catch (Exception e) { e.printStackTrace(); } } public InputStream openAssetInputStream(String fileName) throws IOException { try { AssetManager assetManager = AssetManager.class .newInstance(); if (((Integer) OpenAtlasHacks.AssetManager_addAssetPath.invoke( assetManager, this.bundleFile.getAbsolutePath())) .intValue() != 0) { return assetManager.open(fileName); } } catch (Throwable e) { log.error("Exception while openNonAssetInputStream >>>", e); } return null; } public InputStream openNonAssetInputStream(String assetName) throws IOException { try { AssetManager assetManager = AssetManager.class .newInstance(); int intValue = ((Integer) OpenAtlasHacks.AssetManager_addAssetPath .invoke(assetManager, this.bundleFile.getAbsolutePath())) .intValue(); if (intValue != 0) { return assetManager.openNonAssetFd(intValue, assetName) .createInputStream(); } } catch (Throwable e) { log.error("Exception while openNonAssetInputStream >>>", e); } return null; } Class<?> findClass(String name, ClassLoader classLoader) throws ClassNotFoundException { try { if (OpenAtlasHacks.LexFile == null || OpenAtlasHacks.LexFile.getmClass() == null) { if (!isDexOpted()) { optDexFile(); } if (this.dexFile == null) { loadDex(new File(this.revisionDir, BUNDLE_ODEX_FILE)); } Class<?> loadClass = this.dexFile.loadClass(name, classLoader); this.isDexFileUsed = true; return loadClass; } if (this.dexClassLoader == null) { File file = new File(RuntimeVariables.androidApplication .getFilesDir().getParentFile(), "lib"); this.dexClassLoader = new BundleArchiveRevisionClassLoader( this.bundleFile.getAbsolutePath(), this.revisionDir.getAbsolutePath(), file.getAbsolutePath(), classLoader); } return (Class) OpenAtlasHacks.DexClassLoader_findClass.invoke( this.dexClassLoader, name); } catch (IllegalArgumentException e) { return null; } catch (InvocationTargetException e) { return null; } catch (Throwable e) { if (!(e instanceof ClassNotFoundException)) { if (e instanceof DexLoadException) { throw ((DexLoadException) e); } log.error("Exception while find class in archive revision: " + this.bundleFile.getAbsolutePath(), e); } return null; } } List<URL> getResources(String name) throws IOException { List<URL> arrayList = new ArrayList(); ensureZipFile(); if (!(this.zipFile == null || this.zipFile.getEntry(name) == null)) { try { arrayList.add(new URL("jar:" + this.bundleFile.toURL() + "!/" + name)); } catch (Throwable e) { throw new RuntimeException(e); } } return arrayList; } void close() throws Exception { if (this.zipFile != null) { this.zipFile.close(); } if (this.dexFile != null) { this.dexFile.close(); } } private boolean isSameDriver(File file, File file2) { return StringUtils .equals(StringUtils.substringBetween(file.getAbsolutePath(), "/", "/"), StringUtils.substringBetween( file2.getAbsolutePath(), "/", "/")); } private void ensureZipFile() throws IOException { if (this.zipFile == null) { this.zipFile = new ZipFile(this.bundleFile, 1); } } }