All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.reandroid.apk.ApkModule Maven / Gradle / Ivy

The newest version!
/*
  *  Copyright (C) 2022 github.com/REAndroid
  *
  *  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.reandroid.apk;

import com.reandroid.archive.*;
import com.reandroid.archive.block.ApkSignatureBlock;
import com.reandroid.archive.io.ArchiveFileEntrySource;
import com.reandroid.archive.writer.ApkByteWriter;
import com.reandroid.archive.writer.ApkFileWriter;
import com.reandroid.archive.writer.ApkStreamWriter;
import com.reandroid.arsc.ApkFile;
import com.reandroid.arsc.array.PackageArray;
import com.reandroid.arsc.base.Block;
import com.reandroid.arsc.chunk.PackageBlock;
import com.reandroid.arsc.chunk.TableBlock;
import com.reandroid.arsc.chunk.TypeBlock;
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock;
import com.reandroid.arsc.chunk.xml.ResXmlDocument;
import com.reandroid.arsc.container.SpecTypePair;
import com.reandroid.arsc.item.TableString;
import com.reandroid.arsc.model.FrameworkTable;
import com.reandroid.arsc.pool.TableStringPool;
import com.reandroid.arsc.value.Entry;
import com.reandroid.arsc.value.ResConfig;
import com.reandroid.identifiers.PackageIdentifier;
import com.reandroid.utils.collection.ArrayCollection;
import com.reandroid.utils.collection.CollectionUtil;
import com.reandroid.xml.XMLDocument;
import com.reandroid.xml.XMLElement;

import java.io.*;
import java.util.*;
import java.util.function.Predicate;
import java.util.zip.ZipEntry;

public class ApkModule implements ApkFile, Closeable {
    private String moduleName;
    private final ZipEntryMap zipEntryMap;
    private boolean loadDefaultFramework = true;
    private boolean mDisableLoadFramework = false;
    private TableBlock mTableBlock;
    private InputSource mTableOriginalSource;
    private AndroidManifestBlock mManifestBlock;
    private InputSource mManifestOriginalSource;
    private final UncompressedFiles mUncompressedFiles;
    private APKLogger apkLogger;
    private ApkType mApkType;
    private ApkSignatureBlock apkSignatureBlock;
    private Integer preferredFramework;
    private Closeable mCloseable;
    private final List mExternalFrameworks;

    private final Map mTagMaps;

    public ApkModule(String moduleName, ZipEntryMap zipEntryMap){
        this.moduleName = moduleName;
        this.zipEntryMap = zipEntryMap;
        this.mUncompressedFiles=new UncompressedFiles();
        this.mUncompressedFiles.addPath(zipEntryMap);
        this.mExternalFrameworks = new ArrayCollection<>();
        this.zipEntryMap.setModuleName(moduleName);
        this.mTagMaps = new HashMap<>();
    }
    public ApkModule(ZipEntryMap zipEntryMap){
        this("base", zipEntryMap);
    }
    public ApkModule(){
        this("base", new ZipEntryMap());
    }

    public void putTag(Object key, Object item){
        mTagMaps.put(key, item);
    }
    public Object getTag(Object key){
        return mTagMaps.get(key);
    }
    public Object removeTag(Object key){
        return mTagMaps.remove(key);
    }
    public void clearTags(){
        mTagMaps.clear();
    }
    public void addExternalFramework(File frameworkFile) throws IOException {
        if(frameworkFile == null){
            return;
        }
        logMessage("Loading external framework: " + frameworkFile);
        FrameworkApk framework = FrameworkApk.loadTableBlock(frameworkFile);
        framework.setAPKLogger(getApkLogger());
        addExternalFramework(framework);
    }
    public void addExternalFramework(ApkModule apkModule){
        if(apkModule == null || apkModule == this || !apkModule.hasTableBlock()){
            return;
        }
        addExternalFramework(apkModule.getTableBlock());
    }
    public void addExternalFramework(TableBlock tableBlock){
        if(tableBlock == null
                || tableBlock.getApkFile() == this
                || mExternalFrameworks.contains(tableBlock)){
            return;
        }
        mExternalFrameworks.add(tableBlock);
        updateExternalFramework();
    }
    public String refreshTable(){
        TableBlock tableBlock = this.mTableBlock;
        if(tableBlock != null){
            return tableBlock.refreshFull();
        }
        return null;
    }
    public String refreshManifest(){
        AndroidManifestBlock manifestBlock = this.mManifestBlock;
        if(manifestBlock != null){
            return manifestBlock.refreshFull();
        }
        return null;
    }
    public void validateResourceNames(){
        if(!hasTableBlock()){
            return;
        }
        logMessage("Validating resource names ...");
        TableBlock tableBlock = getTableBlock();
        for(PackageBlock packageBlock : tableBlock.listPackages()){
            validateResourceNames(packageBlock);
        }
    }
    public void validateResourceNames(PackageBlock packageBlock){
        PackageIdentifier packageIdentifier = new PackageIdentifier();
        packageIdentifier.load(packageBlock);
        if(!packageIdentifier.hasDuplicateResources()){
            return;
        }
        logMessage("Renaming duplicate resources ... ");
        packageIdentifier.ensureUniqueResourceNames();
        packageIdentifier.setResourceNamesToPackage(packageBlock);
    }
    public ApkSignatureBlock getApkSignatureBlock() {
        return apkSignatureBlock;
    }
    public void setApkSignatureBlock(ApkSignatureBlock apkSignatureBlock) {
        this.apkSignatureBlock = apkSignatureBlock;
    }

    public boolean hasSignatureBlock(){
        return getApkSignatureBlock() != null;
    }

    public void dumpSignatureInfoFiles(File directory) throws IOException{
        ApkSignatureBlock apkSignatureBlock = getApkSignatureBlock();
        if(apkSignatureBlock == null){
            throw new IOException("Don't have signature block");
        }
        apkSignatureBlock.writeSplitRawToDirectory(directory);
    }
    public void dumpSignatureBlock(File file) throws IOException{
        ApkSignatureBlock apkSignatureBlock = getApkSignatureBlock();
        if(apkSignatureBlock == null){
            throw new IOException("Don't have signature block");
        }
        apkSignatureBlock.writeRaw(file);
    }

    public void scanSignatureInfoFiles(File directory) throws IOException{
        if(!directory.isDirectory()){
            throw new IOException("No such directory: " + directory);
        }
        ApkSignatureBlock apkSignatureBlock = this.apkSignatureBlock;
        if(apkSignatureBlock == null){
            apkSignatureBlock = new ApkSignatureBlock();
        }
        apkSignatureBlock.scanSplitFiles(directory);
        setApkSignatureBlock(apkSignatureBlock);
    }
    public void loadSignatureBlock(File file) throws IOException{
        if(!file.isFile()){
            throw new IOException("No such file: " + file);
        }
        ApkSignatureBlock apkSignatureBlock = this.apkSignatureBlock;
        if(apkSignatureBlock == null){
            apkSignatureBlock = new ApkSignatureBlock();
        }
        apkSignatureBlock.read(file);
        setApkSignatureBlock(apkSignatureBlock);
    }

    public String getSplit(){
        if(!hasAndroidManifest()){
            return null;
        }
        return getAndroidManifest().getSplit();
    }
    public List getLoadedFrameworks(){
        List results = new ArrayCollection<>();
        if(!hasTableBlock()){
            return results;
        }
        TableBlock tableBlock = getTableBlock(false);
        results.addAll(tableBlock.getFrameWorks());
        return results;
    }
    public boolean isFrameworkVersionLoaded(Integer version){
        if(version == null){
            return false;
        }
        for(TableBlock tableBlock : getLoadedFrameworks()){
            if(!(tableBlock instanceof FrameworkTable)){
                continue;
            }
            FrameworkTable frame = (FrameworkTable) tableBlock;
            if(version.equals(frame.getVersionCode())){
                return true;
            }
        }
        return false;
    }
    public FrameworkApk getLoadedFramework(Integer version, boolean onlyAndroid){
        for(TableBlock tableBlock : getLoadedFrameworks()){
            if(!(tableBlock instanceof FrameworkTable)){
                continue;
            }
            FrameworkTable frame = (FrameworkTable) tableBlock;
            if(onlyAndroid && !isAndroid(frame)){
                continue;
            }
            if(version == null || version.equals(frame.getVersionCode())){
                return (FrameworkApk) frame.getApkFile();
            }
        }
        return null;
    }
    public FrameworkApk initializeAndroidFramework(Integer version) throws IOException {
        TableBlock tableBlock = getTableBlock(false);
        return initializeAndroidFramework(tableBlock, version);
    }
    public FrameworkApk initializeAndroidFramework(TableBlock tableBlock, Integer version) throws IOException {
        if(mDisableLoadFramework || tableBlock == null || isAndroid(tableBlock)){
            return null;
        }
        FrameworkApk exist = getLoadedFramework(version, true);
        if(exist != null){
            return exist;
        }
        logMessage("Initializing android framework ...");
        FrameworkApk frameworkApk;
        if(version == null){
            logMessage("Can not read framework version, loading latest");
            frameworkApk = AndroidFrameworks.getLatest();
        }else {
            logMessage("Loading android framework for version: " + version);
            frameworkApk = AndroidFrameworks.getBestMatch(version);
        }
        FrameworkTable frameworkTable = frameworkApk.getTableBlock();
        tableBlock.addFramework(frameworkTable);
        logMessage("Initialized framework: " + frameworkApk.getName()
                + " (" + frameworkApk.getVersionName() + ")");
        return frameworkApk;
    }
    public FrameworkApk initializeAndroidFramework(XMLDocument xmlDocument) throws IOException {
        if(this.preferredFramework != null){
            return initializeAndroidFramework(preferredFramework);
        }
        if(isAndroidCoreApp(xmlDocument)){
            logMessage("Looks framework itself, skip loading frameworks");
            return null;
        }
        Integer version = readVersionCode(xmlDocument);
        return initializeAndroidFramework(version);
    }
    private boolean isAndroid(TableBlock tableBlock){
        if(tableBlock instanceof FrameworkTable){
            FrameworkTable frameworkTable = (FrameworkTable) tableBlock;
            return frameworkTable.isAndroid();
        }
        return false;
    }
    private boolean isAndroidCoreApp(XMLDocument manifestDocument){
        XMLElement root = manifestDocument.getDocumentElement();
        if(root == null){
            return false;
        }
        if(!"android".equals(root.getAttributeValue("package"))){
            return false;
        }
        String coreApp = root.getAttributeValue("coreApp");
        return "true".equals(coreApp);
    }
    private Integer readVersionCode(XMLDocument xmlDocument){
        if(xmlDocument == null){
            return null;
        }
        XMLElement manifestRoot = xmlDocument.getDocumentElement();
        if(manifestRoot == null){
            logMessage("WARN: Manifest root not found");
            return null;
        }
        String versionString = manifestRoot.getAttributeValue("android:compileSdkVersion");
        Integer version = null;
        if(versionString!=null){
            version = safeParseInteger(versionString);
        }
        if(version == null){
            versionString = manifestRoot.getAttributeValue("platformBuildVersionCode");
            if(versionString!=null){
                version = safeParseInteger(versionString);
            }
        }
        Integer target = null;
        Iterator iterator = manifestRoot
                .getElements(AndroidManifestBlock.TAG_uses_sdk);
        while (iterator.hasNext()){
            XMLElement element = iterator.next();
            versionString = element.getAttributeValue("android:targetSdkVersion");
            if(versionString != null){
                target = safeParseInteger(versionString);
            }
        }
        if(version == null){
            version = target;
        }else if(target != null && target > version){
            version = target;
        }
        return version;
    }
    private Integer safeParseInteger(String versionString) {
        try{
            return Integer.parseInt(versionString);
        }catch (NumberFormatException exception){
            logMessage("NumberFormatException on manifest version reading: '"
                    +versionString+"': "+exception.getMessage());
            return null;
        }
    }

    public void setPreferredFramework(Integer version) {
        if(version != null && version.equals(preferredFramework)){
            return;
        }
        this.preferredFramework = version;
        if(version == null || mTableBlock == null){
            return;
        }
        if(isFrameworkVersionLoaded(version)){
            return;
        }
        logMessage("Initializing preferred framework: " + version);
        mTableBlock.clearFrameworks();
        FrameworkApk frameworkApk = AndroidFrameworks.getBestMatch(version);
        AndroidFrameworks.setCurrent(frameworkApk);
        mTableBlock.addFramework(frameworkApk.getTableBlock());
        logMessage("Initialized framework: " + frameworkApk.getVersionCode());
    }

    public Integer getAndroidFrameworkVersion(){
        if(preferredFramework != null){
            return preferredFramework;
        }
        if(!hasAndroidManifest()){
            return null;
        }
        AndroidManifestBlock manifest = getAndroidManifest();
        Integer version = manifest.getCompileSdkVersion();
        if(version == null){
            version = manifest.getPlatformBuildVersionCode();
        }
        Integer target = manifest.getTargetSdkVersion();
        if(version == null){
            version = target;
        }else if(target != null && target > version){
            version = target;
        }
        return version;
    }
    public void removeResFilesWithEntry(int resourceId) {
        removeResFilesWithEntry(resourceId, null, true);
    }
    public void removeResFilesWithEntry(int resourceId, ResConfig resConfig, boolean trimEntryArray) {
        List removedList = removeResFiles(resourceId, resConfig);
        SpecTypePair specTypePair = null;
        for(Entry entry:removedList){
            if(entry == null || entry.isNull()){
                continue;
            }
            if(trimEntryArray && specTypePair==null){
                specTypePair = entry.getTypeBlock().getParentSpecTypePair();
            }
            entry.setNull(true);
        }
        if(specTypePair!=null){
            specTypePair.removeNullEntries(resourceId);
        }
    }
    public List removeResFiles(int resourceId) {
        return removeResFiles(resourceId, null);
    }
    public List removeResFiles(int resourceId, ResConfig resConfig) {
        ArrayCollection results = new ArrayCollection<>();
        if(resourceId == 0 && resConfig == null){
            return results;
        }
        List resFileList = listResFiles(resourceId, resConfig);
        ZipEntryMap zipEntryMap = getZipEntryMap();
        for(ResFile resFile:resFileList){
            results.addAll(resFile.iterator());
            zipEntryMap.remove(resFile.getInputSource());
        }
        return results;
    }
    public XMLDocument decodeXMLFile(String path) throws IOException {
        ResXmlDocument resXmlDocument = loadResXmlDocument(path);
        AndroidManifestBlock manifest = getAndroidManifest();
        int pkgId = manifest.guessCurrentPackageId();
        if(pkgId != 0 && hasTableBlock()){
            PackageBlock packageBlock = getTableBlock().pickOne(pkgId);
            if(packageBlock != null){
                resXmlDocument.setPackageBlock(packageBlock);
            }
        }
        return resXmlDocument.decodeToXml();
    }
    public List listDexFiles(){
        List results = new ArrayCollection<>();
        for(InputSource source: getInputSources()){
            if(DexFileInputSource.isDexName(source.getAlias())){
                DexFileInputSource inputSource;
                if(source instanceof DexFileInputSource){
                    inputSource = (DexFileInputSource)source;
                }else {
                    inputSource = new DexFileInputSource(source.getAlias(), source);
                }
                results.add(inputSource);
            }
        }
        DexFileInputSource.sort(results);
        return results;
    }
    public boolean isBaseModule(){
        if(!hasAndroidManifest()){
            return false;
        }
        AndroidManifestBlock manifest;
        try {
            manifest= getAndroidManifest();
            return !(manifest.isSplit() || manifest.getMainActivity()==null);
        } catch (Exception ignored) {
            return false;
        }
    }
    public String getModuleName(){
        return moduleName;
    }
    public void setModuleName(String moduleName){
        if(moduleName == null){
            throw new NullPointerException();
        }
        this.moduleName = moduleName;
        this.zipEntryMap.setModuleName(moduleName);
    }
    public void writeApk(File file) throws IOException {
        writeApk(file, null);
    }
    public void writeApk(File file, WriteProgress progress) throws IOException {
        ApkFileWriter writer = createApkFileWriter(file);
        writer.setWriteProgress(progress);
        writer.write();
    }
    public byte[] writeApkBytes() throws IOException {
        ApkByteWriter writer = createApkByteWriter();
        writer.write();
        return writer.toByteArray();
    }
    public void writeApk(OutputStream outputStream) throws IOException {
        createApkStreamWriter(outputStream).write();
    }
    public ApkFileWriter createApkFileWriter(File file) throws IOException {
        ZipEntryMap zipEntryMap = getZipEntryMap();
        UncompressedFiles uf = getUncompressedFiles();
        uf.apply(zipEntryMap);
        ApkFileWriter writer = new ApkFileWriter(file, zipEntryMap.toArray(true));
        writer.setAPKLogger(getApkLogger());
        writer.setApkSignatureBlock(getApkSignatureBlock());
        writer.setArchiveInfo(zipEntryMap.getArchiveInfo());
        return writer;
    }
    public ApkByteWriter createApkByteWriter() {
        ZipEntryMap zipEntryMap = getZipEntryMap();
        UncompressedFiles uf = getUncompressedFiles();
        uf.apply(zipEntryMap);
        ApkByteWriter writer = new ApkByteWriter(zipEntryMap.toArray(true));
        writer.setAPKLogger(getApkLogger());
        writer.setApkSignatureBlock(getApkSignatureBlock());
        writer.setArchiveInfo(zipEntryMap.getArchiveInfo());
        return writer;
    }
    public ApkStreamWriter createApkStreamWriter(OutputStream outputStream) {
        ZipEntryMap zipEntryMap = getZipEntryMap();
        UncompressedFiles uf = getUncompressedFiles();
        uf.apply(zipEntryMap);
        ApkStreamWriter writer = new ApkStreamWriter(outputStream,
                zipEntryMap.toArray(true));
        writer.setAPKLogger(getApkLogger());
        writer.setApkSignatureBlock(getApkSignatureBlock());
        writer.setArchiveInfo(zipEntryMap.getArchiveInfo());
        return writer;
    }
    public void uncompressNonXmlResFiles() {
        for(ResFile resFile:listResFiles()){
            if(resFile.isBinaryXml()){
                continue;
            }
            resFile.getInputSource().setMethod(ZipEntry.STORED);
        }
    }
    public UncompressedFiles getUncompressedFiles(){
        return mUncompressedFiles;
    }
    public void removeDir(String dirName){
        getZipEntryMap().removeDir(dirName);
    }
    public void validateResourcesDir() {
        List resFileList = listResFiles();
        Set existPaths=new HashSet<>();
        InputSource[] sourceList = getInputSources();
        for(InputSource inputSource:sourceList){
            existPaths.add(inputSource.getAlias());
        }
        for(ResFile resFile:resFileList){
            String path = resFile.getFilePath();
            String pathNew = resFile.validateTypeDirectoryName();
            if(pathNew == null || pathNew.equals(path)){
                continue;
            }
            if(existPaths.contains(pathNew)){
                continue;
            }
            existPaths.remove(path);
            existPaths.add(pathNew);
            resFile.setFilePath(pathNew);
            if(resFile.getInputSource().getMethod() == ZipEntry.STORED){
                getUncompressedFiles().replacePath(path, pathNew);
            }
            logVerbose("Dir validated: '"+path+"' -> '"+pathNew+"'");
        }
        getTableBlock().refresh();
    }
    public void setResourcesRootDir(String dirName) {
        List resFileList = listResFiles();
        Set existPaths=new HashSet<>();
        InputSource[] sourceList = getInputSources();
        for(InputSource inputSource:sourceList){
            existPaths.add(inputSource.getAlias());
        }
        for(ResFile resFile:resFileList){
            String path=resFile.getFilePath();
            String pathNew=ApkUtil.replaceRootDir(path, dirName);
            if(existPaths.contains(pathNew)){
                continue;
            }
            existPaths.remove(path);
            existPaths.add(pathNew);
            resFile.setFilePath(pathNew);
            if(resFile.getInputSource().getMethod() == ZipEntry.STORED){
                getUncompressedFiles().replacePath(path, pathNew);
            }
            logVerbose("Root changed: '"+path+"' -> '"+pathNew+"'");
        }
        getTableBlock().refresh();
    }
    public List listResFiles() {
        return listResFiles(0, null);
    }
    public List listResFiles(int resourceId, ResConfig resConfig) {
        List results = new ArrayCollection<>();
        TableBlock tableBlock = getTableBlock();
        if (tableBlock == null){
            return results;
        }
        TableStringPool stringPool= tableBlock.getStringPool();
        for(InputSource inputSource : getInputSources()){
            String name = inputSource.getAlias();
            Iterator iterator = stringPool.getAll(name);
            while (iterator.hasNext()){
                TableString tableString = iterator.next();
                List entryList = filterResFileEntries(tableString, resourceId, resConfig);
                if(!entryList.isEmpty()) {
                    ResFile resFile = new ResFile(inputSource, entryList);
                    results.add(resFile);
                }
            }
        }
        return results;
    }
    public boolean removeResFile(String path) {
        return removeResFile(path, true);
    }
    public boolean removeResFile(String path, boolean keepResourceId) {
        InputSource inputSource = getInputSource(path);
        if(inputSource == null) {
            return false;
        }
        ResFile resFile = getResFile(path);
        if(resFile == null) {
            return false;
        }
        resFile.delete(keepResourceId);
        removeInputSource(path);
        return true;
    }
    public ResFile getResFile(String path) {
        InputSource inputSource = getInputSource(path);
        if(inputSource == null) {
            return null;
        }
        List entryList = listReferencedEntries(path);
        if(entryList.isEmpty()) {
            return null;
        }
        return new ResFile(inputSource, entryList);
    }

    public List listReferencedEntries(String path) {
        ArrayCollection results = new ArrayCollection<>();
        TableBlock tableBlock = getTableBlock();
        if (tableBlock != null) {
            TableStringPool stringPool = tableBlock.getStringPool();
            Iterator iterator = stringPool.getAll(path);
            Predicate filter = entry -> entry.isScalar() &&
                    TypeBlock.canHaveResourceFile(entry.getTypeName());
            while (iterator.hasNext()) {
                results.addAll(iterator.next().getEntries(filter));
            }
        }
        return results;
    }
    private List filterResFileEntries(TableString tableString, int resourceId, ResConfig resConfig){
        Iterator itr = tableString.getEntries(item -> {
            if(!item.isScalar() ||
                    !TypeBlock.canHaveResourceFile(item.getTypeName())){
                return false;
            }
            if(resourceId != 0 && resourceId != item.getResourceId()){
                return false;
            }
            return resConfig == null || resConfig.equals(item.getResConfig());
        });
        return CollectionUtil.toList(itr);
    }
    public int getVersionCode() {
        AndroidManifestBlock manifestBlock = getAndroidManifest();
        if(manifestBlock != null) {
            Integer versionCode = manifestBlock.getVersionCode();
            if(versionCode != null) {
                return versionCode;
            }
        }
        return 0;
    }
    public String getPackageName(){
        if(hasAndroidManifest()){
            return getAndroidManifest().getPackageName();
        }
        if(!hasTableBlock()){
            return null;
        }
        TableBlock tableBlock=getTableBlock();
        PackageArray pkgArray = tableBlock.getPackageArray();
        PackageBlock pkg = pkgArray.get(0);
        if(pkg==null){
            return null;
        }
        return pkg.getName();
    }
    public void setPackageName(String name) {
        String old=getPackageName();
        if(hasAndroidManifest()){
            getAndroidManifest().setPackageName(name);
        }
        if(!hasTableBlock()){
            return;
        }
        TableBlock tableBlock=getTableBlock();
        PackageArray pkgArray = tableBlock.getPackageArray();
        for(PackageBlock pkg:pkgArray.listItems()){
            if(pkgArray.size()==1){
                pkg.setName(name);
                continue;
            }
            String pkgName=pkg.getName();
            if(pkgName.startsWith(old)){
                pkgName=pkgName.replace(old, name);
                pkg.setName(pkgName);
            }
        }
    }
    // Use hasAndroidManifest
    @Deprecated
    public boolean hasAndroidManifestBlock(){
        return hasAndroidManifest();
    }
    public boolean hasAndroidManifest(){
        return mManifestBlock!=null
                || getZipEntryMap().getInputSource(AndroidManifestBlock.FILE_NAME)!=null;
    }
    public boolean hasTableBlock(){
        return mTableBlock!=null
                || getZipEntryMap().getInputSource(TableBlock.FILE_NAME)!=null;
    }
    public void destroy(){
        getZipEntryMap().clear();
        AndroidManifestBlock manifestBlock = this.mManifestBlock;
        if(manifestBlock!=null){
            manifestBlock.destroy();
            this.mManifestBlock = null;
        }
        TableBlock tableBlock = this.mTableBlock;
        if(tableBlock!=null){
            mExternalFrameworks.clear();
            tableBlock.clear();
            this.mTableBlock = null;
        }
        try {
            close();
        } catch (IOException ignored) {
        }
    }
    public void setManifest(AndroidManifestBlock manifestBlock){
        ZipEntryMap archive = getZipEntryMap();
        if(manifestBlock==null){
            mManifestBlock = null;
            mManifestOriginalSource = null;
            archive.remove(AndroidManifestBlock.FILE_NAME);
            return;
        }
        manifestBlock.setApkFile(this);
        BlockInputSource source =
                new BlockInputSource<>(AndroidManifestBlock.FILE_NAME, manifestBlock);
        source.setMethod(ZipEntry.STORED);
        source.setSort(0);
        archive.add(source);
        mManifestBlock = manifestBlock;
    }
    public void setTableBlock(TableBlock tableBlock){
        ZipEntryMap archive = getZipEntryMap();
        if(tableBlock == null){
            mTableBlock = null;
            mTableOriginalSource = null;
            archive.remove(TableBlock.FILE_NAME);
            unlinkLoadedManifest();
            return;
        }
        tableBlock.setApkFile(this);
        BlockInputSource source =
                new BlockInputSource<>(TableBlock.FILE_NAME, tableBlock);
        archive.add(source);
        source.setMethod(ZipEntry.STORED);
        source.setSort(1);
        getUncompressedFiles().addPath(source);
        mTableBlock = tableBlock;
        updateExternalFramework();
        ensureLoadedManifestLinked();
    }
    public boolean ensureTableBlock() {
        if(!hasTableBlock()) {
            setTableBlock(TableBlock.createEmpty());
            return true;
        }
        return false;
    }
    /**
     * Use getAndroidManifest()
     * */
    @Deprecated
    public AndroidManifestBlock getAndroidManifestBlock(){
        return getAndroidManifest();
    }
    @Override
    public AndroidManifestBlock getAndroidManifest() {
        if(mManifestBlock!=null){
            return mManifestBlock;
        }
        InputSource inputSource = getInputSource(AndroidManifestBlock.FILE_NAME);
        if(inputSource == null){
            return null;
        }
        setManifestOriginalSource(inputSource);
        InputStream inputStream;
        try {
            inputStream = inputSource.openStream();
            AndroidManifestBlock manifestBlock = AndroidManifestBlock.load(inputStream);
            inputStream.close();
            this.mManifestBlock = manifestBlock;
            BlockInputSource blockInputSource = new BlockInputSource<>(
                    inputSource.getName(),manifestBlock);
            blockInputSource.copyAttributes(inputSource);
            addInputSource(blockInputSource);
            ensureLoadedManifestLinked();
            onManifestBlockLoaded(manifestBlock);
        } catch (IOException exception) {
            throw new IllegalArgumentException(exception);
        }
        return mManifestBlock;
    }
    private void onManifestBlockLoaded(AndroidManifestBlock manifestBlock){
        initializeApkType(manifestBlock);
    }
    public TableBlock getTableBlock(boolean initFramework) {
        TableBlock tableBlock = this.mTableBlock;
        if(tableBlock == null){
            if(!hasTableBlock()){
                return null;
            }
            try {
                tableBlock = loadTableBlock();
                this.mTableBlock = tableBlock;
                if(initFramework && loadDefaultFramework){
                    Integer version = getAndroidFrameworkVersion();
                    initializeAndroidFramework(tableBlock, version);
                }
                updateExternalFramework();
            } catch (IOException exception) {
                throw new IllegalArgumentException(exception);
            }
            ensureLoadedManifestLinked();
        }
        return tableBlock;
    }
    private void ensureLoadedManifestLinked() {
        TableBlock tableBlock = this.mTableBlock;
        if(tableBlock == null) {
            return;
        }
        AndroidManifestBlock manifestBlock = this.mManifestBlock;
        if(manifestBlock == null) {
            return;
        }
        PackageBlock packageBlock = manifestBlock.getPackageBlock();
        if(packageBlock != null) {
            TableBlock linkedTable = packageBlock.getTableBlock();
            if(linkedTable == tableBlock) {
                return;
            }
        }
        packageBlock = tableBlock.pickOne(manifestBlock.guessCurrentPackageId());
        if(packageBlock == null) {
            packageBlock = tableBlock.pickOne();
        }
        if(packageBlock != null) {
            manifestBlock.setPackageBlock(packageBlock);
        }
        manifestBlock.setApkFile(this);
        ensureFrameworkLinked();
    }
    private void unlinkLoadedManifest() {
        AndroidManifestBlock manifestBlock = this.mManifestBlock;
        if(manifestBlock == null) {
            return;
        }
        manifestBlock.setPackageBlock(null);
        manifestBlock.setApkFile(null);
    }
    private void ensureFrameworkLinked() {
        if(mDisableLoadFramework) {
            return;
        }
        TableBlock tableBlock = this.mTableBlock;
        if(tableBlock == null ||
                tableBlock instanceof FrameworkTable ||
                isAndroid(tableBlock)) {
            return;
        }
        Integer preferred = this.preferredFramework;
        if(preferred != null || (mManifestBlock != null && !tableBlock.hasFramework())) {
            try {
                initializeAndroidFramework(tableBlock, preferred);
            } catch (IOException ignored) {
            }
        }
    }
    private void updateExternalFramework(){
        TableBlock tableBlock = mTableBlock;
        if(tableBlock == null){
            return;
        }
        for(TableBlock framework : mExternalFrameworks){
            tableBlock.addFramework(framework);
        }
    }
    public void discardManifestChanges(){
        getZipEntryMap().add(getManifestOriginalSource());
    }
    public void keepManifestChanges(){
        mManifestOriginalSource = null;
    }
    public InputSource getManifestOriginalSource(){
        InputSource inputSource = this.mManifestOriginalSource;
        if(inputSource == null){
            inputSource = getInputSource(AndroidManifestBlock.FILE_NAME);
            mManifestOriginalSource = inputSource;
        }
        return inputSource;
    }
    private void setManifestOriginalSource(InputSource inputSource){
        if(mManifestOriginalSource == null
                && !(inputSource instanceof BlockInputSource)){
            mManifestOriginalSource = inputSource;
        }
    }
    public void discardTableBlockChanges(){
        getZipEntryMap().add(getTableOriginalSource());
    }
    public void keepTableBlockChanges(){
        mTableOriginalSource = null;
    }
    public InputSource getTableOriginalSource(){
        InputSource inputSource = this.mTableOriginalSource;
        if(inputSource == null){
            inputSource = getInputSource(TableBlock.FILE_NAME);
            mTableOriginalSource = inputSource;
        }
        return inputSource;
    }
    private void setTableOriginalSource(InputSource inputSource){
        if(mTableOriginalSource == null
                && !(inputSource instanceof BlockInputSource)){
            mTableOriginalSource = inputSource;
        }
    }
    @Override
    public TableBlock getTableBlock() {
        if(mTableBlock != null){
            return mTableBlock;
        }
        checkExternalFramework();
        checkSelfFramework();
        return getTableBlock(!mDisableLoadFramework);
    }
    @Override
    public TableBlock getLoadedTableBlock(){
        return mTableBlock;
    }
    private void checkExternalFramework(){
        if(mDisableLoadFramework || preferredFramework != null){
            return;
        }
        if(mExternalFrameworks.size() == 0){
            return;
        }
        mDisableLoadFramework = true;
    }
    private void checkSelfFramework(){
        if(mDisableLoadFramework || preferredFramework != null){
            return;
        }
        AndroidManifestBlock manifest = getAndroidManifest();
        if(manifest == null){
            return;
        }
        if(manifest.isCoreApp() == null
                || !"android".equals(manifest.getPackageName())){
            return;
        }
        if(manifest.guessCurrentPackageId() != 0x01){
            return;
        }
        logMessage("Looks like framework apk, skip loading framework");
        mDisableLoadFramework = true;
    }

    @Override
    public ResXmlDocument getResXmlDocument(String path) {
        InputSource inputSource = getInputSource(path);
        if(inputSource != null){
            try {
                return loadResXmlDocument(inputSource);
            } catch (IOException ignored) {
            }
        }
        return null;
    }
    @Override
    public ResXmlDocument loadResXmlDocument(String path) throws IOException{
        InputSource inputSource = getInputSource(path);
        if(inputSource == null){
            throw new FileNotFoundException("No such file in apk: " + path);
        }
        return loadResXmlDocument(inputSource);
    }
    public ResXmlDocument loadResXmlDocument(InputSource inputSource) throws IOException{
        ResXmlDocument resXmlDocument = null;
        if(inputSource instanceof BlockInputSource){
            Block block = ((BlockInputSource) inputSource).getBlock();
            if(block instanceof ResXmlDocument){
                resXmlDocument = (ResXmlDocument) block;
            }
        }
        if(resXmlDocument == null){
            resXmlDocument = new ResXmlDocument();
            resXmlDocument.readBytes(inputSource.openStream());
        }
        resXmlDocument.setApkFile(this);
        if(resXmlDocument.getPackageBlock() == null){
            resXmlDocument.setPackageBlock(findPackageForPath(inputSource.getAlias()));
        }
        return resXmlDocument;
    }
    private PackageBlock findPackageForPath(String path) {
        TableBlock tableBlock = getTableBlock();
        if(tableBlock == null){
            return null;
        }
        if(tableBlock.size() == 1){
            return tableBlock.get(0);
        }
        PackageBlock packageBlock = CollectionUtil.getFirst(
                tableBlock.getStringPool().getUsers(PackageBlock.class, path));
        if(packageBlock == null){
            packageBlock = tableBlock.pickOne();
        }
        return packageBlock;
    }
    public ApkType getApkType(){
        if(mApkType!=null){
            return mApkType;
        }
        return initializeApkType(mManifestBlock);
    }
    public void setApkType(ApkType apkType){
        this.mApkType = apkType;
    }
    private ApkType initializeApkType(AndroidManifestBlock manifestBlock){
        if(mApkType!=null){
            return mApkType;
        }
        ApkType apkType = null;
        if(manifestBlock!=null){
            apkType = manifestBlock.guessApkType();
        }
        if(apkType != null){
            mApkType = apkType;
        }else {
            apkType = ApkType.UNKNOWN;
        }
        return apkType;
    }

    // If we need TableStringPool only, this loads pool without
    // loading packages and other chunk blocks for faster and less memory usage
    public TableStringPool getVolatileTableStringPool() throws IOException{
        if(mTableBlock!=null){
            return mTableBlock.getStringPool();
        }
        InputSource inputSource = getInputSource(TableBlock.FILE_NAME);
        if(inputSource==null){
            throw new IOException("Module don't have: "+TableBlock.FILE_NAME);
        }
        if((inputSource instanceof ArchiveFileEntrySource)
                ||(inputSource instanceof FileInputSource)){
            InputStream inputStream = inputSource.openStream();
            TableStringPool stringPool = TableStringPool.readFromTable(inputStream);
            inputStream.close();
            return stringPool;
        }
        return getTableBlock().getStringPool();
    }
    TableBlock loadTableBlock() throws IOException {
        InputSource inputSource = getInputSource(TableBlock.FILE_NAME);
        if(inputSource == null){
            throw new IOException("Entry not found: "+TableBlock.FILE_NAME);
        }
        TableBlock tableBlock;
        if(inputSource instanceof BlockInputSource){
            tableBlock = (TableBlock) ((BlockInputSource) inputSource).getBlock();
        }else {
            setTableOriginalSource(inputSource);
            InputStream inputStream = inputSource.openStream();
            tableBlock = TableBlock.load(inputStream);
            inputStream.close();
        }
        BlockInputSource blockInputSource = new BlockInputSource<>(
                inputSource.getName(), tableBlock);
        blockInputSource.copyAttributes(inputSource);
        getZipEntryMap().add(blockInputSource);
        tableBlock.setApkFile(this);
        return tableBlock;
    }
    @Override
    public void add(InputSource inputSource){
        if(inputSource == null){
            return;
        }
        String path = inputSource.getAlias();
        if(AndroidManifestBlock.FILE_NAME.equals(path)){
            InputSource manifestSource = getManifestOriginalSource();
            if(manifestSource != inputSource){
                mManifestBlock = null;
            }
            setManifestOriginalSource(inputSource);
        }else if(TableBlock.FILE_NAME.equals(path)){
            InputSource table = getTableOriginalSource();
            if(inputSource != table){
                mTableBlock = null;
            }
            setTableOriginalSource(inputSource);
        }
        addInputSource(inputSource);
    }

    @Override
    public boolean containsFile(String path) {
        return getZipEntryMap().contains(path);
    }

    @Override
    public InputSource getInputSource(String path){
        return getZipEntryMap().getInputSource(path);
    }
    public InputSource removeInputSource(String path){
        return getZipEntryMap().remove(path);
    }
    private void addInputSource(InputSource inputSource){
        getZipEntryMap().add(inputSource);
    }
    public List listInputSources(){
        return getZipEntryMap().listInputSources();
    }
    public InputSource[] getInputSources(){
        return getZipEntryMap().toArray();
    }
    public ZipEntryMap getZipEntryMap() {
        return zipEntryMap;
    }
    public void setLoadDefaultFramework(boolean loadDefaultFramework) {
        this.loadDefaultFramework = loadDefaultFramework;
        this.mDisableLoadFramework = !loadDefaultFramework;
    }

    public void merge(ApkModule module) throws IOException {
        merge(module, false);
    }
    public void merge(ApkModule module, boolean force) throws IOException {
        if(module == null || module == this){
            return;
        }
        logMessage("Merging: " + module.getModuleName());
        validateMerge(module, force);
        mergeDexFiles(module);
        mergeTable(module);
        mergeFiles(module);
        getUncompressedFiles().merge(module.getUncompressedFiles());
    }
    private void validateMerge(ApkModule apkModule, boolean force) throws IOException{
        if(!hasTableBlock()) {
            return;
        }
        String packageName = getPackageName();
        int code = getVersionCode();
        if(packageName == null || code == 0) {
            return;
        }
        String packageName2 = apkModule.getPackageName();
        int code2 = apkModule.getVersionCode();
        if(packageName2 == null || code2 == 0) {
            return;
        }
        if(!packageName.equals(packageName2)) {
            return;
        }
        if(code == code2) {
            return;
        }
        StringBuilder builder = new StringBuilder();
        if(!force) {
            builder.append("WARN: ");
        }
        builder.append("Incompatible to merge: {");
        builder.append(packageName);
        builder.append(", ");
        builder.append(code);
        builder.append("}, with {");
        builder.append(packageName2);
        builder.append(", ");
        builder.append(code2);
        builder.append("}");
        String msg = builder.toString();
        if(force) {
            throw new IOException(msg);
        }
        logMessage(msg);
    }
    private void mergeTable(ApkModule module) {
        if(!module.hasTableBlock()){
            return;
        }
        TableBlock exist;
        if(!hasTableBlock()){
            exist=new TableBlock();
            BlockInputSource inputSource=new BlockInputSource<>(TableBlock.FILE_NAME, exist);
            addInputSource(inputSource);
        }else{
            exist=getTableBlock();
        }
        TableBlock coming=module.getTableBlock();
        exist.merge(coming);
    }
    private void mergeFiles(ApkModule module) {
        ZipEntryMap entryMapExist = getZipEntryMap();
        ZipEntryMap entryMapComing = module.getZipEntryMap();
        Map comingAlias = entryMapComing.toAliasMap();
        Map existAlias = entryMapExist.toAliasMap();
        UncompressedFiles uncompressedFiles = module.getUncompressedFiles();
        for(InputSource inputSource:comingAlias.values()){
            if(existAlias.containsKey(inputSource.getAlias())
                    || existAlias.containsKey(inputSource.getName())){
                continue;
            }
            if(DexFileInputSource.isDexName(inputSource.getName())){
                continue;
            }
            if(inputSource.getAlias().startsWith("lib/")){
                uncompressedFiles.removePath(inputSource.getAlias());
            }
            logVerbose("Added: " + inputSource.getAlias());
            entryMapExist.add(inputSource);
        }
    }
    private void mergeDexFiles(ApkModule module){
        UncompressedFiles uncompressedFiles = module.getUncompressedFiles();
        List existList = listDexFiles();
        List comingList = module.listDexFiles();
        ZipEntryMap zipEntryMap = getZipEntryMap();
        int index=0;
        if(existList.size()>0){
            index=existList.get(existList.size()-1).getDexNumber();
            if(index==0){
                index=2;
            }else {
                index++;
            }
        }
        for(DexFileInputSource source : comingList){
            uncompressedFiles.removePath(source.getAlias());
            String name = DexFileInputSource.getDexName(index);
            DexFileInputSource add = new DexFileInputSource(name, source.getInputSource());
            zipEntryMap.add(add);
            logMessage("Added [" + module.getModuleName() +"] "
                    + source.getAlias() + " -> " + name);
            index++;
            if(index==1){
                index=2;
            }
        }
    }
    public APKLogger getApkLogger(){
        return apkLogger;
    }
    public void setAPKLogger(APKLogger logger) {
        this.apkLogger = logger;
    }
    void logMessage(String msg) {
        if(apkLogger!=null){
            apkLogger.logMessage(msg);
        }
    }
    private void logError(String msg, Throwable tr) {
        if(apkLogger!=null){
            apkLogger.logError(msg, tr);
        }
    }
    private void logVerbose(String msg) {
        if(apkLogger!=null){
            apkLogger.logVerbose(msg);
        }
    }
    public void setCloseable(Closeable closeable){
        this.mCloseable = closeable;
    }
    @Override
    public void close() throws IOException {
        Closeable closeable = this.mCloseable;
        if(closeable != null){
            closeable.close();
        }
    }
    @Override
    public String toString(){
        return getModuleName();
    }
    public static ApkModule loadApkFile(File apkFile) throws IOException {
        return loadApkFile(apkFile, ApkUtil.DEF_MODULE_NAME);
    }
    public static ApkModule loadApkFile(File apkFile, String moduleName) throws IOException {
        ArchiveFile archive = new ArchiveFile(apkFile);
        ApkModule apkModule = new ApkModule(moduleName, archive.createZipEntryMap());
        apkModule.setApkSignatureBlock(archive.getApkSignatureBlock());
        apkModule.setCloseable(archive);
        return apkModule;
    }
    public static ApkModule loadApkFile(File apkFile, File ... externalFrameworks) throws IOException {
        return loadApkFile(null, apkFile, externalFrameworks);
    }
    public static ApkModule loadApkFile(APKLogger logger, File apkFile, File ... externalFrameworks) throws IOException {
        ArchiveFile archive = new ArchiveFile(apkFile);
        ApkModule apkModule = new ApkModule(ApkUtil.DEF_MODULE_NAME, archive.createZipEntryMap());
        apkModule.setAPKLogger(logger);
        apkModule.setApkSignatureBlock(archive.getApkSignatureBlock());
        apkModule.setCloseable(archive);
        if(externalFrameworks == null || externalFrameworks.length == 0){
            return apkModule;
        }
        for(File frameworkFile : externalFrameworks){
            if(frameworkFile == null){
                continue;
            }
            if(apkFile.equals(frameworkFile)){
                throw new IOException("External framework should be different: " + apkFile);
            }
            apkModule.addExternalFramework(frameworkFile);
        }
        return apkModule;
    }
    public static ApkModule readApkBytes(byte[] bytes) throws IOException {
        ArchiveBytes archiveBytes = new ArchiveBytes(bytes);
        ApkModule apkModule = new ApkModule(archiveBytes.createZipEntryMap());
        apkModule.setModuleName("byte_" + System.currentTimeMillis());
        return apkModule;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy